[lld] aaf00f3 - Add MachO signature verification test

Daniel Rodríguez Troitiño via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 16 17:56:47 PDT 2021


Author: Nuri Amari
Date: 2021-09-16T17:55:32-07:00
New Revision: aaf00f3f19c1be69ce10d53236f824c2580c2139

URL: https://github.com/llvm/llvm-project/commit/aaf00f3f19c1be69ce10d53236f824c2580c2139
DIFF: https://github.com/llvm/llvm-project/commit/aaf00f3f19c1be69ce10d53236f824c2580c2139.diff

LOG: Add MachO signature verification test

Add a test to ensure that MachO files including
a LC_CODE_SIGNATURE load command produced by lld
are signed correctly.

Reviewed By: #lld-macho, int3

Differential Revision: https://reviews.llvm.org/D109840

Added: 
    lld/test/MachO/Inputs/code-signature-check.py
    lld/test/MachO/adhoc-codesign-hash.s

Modified: 
    

Removed: 
    


################################################################################
diff  --git a/lld/test/MachO/Inputs/code-signature-check.py b/lld/test/MachO/Inputs/code-signature-check.py
new file mode 100644
index 0000000000000..75c01a2174c7e
--- /dev/null
+++ b/lld/test/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/lld/test/MachO/adhoc-codesign-hash.s b/lld/test/MachO/adhoc-codesign-hash.s
new file mode 100644
index 0000000000000..937f29c060590
--- /dev/null
+++ b/lld/test/MachO/adhoc-codesign-hash.s
@@ -0,0 +1,23 @@
+# REQUIRES: x86, aarch64
+# RUN: rm -rf %t; mkdir -p %t
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/empty-arm64-macos.o %s
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-iossimulator -o %t/empty-arm64-iossimulator.o %s
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/empty-x86_64-macos.o %s
+
+# RUN: %lld -arch arm64 -dylib -adhoc_codesign -o %t/empty-arm64-macos.dylib %t/empty-arm64-macos.o
+# RUN: %lld -arch arm64 -dylib -adhoc_codesign -o %t/empty-arm64-iossimulator.dylib %t/empty-arm64-iossimulator.o
+# RUN: %lld -arch x86_64 -dylib -adhoc_codesign -o %t/empty-x86_64-macos.dylib %t/empty-x86_64-macos.o
+
+# RUN: obj2yaml %t/empty-arm64-macos.dylib | FileCheck %s -D#DATA_OFFSET=16400 -D#DATA_SIZE=304
+# RUN: obj2yaml %t/empty-arm64-iossimulator.dylib | FileCheck %s -D#DATA_OFFSET=16400 -D#DATA_SIZE=304
+# RUN: obj2yaml %t/empty-x86_64-macos.dylib | FileCheck %s -D#DATA_OFFSET=4112 -D#DATA_SIZE=208
+
+# CHECK:    - cmd:             LC_CODE_SIGNATURE
+# CHECK-NEXT: cmdsize:         16
+# CHECK-NEXT: dataoff:         [[#DATA_OFFSET]]
+# CHECK-NEXT: datasize:        [[#DATA_SIZE]]
+
+# RUN: %python %p/Inputs/code-signature-check.py %t/empty-arm64-macos.dylib 16400 304 0 16400
+# RUN: %python %p/Inputs/code-signature-check.py %t/empty-arm64-iossimulator.dylib 16400 304 0 16400
+# RUN: %python %p/Inputs/code-signature-check.py %t/empty-x86_64-macos.dylib 4112 208 0 4112


        


More information about the llvm-commits mailing list