diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -476,6 +476,8 @@ // The code signature comes at the very end of the linked output file. class CodeSignatureSection final : public LinkEditSection { public: + // NOTE: These values are duplicated in llvm-objcopy's MachO/Object.h file + // and any changes here, should be repeated there. static constexpr uint8_t blockSizeShift = 12; static constexpr size_t blockSize = (1 << blockSizeShift); // 4 KiB static constexpr size_t hashSize = 256 / 8; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -1169,6 +1169,9 @@ size_t slashIndex = fileName.rfind("/"); if (slashIndex != std::string::npos) fileName = fileName.drop_front(slashIndex + 1); + + // NOTE: Any changes to these calculations should be repeated + // in llvm-objcopy's MachOLayoutBuilder::layoutTail. allHeadersSize = alignTo<16>(fixedHeadersSize + fileName.size() + 1); fileNamePad = allHeadersSize - fixedHeadersSize - fileName.size(); } @@ -1182,6 +1185,8 @@ } void CodeSignatureSection::writeHashes(uint8_t *buf) const { + // NOTE: Changes to this functionality should be repeated in llvm-objcopy's + // MachOWriter::writeSignatureData. uint8_t *code = buf; uint8_t *codeEnd = buf + fileOff; uint8_t *hashes = codeEnd + allHeadersSize; @@ -1212,6 +1217,8 @@ } void CodeSignatureSection::writeTo(uint8_t *buf) const { + // NOTE: Changes to this functionality should be repeated in llvm-objcopy's + // MachOWriter::writeSignatureData. uint32_t signatureSize = static_cast(getSize()); auto *superBlob = reinterpret_cast(buf); write32be(&superBlob->magic, CSMAGIC_EMBEDDED_SIGNATURE); diff --git a/llvm/test/tools/llvm-objcopy/MachO/Inputs/code-signature-check.py b/llvm/test/tools/llvm-objcopy/MachO/Inputs/code-signature-check.py new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/MachO/Inputs/code-signature-check.py @@ -0,0 +1,257 @@ +"""Checks the validity of MachO binary signatures + +MachO binaries sometimes include a LC_CODE_SIGNATURE load command +and corresponding section in the __LINKEDIT segment that together +work to "sign" the binary. This script is used to check the validity +of this signature. + +Usage: + ./code-signature-check.py my_binary 800 300 0 800 + +Arguments: + binary - The MachO binary to be tested + offset - The offset from the start of the binary to where the code signature section begins + size - The size of the code signature section in the binary + code_offset - The point in the binary to begin hashing + code_size - The length starting from code_offset to hash +""" + +import argparse +import collections +import hashlib +import itertools +import struct +import sys +import typing + +class CodeDirectoryVersion: + SUPPORTSSCATTER = 0x20100 + SUPPORTSTEAMID = 0x20200 + SUPPORTSCODELIMIT64 = 0x20300 + SUPPORTSEXECSEG = 0x20400 + +class CodeDirectory: + @staticmethod + def make(buf: memoryview) -> typing.Union['CodeDirectoryBase', 'CodeDirectoryV20100', 'CodeDirectoryV20200', 'CodeDirectoryV20300', 'CodeDirectoryV20400']: + _magic, _length, version = struct.unpack_from(">III", buf, 0) + subtype = { + CodeDirectoryVersion.SUPPORTSSCATTER: CodeDirectoryV20100, + CodeDirectoryVersion.SUPPORTSTEAMID: CodeDirectoryV20200, + CodeDirectoryVersion.SUPPORTSCODELIMIT64: CodeDirectoryV20300, + CodeDirectoryVersion.SUPPORTSEXECSEG: CodeDirectoryV20400, + }.get(version, CodeDirectoryBase) + + return subtype._make(struct.unpack_from(subtype._format(), buf, 0)) + +class CodeDirectoryBase(typing.NamedTuple): + magic: int + length: int + version: int + flags: int + hashOffset: int + identOffset: int + nSpecialSlots: int + nCodeSlots: int + codeLimit: int + hashSize: int + hashType: int + platform: int + pageSize: int + spare2: int + + @staticmethod + def _format() -> str: + return ">IIIIIIIIIBBBBI" + +class CodeDirectoryV20100(typing.NamedTuple): + magic: int + length: int + version: int + flags: int + hashOffset: int + identOffset: int + nSpecialSlots: int + nCodeSlots: int + codeLimit: int + hashSize: int + hashType: int + platform: int + pageSize: int + spare2: int + + scatterOffset: int + + @staticmethod + def _format() -> str: + return CodeDirectoryBase._format() + "I" + +class CodeDirectoryV20200(typing.NamedTuple): + magic: int + length: int + version: int + flags: int + hashOffset: int + identOffset: int + nSpecialSlots: int + nCodeSlots: int + codeLimit: int + hashSize: int + hashType: int + platform: int + pageSize: int + spare2: int + + scatterOffset: int + + teamOffset: int + + @staticmethod + def _format() -> str: + return CodeDirectoryV20100._format() + "I" + +class CodeDirectoryV20300(typing.NamedTuple): + magic: int + length: int + version: int + flags: int + hashOffset: int + identOffset: int + nSpecialSlots: int + nCodeSlots: int + codeLimit: int + hashSize: int + hashType: int + platform: int + pageSize: int + spare2: int + + scatterOffset: int + + teamOffset: int + + spare3: int + codeLimit64: int + + @staticmethod + def _format() -> str: + return CodeDirectoryV20200._format() + "IQ" + +class CodeDirectoryV20400(typing.NamedTuple): + magic: int + length: int + version: int + flags: int + hashOffset: int + identOffset: int + nSpecialSlots: int + nCodeSlots: int + codeLimit: int + hashSize: int + hashType: int + platform: int + pageSize: int + spare2: int + + scatterOffset: int + + teamOffset: int + + spare3: int + codeLimit64: int + + execSegBase: int + execSegLimit: int + execSegFlags: int + + @staticmethod + def _format() -> str: + return CodeDirectoryV20300._format() + "QQQ" + +class CodeDirectoryBlobIndex(typing.NamedTuple): + type_: int + offset: int + + @staticmethod + def make(buf: memoryview) -> 'CodeDirectoryBlobIndex': + return CodeDirectoryBlobIndex._make(struct.unpack_from(CodeDirectoryBlobIndex.__format(), buf, 0)) + + @staticmethod + def bytesize() -> int: + return struct.calcsize(CodeDirectoryBlobIndex.__format()) + + @staticmethod + def __format() -> str: + return ">II" + +class CodeDirectorySuperBlob(typing.NamedTuple): + magic: int + length: int + count: int + blob_indices: typing.List[CodeDirectoryBlobIndex] + + @staticmethod + def make(buf: memoryview) -> 'CodeDirectorySuperBlob': + super_blob_layout = ">III" + super_blob = struct.unpack_from(super_blob_layout, buf, 0) + + offset = struct.calcsize(super_blob_layout) + blob_indices = [] + for idx in range(super_blob[2]): + blob_indices.append(CodeDirectoryBlobIndex.make(buf[offset:])) + offset += CodeDirectoryBlobIndex.bytesize() + + return CodeDirectorySuperBlob(*super_blob, blob_indices) + +def unpack_null_terminated_string(buf: memoryview) -> str: + b = bytes(itertools.takewhile(lambda b: b != 0, buf)) + return b.decode() + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('binary', type=argparse.FileType('rb'), help='The file to analyze') + parser.add_argument('offset', type=int, help='Offset to start of Code Directory data') + parser.add_argument('size', type=int, help='Size of Code Directory data') + parser.add_argument('code_offset', type=int, help='Offset to start of code pages to hash') + parser.add_argument('code_size', type=int, help='Size of the code pages to hash') + + args = parser.parse_args() + + args.binary.seek(args.offset) + super_blob_bytes = args.binary.read(args.size) + super_blob_mem = memoryview(super_blob_bytes) + + super_blob = CodeDirectorySuperBlob.make(super_blob_mem) + print(super_blob) + + for blob_index in super_blob.blob_indices: + code_directory_offset = blob_index.offset + code_directory = CodeDirectory.make(super_blob_mem[code_directory_offset:]) + print(code_directory) + + ident_offset = code_directory_offset + code_directory.identOffset + print("Code Directory ID: " + unpack_null_terminated_string(super_blob_mem[ident_offset:])) + + code_offset = args.code_offset + code_end = code_offset + args.code_size + page_size = 1 << code_directory.pageSize + args.binary.seek(code_offset) + + hashes_offset = code_directory_offset + code_directory.hashOffset + for idx in range(code_directory.nCodeSlots): + hash_bytes = bytes(super_blob_mem[hashes_offset:hashes_offset+code_directory.hashSize]) + hashes_offset += code_directory.hashSize + + hasher = hashlib.sha256() + read_size = min(page_size, code_end - code_offset) + hasher.update(args.binary.read(read_size)) + calculated_hash_bytes = hasher.digest() + code_offset += read_size + + print("%s <> %s" % (hash_bytes.hex(), calculated_hash_bytes.hex())) + + if hash_bytes != calculated_hash_bytes: + sys.exit(-1) + + +if __name__ == '__main__': + main() diff --git a/llvm/test/tools/llvm-objcopy/MachO/code_signature_lc.test b/llvm/test/tools/llvm-objcopy/MachO/code_signature_lc.test --- a/llvm/test/tools/llvm-objcopy/MachO/code_signature_lc.test +++ b/llvm/test/tools/llvm-objcopy/MachO/code_signature_lc.test @@ -1,40 +1,253 @@ # RUN: yaml2obj %s -o %t ## Verify that the input file is valid and contains the expected load command. -# RUN: llvm-objdump --private-headers %t | FileCheck %s +# RUN: llvm-objdump --private-headers %t | FileCheck %s --check-prefix=CHECK-ORIGINAL -# CHECK: cmd LC_CODE_SIGNATURE -# CHECK-NEXT: cmdsize 16 -# CHECK-NEXT: dataoff 128 -# CHECK-NEXT: datasize 16 +# CHECK-ORIGINAL: cmd LC_CODE_SIGNATURE +# CHECK-ORIGINAL-NEXT: cmdsize 16 +# CHECK-ORIGINAL-NEXT: dataoff 16544 +# CHECK-ORIGINAL-NEXT: datasize 280 # RUN: llvm-objcopy %t %t.copy -# RUN: cmp %t %t.copy +# RUN: obj2yaml %t > %t.yaml +# RUN: obj2yaml %t.copy > %t.copy.yaml + +## Verify that the copy still includes the load command +# RUN: cat %t.copy.yaml | FileCheck %s --check-prefix=CHECK-COPY +# CHECK-COPY: - cmd: LC_CODE_SIGNATURE +# CHECK-COPY-NEXT: cmdsize: 16 +# CHECK-COPY-NEXT: dataoff: 16544 +# CHECK-COPY-NEXT: datasize: 304 + +## Remove information changed by regeneration of load command: +## - __LINKEDIT segment filesize may change +## - LC_CODE_SIGNATURE command dataoff and datasize may change +## - __LINKEDIT data locations may change + +# RUN: sed -e '/__LINKEDIT/,+4d' \ +# RUN: -e '/LC_CODE_SIGNATURE/,+3d' \ +# RUN: -e '/n_strx/d' \ +# RUN: -e '/dyld_stub_binder/d' %t.yaml > %t.clean.yaml + +# RUN: sed -e '/__LINKEDIT/,+4d' \ +# RUN: -e '/LC_CODE_SIGNATURE/,+3d' \ +# RUN: -e '/n_strx/d' \ +# RUN: -e '/dyld_stub_binder/d' %t.copy.yaml > %t.copy.clean.yaml + +## Verify the remainder of the object file remains unchanged +# RUN: diff %t.clean.yaml %t.copy.clean.yaml + +## Verify the new signature is valid +# RUN: %python %p/Inputs/code-signature-check.py %t.copy 16544 304 0 16544 --- !mach-o FileHeader: magic: 0xFEEDFACF - cputype: 0x01000007 - cpusubtype: 0x80000003 - filetype: 0x00000002 - ncmds: 2 - sizeofcmds: 88 - flags: 0x00218085 - reserved: 0x00000000 + cputype: 0x1000007 + cpusubtype: 0x3 + filetype: 0x2 + ncmds: 15 + sizeofcmds: 760 + flags: 0x200085 + reserved: 0x0 LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __PAGEZERO + vmaddr: 0 + vmsize: 4294967296 + fileoff: 0 + filesize: 0 + maxprot: 0 + initprot: 0 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 232 + segname: __TEXT + vmaddr: 4294967296 + vmsize: 16384 + fileoff: 0 + filesize: 16384 + maxprot: 5 + initprot: 5 + nsects: 2 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x100003FA0 + size: 15 + offset: 0x3FA0 + align: 4 + reloff: 0x0 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 554889E531C0C745FC000000005DC3 + - sectname: __unwind_info + segname: __TEXT + addr: 0x100003FB0 + size: 72 + offset: 0x3FB0 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 010000001C000000000000001C000000000000001C00000002000000A03F00003400000034000000B03F00000000000034000000030000000C000100100001000000000000000001 - cmd: LC_SEGMENT_64 cmdsize: 72 segname: __LINKEDIT - vmaddr: 4294979584 - vmsize: 4096 - fileoff: 120 - filesize: 24 - maxprot: 7 + vmaddr: 4294983680 + vmsize: 16384 + fileoff: 16384 + filesize: 440 + maxprot: 1 initprot: 1 nsects: 0 flags: 0 + - cmd: LC_DYLD_INFO_ONLY + cmdsize: 48 + rebase_off: 0 + rebase_size: 0 + bind_off: 0 + bind_size: 0 + weak_bind_off: 0 + weak_bind_size: 0 + lazy_bind_off: 0 + lazy_bind_size: 0 + export_off: 16384 + export_size: 48 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 16440 + nsyms: 3 + stroff: 16488 + strsize: 48 + - cmd: LC_DYSYMTAB + cmdsize: 80 + ilocalsym: 0 + nlocalsym: 0 + iextdefsym: 0 + nextdefsym: 2 + iundefsym: 2 + nundefsym: 1 + tocoff: 0 + ntoc: 0 + modtaboff: 0 + nmodtab: 0 + extrefsymoff: 0 + nextrefsyms: 0 + indirectsymoff: 0 + nindirectsyms: 0 + extreloff: 0 + nextrel: 0 + locreloff: 0 + nlocrel: 0 + - cmd: LC_LOAD_DYLINKER + cmdsize: 32 + name: 12 + Content: '/usr/lib/dyld' + ZeroPadBytes: 7 + - cmd: LC_UUID + cmdsize: 24 + uuid: 42759668-1CBA-3094-8E2D-F01E1A66E815 + - cmd: LC_BUILD_VERSION + cmdsize: 32 + platform: 1 + minos: 720896 + sdk: 721664 + ntools: 1 + Tools: + - tool: 3 + version: 42600704 + - cmd: LC_SOURCE_VERSION + cmdsize: 16 + version: 0 + - cmd: LC_MAIN + cmdsize: 24 + entryoff: 16288 + stacksize: 0 + - cmd: LC_LOAD_DYLIB + cmdsize: 56 + dylib: + name: 24 + timestamp: 2 + current_version: 84698117 + compatibility_version: 65536 + Content: '/usr/lib/libSystem.B.dylib' + ZeroPadBytes: 6 + - cmd: LC_FUNCTION_STARTS + cmdsize: 16 + dataoff: 16432 + datasize: 8 + - cmd: LC_DATA_IN_CODE + cmdsize: 16 + dataoff: 16440 + datasize: 0 - cmd: LC_CODE_SIGNATURE cmdsize: 16 - dataoff: 128 - datasize: 16 + dataoff: 16544 + datasize: 280 +LinkEditData: + ExportTrie: + TerminalSize: 0 + NodeOffset: 0 + Name: '' + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 0 + NodeOffset: 5 + Name: _ + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 2 + NodeOffset: 33 + Name: _mh_execute_header + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + - TerminalSize: 3 + NodeOffset: 37 + Name: main + Flags: 0x0 + Address: 0x3FA0 + Other: 0x0 + ImportName: '' + NameList: + - n_strx: 2 + n_type: 0xF + n_sect: 1 + n_desc: 16 + n_value: 4294967296 + - n_strx: 22 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294983584 + - n_strx: 28 + n_type: 0x1 + n_sect: 0 + n_desc: 256 + n_value: 0 + StringTable: + - ' ' + - __mh_execute_header + - _main + - dyld_stub_binder + - '' + - '' + - '' ... diff --git a/llvm/test/tools/llvm-objcopy/MachO/code_signature_lc_update.test b/llvm/test/tools/llvm-objcopy/MachO/code_signature_lc_update.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/MachO/code_signature_lc_update.test @@ -0,0 +1,284 @@ +# RUN: yaml2obj %s -o %t + +# RUN: llvm-objdump --private-headers %t | FileCheck %s --check-prefix=CHECK-ORIGINAL + +## Check offset, size and index of text segment command +# CHECK-ORIGINAL: Load command 1 +# CHECK-ORIGINAL-NEXT cmdsize +# CHECK-ORIGINAL-NEXT segname __TEXT +# CHECK-ORIGINAL-NEXT vmaddr +# CHECK-ORIGINAL-NEXT vmsize +# CHECK-ORIGINAL-NEXT fileoff 0 +# CHECK-ORIGINAL-NEXT filesize 16384 + +## Check offset and index of code signature command +# CHECK-ORIGINAL: Load command 14 +# CHECK-ORIGINAL-NEXT: cmd LC_CODE_SIGNATURE +# CHECK-ORIGINAL-NEXT: cmdsize 16 +# CHECK-ORIGINAL-NEXT: dataoff 16544 +# CHECK-ORIGINAL-NEXT: datasize 280 + +# RUN: llvm-install-name-tool -prepend_rpath abcd %t + +# RUN: llvm-objdump --private-headers %t | FileCheck %s --check-prefix=CHECK-PREPEND +## Verify that the binary contains the new RPATH command, as the first command +# CHECK-PREPEND: Load command 0 +# CHECK-PREPEND-NEXT: cmd LC_RPATH +# CHECK-PREPEND-NEXT: cmdsize +# CHECK-PREPEND-NEXT: path abcd + +## Verify the text segment command index increased by 1 +# CHECK-PREPEND: Load command 2 +# CHECK-PREPEND-NEXT cmdsize +# CHECK-PREPEND-NEXT segname __TEXT +# CHECK-PREPEND-NEXT vmaddr +# CHECK-PREPEND-NEXT vmsize +# CHECK-PREPEND-NEXT fileoff 0 +# CHECK-PREPEND-NEXT filesize 16384 + +## Verify the code signature command index increased by 1 +# CHECK-PREPEND: Load command 15 +# CHECK-PREPEND-NEXT: cmd LC_CODE_SIGNATURE +# CHECK-PREPEND-NEXT: cmdsize 16 +# CHECK-PREPEND-NEXT: dataoff 16544 +# CHECK-PREPEND-NEXT: datasize 320 + +## Verify the new signature is valid +# RUN: %python %p/Inputs/code-signature-check.py %t 16544 320 0 16544 | FileCheck %s --check-prefix=CHECK-TEXT-SEGMENT +## Verify the text segment offset and text segment size values included in the signature header are accurate +# CHECK-TEXT-SEGMENT: execSegBase=0, execSegLimit=16384 + +# RUN: llvm-install-name-tool -delete_rpath abcd %t + +# RUN: llvm-objdump --private-headers %t | FileCheck %s --check-prefix=CHECK-REMOVE + +## Verify text segment command index returned to orignal +# CHECK-REMOVE: Load command 1 +# CHECK-REMOVE-NEXT cmdsize +# CHECK-REMOVE-NEXT segname __TEXT +# CHECK-REMOVE-NEXT vmaddr +# CHECK-REMOVE-NEXT vmsize +# CHECK-REMOVE-NEXT fileoff 0 +# CHECK-REMOVE-NEXT filesize 16384 + +## Verify text segment command index returned to original +# CHECK-REMOVE: Load command 14 +# CHECK-REMOVE-NEXT: cmd LC_CODE_SIGNATURE +# CHECK-REMOVE-NEXT: cmdsize 16 +# CHECK-REMOVE-NEXT: dataoff 16544 +# CHECK-REMOVE-NEXT: datasize 320 + +## Verify the new signature is valid and text segment values are accurate +# RUN: %python %p/Inputs/code-signature-check.py %t 16544 320 0 16544 | FileCheck %s --check-prefix=CHECK-TEXT-SEGMENT + +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x1000007 + cpusubtype: 0x3 + filetype: 0x2 + ncmds: 15 + sizeofcmds: 760 + flags: 0x200085 + reserved: 0x0 +LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __PAGEZERO + vmaddr: 0 + vmsize: 4294967296 + fileoff: 0 + filesize: 0 + maxprot: 0 + initprot: 0 + nsects: 0 + flags: 0 + - cmd: LC_SEGMENT_64 + cmdsize: 232 + segname: __TEXT + vmaddr: 4294967296 + vmsize: 16384 + fileoff: 0 + filesize: 16384 + maxprot: 5 + initprot: 5 + nsects: 2 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x100003FA0 + size: 15 + offset: 0x3FA0 + align: 4 + reloff: 0x0 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 554889E531C0C745FC000000005DC3 + - sectname: __unwind_info + segname: __TEXT + addr: 0x100003FB0 + size: 72 + offset: 0x3FB0 + align: 2 + reloff: 0x0 + nreloc: 0 + flags: 0x0 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: 010000001C000000000000001C000000000000001C00000002000000A03F00003400000034000000B03F00000000000034000000030000000C000100100001000000000000000001 + - cmd: LC_SEGMENT_64 + cmdsize: 72 + segname: __LINKEDIT + vmaddr: 4294983680 + vmsize: 16384 + fileoff: 16384 + filesize: 440 + maxprot: 1 + initprot: 1 + nsects: 0 + flags: 0 + - cmd: LC_DYLD_INFO_ONLY + cmdsize: 48 + rebase_off: 0 + rebase_size: 0 + bind_off: 0 + bind_size: 0 + weak_bind_off: 0 + weak_bind_size: 0 + lazy_bind_off: 0 + lazy_bind_size: 0 + export_off: 16384 + export_size: 48 + - cmd: LC_SYMTAB + cmdsize: 24 + symoff: 16440 + nsyms: 3 + stroff: 16488 + strsize: 48 + - cmd: LC_DYSYMTAB + cmdsize: 80 + ilocalsym: 0 + nlocalsym: 0 + iextdefsym: 0 + nextdefsym: 2 + iundefsym: 2 + nundefsym: 1 + tocoff: 0 + ntoc: 0 + modtaboff: 0 + nmodtab: 0 + extrefsymoff: 0 + nextrefsyms: 0 + indirectsymoff: 0 + nindirectsyms: 0 + extreloff: 0 + nextrel: 0 + locreloff: 0 + nlocrel: 0 + - cmd: LC_LOAD_DYLINKER + cmdsize: 32 + name: 12 + Content: '/usr/lib/dyld' + ZeroPadBytes: 7 + - cmd: LC_UUID + cmdsize: 24 + uuid: 42759668-1CBA-3094-8E2D-F01E1A66E815 + - cmd: LC_BUILD_VERSION + cmdsize: 32 + platform: 1 + minos: 720896 + sdk: 721664 + ntools: 1 + Tools: + - tool: 3 + version: 42600704 + - cmd: LC_SOURCE_VERSION + cmdsize: 16 + version: 0 + - cmd: LC_MAIN + cmdsize: 24 + entryoff: 16288 + stacksize: 0 + - cmd: LC_LOAD_DYLIB + cmdsize: 56 + dylib: + name: 24 + timestamp: 2 + current_version: 84698117 + compatibility_version: 65536 + Content: '/usr/lib/libSystem.B.dylib' + ZeroPadBytes: 6 + - cmd: LC_FUNCTION_STARTS + cmdsize: 16 + dataoff: 16432 + datasize: 8 + - cmd: LC_DATA_IN_CODE + cmdsize: 16 + dataoff: 16440 + datasize: 0 + - cmd: LC_CODE_SIGNATURE + cmdsize: 16 + dataoff: 16544 + datasize: 280 +LinkEditData: + ExportTrie: + TerminalSize: 0 + NodeOffset: 0 + Name: '' + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 0 + NodeOffset: 5 + Name: _ + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + Children: + - TerminalSize: 2 + NodeOffset: 33 + Name: _mh_execute_header + Flags: 0x0 + Address: 0x0 + Other: 0x0 + ImportName: '' + - TerminalSize: 3 + NodeOffset: 37 + Name: main + Flags: 0x0 + Address: 0x3FA0 + Other: 0x0 + ImportName: '' + NameList: + - n_strx: 2 + n_type: 0xF + n_sect: 1 + n_desc: 16 + n_value: 4294967296 + - n_strx: 22 + n_type: 0xF + n_sect: 1 + n_desc: 0 + n_value: 4294983584 + - n_strx: 28 + n_type: 0x1 + n_sect: 0 + n_desc: 256 + n_value: 0 + StringTable: + - ' ' + - __mh_execute_header + - _main + - dyld_stub_binder + - '' + - '' + - '' +... diff --git a/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.h b/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.h --- a/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.h +++ b/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.h @@ -16,10 +16,49 @@ namespace objcopy { namespace macho { +/// When MachO binaries include a LC_CODE_SIGNATURE load command, +/// the __LINKEDIT data segment will include a section corresponding +/// to the LC_CODE_SIGNATURE load command. This section serves as a signature +/// for the binary. Included in the CodeSignature section is a header followed +/// by a hash of the binary. If present, the CodeSignature section is the +/// last component of the binary. +struct CodeSignatureInfo { + // NOTE: These values are to be kept in sync with those in + // LLD's CodeSignatureSection class. + + static constexpr uint32_t Align = 16; + static constexpr uint8_t BlockSizeShift = 12; + // The binary is read in blocks of the following size. + static constexpr size_t BlockSize = (1 << BlockSizeShift); // 4 KiB + // For each block, a SHA256 hash (256 bits, 32 bytes) is written to + // the CodeSignature section. + static constexpr size_t HashSize = 256 / 8; + static constexpr size_t BlobHeadersSize = llvm::alignTo<8>( + sizeof(llvm::MachO::CS_SuperBlob) + sizeof(llvm::MachO::CS_BlobIndex)); + // The size of the entire header depends upon the filename the binary is being + // written to, but the rest of the header is fixed in size. + static constexpr uint32_t FixedHeadersSize = + BlobHeadersSize + sizeof(llvm::MachO::CS_CodeDirectory); + + // The offset relative to the start of the binary where + // the CodeSignature section should begin. + uint32_t StartOffset; + // The size of the entire header, output file name size included. + uint32_t AllHeadersSize; + // The number of blocks required to hash the binary. + uint32_t BlockCount; + StringRef OutputFileName; + // The size of the entire CodeSignature section, including both the header and + // hashes. + uint32_t Size; +}; + class MachOLayoutBuilder { Object &O; bool Is64Bit; + StringRef OutputFileName; uint64_t PageSize; + CodeSignatureInfo CodeSignature; // Points to the __LINKEDIT segment if it exists. MachO::macho_load_command *LinkEditLoadCommand = nullptr; @@ -37,14 +76,18 @@ bool Is64Bit); public: - MachOLayoutBuilder(Object &O, bool Is64Bit, uint64_t PageSize) - : O(O), Is64Bit(Is64Bit), PageSize(PageSize), + MachOLayoutBuilder(Object &O, bool Is64Bit, StringRef OutputFileName, + uint64_t PageSize) + : O(O), Is64Bit(Is64Bit), OutputFileName(OutputFileName), + PageSize(PageSize), StrTableBuilder(getStringTableBuilderKind(O, Is64Bit)) {} // Recomputes and updates fields in the given object such as file offsets. Error layout(); StringTableBuilder &getStringTableBuilder() { return StrTableBuilder; } + + const CodeSignatureInfo &getCodeSignature() { return CodeSignature; } }; } // end namespace macho diff --git a/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.cpp b/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOLayoutBuilder.cpp @@ -262,10 +262,31 @@ sizeof(uint32_t) * O.IndirectSymTable.Symbols.size(); uint64_t StartOfCodeSignature = StartOfSymbolStrings + StrTableBuilder.getSize(); - if (O.CodeSignatureCommandIndex) + uint32_t CodeSignatureSize = 0; + if (O.CodeSignatureCommandIndex) { StartOfCodeSignature = alignTo(StartOfCodeSignature, 16); + + // Note: These calculations are to be kept in sync with the same + // calculations performed in LLD's CodeSignatureSection. + const uint32_t AllHeadersSize = + alignTo(CodeSignature.FixedHeadersSize + OutputFileName.size() + 1, + CodeSignature.Align); + const uint32_t BlockCount = + (StartOfCodeSignature + CodeSignature.BlockSize - 1) / + CodeSignature.BlockSize; + const uint32_t Size = + alignTo(AllHeadersSize + BlockCount * CodeSignature.HashSize, + CodeSignature.Align); + + CodeSignature.StartOffset = StartOfCodeSignature; + CodeSignature.AllHeadersSize = AllHeadersSize; + CodeSignature.BlockCount = BlockCount; + CodeSignature.OutputFileName = OutputFileName; + CodeSignature.Size = Size; + CodeSignatureSize = Size; + } uint64_t LinkEditSize = - (StartOfCodeSignature + O.CodeSignature.Data.size()) - StartOfLinkEdit; + StartOfCodeSignature + CodeSignatureSize - StartOfLinkEdit; // Now we have determined the layout of the contents of the __LINKEDIT // segment. Update its load command. @@ -293,7 +314,7 @@ switch (cmd) { case MachO::LC_CODE_SIGNATURE: MLC.linkedit_data_command_data.dataoff = StartOfCodeSignature; - MLC.linkedit_data_command_data.datasize = O.CodeSignature.Data.size(); + MLC.linkedit_data_command_data.datasize = CodeSignatureSize; break; case MachO::LC_SYMTAB: MLC.symtab_command_data.symoff = StartOfSymbols; diff --git a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp @@ -20,6 +20,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileOutputBuffer.h" +#include "llvm/Support/Path.h" #include "llvm/Support/SmallVectorMemoryBuffer.h" using namespace llvm; @@ -404,7 +405,8 @@ PageSize = 4096; } - MachOWriter Writer(**O, In.is64Bit(), In.isLittleEndian(), PageSize, Out); + MachOWriter Writer(**O, In.is64Bit(), In.isLittleEndian(), + sys::path::filename(Config.OutputFilename), PageSize, Out); if (auto E = Writer.finalize()) return E; return Writer.write(); diff --git a/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp b/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp @@ -116,6 +116,7 @@ Error MachOReader::readLoadCommands(Object &O) const { // For MachO sections indices start from 1. uint32_t NextSectionIndex = 1; + static constexpr char TextSegmentName[] = "__TEXT"; for (auto LoadCmd : MachOObj.load_commands()) { LoadCommand LC; switch (LoadCmd.C.cmd) { @@ -123,6 +124,11 @@ O.CodeSignatureCommandIndex = O.LoadCommands.size(); break; case MachO::LC_SEGMENT: + if (StringRef( + reinterpret_cast(LoadCmd.Ptr) + ->segname) == TextSegmentName) + O.TextSegmentCommandIndex = O.LoadCommands.size(); + if (Expected>> Sections = extractSections( LoadCmd, MachOObj, NextSectionIndex)) @@ -131,6 +137,11 @@ return Sections.takeError(); break; case MachO::LC_SEGMENT_64: + if (StringRef( + reinterpret_cast(LoadCmd.Ptr) + ->segname) == TextSegmentName) + O.TextSegmentCommandIndex = O.LoadCommands.size(); + if (Expected>> Sections = extractSections( LoadCmd, MachOObj, NextSectionIndex)) @@ -271,10 +282,6 @@ arrayRefFromStringRef(MachOObj.getData().substr(LC.dataoff, LC.datasize)); } -void MachOReader::readCodeSignature(Object &O) const { - return readLinkData(O, O.CodeSignatureCommandIndex, O.CodeSignature); -} - void MachOReader::readDataInCodeData(Object &O) const { return readLinkData(O, O.DataInCodeCommandIndex, O.DataInCode); } @@ -336,7 +343,6 @@ readWeakBindInfo(*Obj); readLazyBindInfo(*Obj); readExportInfo(*Obj); - readCodeSignature(*Obj); readDataInCodeData(*Obj); readLinkerOptimizationHint(*Obj); readFunctionStartsData(*Obj); diff --git a/llvm/tools/llvm-objcopy/MachO/MachOWriter.h b/llvm/tools/llvm-objcopy/MachO/MachOWriter.h --- a/llvm/tools/llvm-objcopy/MachO/MachOWriter.h +++ b/llvm/tools/llvm-objcopy/MachO/MachOWriter.h @@ -53,10 +53,11 @@ void writeTail(); public: - MachOWriter(Object &O, bool Is64Bit, bool IsLittleEndian, uint64_t PageSize, - raw_ostream &Out) + MachOWriter(Object &O, bool Is64Bit, bool IsLittleEndian, + StringRef OutputFileName, uint64_t PageSize, raw_ostream &Out) : O(O), Is64Bit(Is64Bit), IsLittleEndian(IsLittleEndian), - PageSize(PageSize), Out(Out), LayoutBuilder(O, Is64Bit, PageSize) {} + PageSize(PageSize), Out(Out), + LayoutBuilder(O, Is64Bit, OutputFileName, PageSize) {} size_t totalSize() const; Error finalize(); diff --git a/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp b/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp @@ -14,10 +14,16 @@ #include "llvm/Object/MachO.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/SHA256.h" #include +#if defined(__APPLE__) +#include +#endif + using namespace llvm; using namespace llvm::objcopy::macho; +using namespace llvm::support::endian; size_t MachOWriter::headerSize() const { return Is64Bit ? sizeof(MachO::mach_header_64) : sizeof(MachO::mach_header); @@ -423,8 +429,147 @@ memcpy(Out, LD.Data.data(), LD.Data.size()); } +static uint64_t +getSegmentFileOffset(const LoadCommand &TextSegmentLoadCommand) { + const MachO::macho_load_command &MLC = + TextSegmentLoadCommand.MachOLoadCommand; + switch (MLC.load_command_data.cmd) { + case MachO::LC_SEGMENT: + return MLC.segment_command_data.fileoff; + case MachO::LC_SEGMENT_64: + return MLC.segment_command_64_data.fileoff; + default: + return 0; + } +} + +static uint64_t getSegmentFileSize(const LoadCommand &TextSegmentLoadCommand) { + const MachO::macho_load_command &MLC = + TextSegmentLoadCommand.MachOLoadCommand; + switch (MLC.load_command_data.cmd) { + case MachO::LC_SEGMENT: + return MLC.segment_command_data.filesize; + case MachO::LC_SEGMENT_64: + return MLC.segment_command_64_data.filesize; + default: + return 0; + } +} + void MachOWriter::writeCodeSignatureData() { - return writeLinkData(O.CodeSignatureCommandIndex, O.CodeSignature); + // NOTE: This CodeSignature section behaviour must be kept in sync with that + // performed in LLD's CodeSignatureSection::write / + // CodeSignatureSection::writeHashes. Furthermore, this call must occur only + // after the rest of the binary has already been written to the buffer. This + // is because the buffer is read from to perform the necessary hashing. + + // The CodeSignature section is the last section in the MachO binary and + // contains a hash of all content in the binary before it. Since llvm-objcopy + // has likely modified the target binary, the hash must be regenerated + // entirely. To generate this hash, we must read from the start of the binary + // (HashReadStart) to just before the start of the CodeSignature section + // (HashReadEnd). + + const CodeSignatureInfo &CodeSignature = LayoutBuilder.getCodeSignature(); + + uint8_t *BufferStart = reinterpret_cast(Buf->getBufferStart()); + uint8_t *HashReadStart = BufferStart; + uint8_t *HashReadEnd = BufferStart + CodeSignature.StartOffset; + + // The CodeSignature section begins with a header, after which the hashes + // of each page of the binary are written. + uint8_t *HashWriteStart = HashReadEnd + CodeSignature.AllHeadersSize; + + uint32_t TextSegmentFileOff = 0; + uint32_t TextSegmentFileSize = 0; + if (O.TextSegmentCommandIndex) { + const LoadCommand &TextSegmentLoadCommand = + O.LoadCommands[*O.TextSegmentCommandIndex]; + assert(TextSegmentLoadCommand.MachOLoadCommand.load_command_data.cmd == + MachO::LC_SEGMENT || + TextSegmentLoadCommand.MachOLoadCommand.load_command_data.cmd == + MachO::LC_SEGMENT_64); + assert(StringRef(TextSegmentLoadCommand.MachOLoadCommand + .segment_command_data.segname) == "__TEXT"); + TextSegmentFileOff = getSegmentFileOffset(TextSegmentLoadCommand); + TextSegmentFileSize = getSegmentFileSize(TextSegmentLoadCommand); + } + + const uint32_t FileNamePad = CodeSignature.AllHeadersSize - + CodeSignature.FixedHeadersSize - + CodeSignature.OutputFileName.size(); + + // Write code section header. + auto *SuperBlob = reinterpret_cast(HashReadEnd); + write32be(&SuperBlob->magic, MachO::CSMAGIC_EMBEDDED_SIGNATURE); + write32be(&SuperBlob->length, CodeSignature.Size); + write32be(&SuperBlob->count, 1); + auto *BlobIndex = reinterpret_cast(&SuperBlob[1]); + write32be(&BlobIndex->type, MachO::CSSLOT_CODEDIRECTORY); + write32be(&BlobIndex->offset, CodeSignature.BlobHeadersSize); + auto *CodeDirectory = reinterpret_cast( + HashReadEnd + CodeSignature.BlobHeadersSize); + write32be(&CodeDirectory->magic, MachO::CSMAGIC_CODEDIRECTORY); + write32be(&CodeDirectory->length, + CodeSignature.Size - CodeSignature.BlobHeadersSize); + write32be(&CodeDirectory->version, MachO::CS_SUPPORTSEXECSEG); + write32be(&CodeDirectory->flags, MachO::CS_ADHOC | MachO::CS_LINKER_SIGNED); + write32be(&CodeDirectory->hashOffset, + sizeof(MachO::CS_CodeDirectory) + + CodeSignature.OutputFileName.size() + FileNamePad); + write32be(&CodeDirectory->identOffset, sizeof(MachO::CS_CodeDirectory)); + CodeDirectory->nSpecialSlots = 0; + write32be(&CodeDirectory->nCodeSlots, CodeSignature.BlockCount); + write32be(&CodeDirectory->codeLimit, CodeSignature.StartOffset); + CodeDirectory->hashSize = static_cast(CodeSignature.HashSize); + CodeDirectory->hashType = MachO::kSecCodeSignatureHashSHA256; + CodeDirectory->platform = 0; + CodeDirectory->pageSize = CodeSignature.BlockSizeShift; + CodeDirectory->spare2 = 0; + CodeDirectory->scatterOffset = 0; + CodeDirectory->teamOffset = 0; + CodeDirectory->spare3 = 0; + CodeDirectory->codeLimit64 = 0; + write64be(&CodeDirectory->execSegBase, TextSegmentFileOff); + write64be(&CodeDirectory->execSegLimit, TextSegmentFileSize); + write64be(&CodeDirectory->execSegFlags, O.Header.FileType == MachO::MH_EXECUTE + ? MachO::CS_EXECSEG_MAIN_BINARY + : 0); + + auto *Id = reinterpret_cast(&CodeDirectory[1]); + memcpy(Id, CodeSignature.OutputFileName.begin(), + CodeSignature.OutputFileName.size()); + memset(Id + CodeSignature.OutputFileName.size(), 0, FileNamePad); + + // Write the hashes. + uint8_t *CurrHashReadPosition = HashReadStart; + uint8_t *CurrHashWritePosition = HashWriteStart; + while (CurrHashReadPosition < HashReadEnd) { + StringRef Block(reinterpret_cast(CurrHashReadPosition), + std::min(HashReadEnd - CurrHashReadPosition, + static_cast(CodeSignature.BlockSize))); + SHA256 Hasher; + Hasher.update(Block); + StringRef Hash = Hasher.final(); + assert(Hash.size() == CodeSignature.HashSize); + memcpy(CurrHashWritePosition, Hash.data(), CodeSignature.HashSize); + CurrHashReadPosition += CodeSignature.BlockSize; + CurrHashWritePosition += CodeSignature.HashSize; + } +#if defined(__APPLE__) + // This is macOS-specific work-around and makes no sense for any + // other host OS. See https://openradar.appspot.com/FB8914231 + // + // The macOS kernel maintains a signature-verification cache to + // quickly validate applications at time of execve(2). The trouble + // is that for the kernel creates the cache entry at the time of the + // mmap(2) call, before we have a chance to write either the code to + // sign or the signature header+hashes. The fix is to invalidate + // all cached data associated with the output file, thus discarding + // the bogus prematurely-cached signature. + msync(BufferStart, CodeSignature.StartOffset + CodeSignature.Size, + MS_INVALIDATE); +#endif } void MachOWriter::writeDataInCodeData() { diff --git a/llvm/tools/llvm-objcopy/MachO/Object.h b/llvm/tools/llvm-objcopy/MachO/Object.h --- a/llvm/tools/llvm-objcopy/MachO/Object.h +++ b/llvm/tools/llvm-objcopy/MachO/Object.h @@ -315,7 +315,6 @@ LinkData DataInCode; LinkData LinkerOptimizationHint; LinkData FunctionStarts; - LinkData CodeSignature; Optional SwiftVersion; @@ -333,6 +332,9 @@ Optional LinkerOptimizationHintCommandIndex; /// The index LC_FUNCTION_STARTS load comamnd if present. Optional FunctionStartsCommandIndex; + /// The index of the LC_SEGMENT or LC_SEGMENT_64 load command + /// corresponding to the __TEXT segment. + Optional TextSegmentCommandIndex; BumpPtrAllocator Alloc; StringSaver NewSectionsContents; diff --git a/llvm/tools/llvm-objcopy/MachO/Object.cpp b/llvm/tools/llvm-objcopy/MachO/Object.cpp --- a/llvm/tools/llvm-objcopy/MachO/Object.cpp +++ b/llvm/tools/llvm-objcopy/MachO/Object.cpp @@ -29,10 +29,24 @@ } void Object::updateLoadCommandIndexes() { + static constexpr char TextSegmentName[] = "__TEXT"; // Update indices of special load commands for (size_t Index = 0, Size = LoadCommands.size(); Index < Size; ++Index) { LoadCommand &LC = LoadCommands[Index]; switch (LC.MachOLoadCommand.load_command_data.cmd) { + case MachO::LC_CODE_SIGNATURE: + CodeSignatureCommandIndex = Index; + break; + case MachO::LC_SEGMENT: + if (StringRef(LC.MachOLoadCommand.segment_command_data.segname) == + TextSegmentName) + TextSegmentCommandIndex = Index; + break; + case MachO::LC_SEGMENT_64: + if (StringRef(LC.MachOLoadCommand.segment_command_64_data.segname) == + TextSegmentName) + TextSegmentCommandIndex = Index; + break; case MachO::LC_SYMTAB: SymTabCommandIndex = Index; break;