Files
UniXNU/scripts/seal-source-crypto-kernel.py
T
rpone ab11a8cb68 feat(scripts): iPadOS 27 kernel, driver, GPU addon and seed build steps
Source-driver and source-storage builds, the arm64e PAC rewrite for the Rust bridge, the IOAccelerator base ABI of 24A446, the bridge with mimalloc and link-time optimisation, the plugin with the prime list, addon options for a prime list, a pipeline pack and cache export, wallpaper overlay and seed changes, and payload, kernel and userdata steps for the new profile.
2026-10-28 09:20:55 +08:00

142 lines
7.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Fill the source provider's normal POST HMAC field after final kernel link.
Implements the source fipspost_get_hmac/hmacfiletool build contract: HMAC-SHA256
with a one-byte zero key over the first __TEXT[ _EXEC ]/__text section.
No selftest or integrity check is disabled. Only locally compiled input is used.
static_if_init rewrites __text before the POST runs. It stores absolute instructions (b or nop), so the
sites of keys that boot flips are written here in their final state and the boot rewrite leaves the
hashed bytes unchanged. --kernel-args names the boot arguments the kernel starts with.
"""
import argparse
import hashlib
import hmac
import json
from pathlib import Path
import struct
from kernel_target import ARCH, SUBTYPE
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('kernel', type=Path)
parser.add_argument('--kernel-args', default='', help='boot arguments, to refuse ones that flip more static_if keys')
args = parser.parse_args()
# kern_feature_override_apply flips assert and sched debug keys only for these arguments.
for argument in args.kernel_args.split():
if argument.lstrip('-').split('=', 1)[0] in ('validation_disables', 'serverperfmode', 'lcks'):
raise SystemExit(f'{argument} flips static_if keys at boot that this seal does not model')
data = bytearray(args.kernel.read_bytes())
magic, cpu, subtype, kind = struct.unpack_from('<4I', data)
header = 0
if (magic, cpu, subtype & 0xff) == (0xfeedfacf, 0x100000c, SUBTYPE) and kind == 12:
# A boot kernel collection: seal its com.apple.kernel entry. Section offsets are collection file offsets.
# The emitter moves the native text, so the HMAC is computed here, after emission. The static_if sites
# were prepatched when the native kernel was sealed before emission.
offset = 32
for _ in range(struct.unpack_from('<I', data, 16)[0]):
cmd, length = struct.unpack_from('<II', data, offset)
if cmd == 0x80000035:
name = struct.unpack_from('<I', data, offset + 24)[0]
if data[offset + name:data.index(b'\0', offset + name)] == b'com.apple.kernel':
header = struct.unpack_from('<Q', data, offset + 16)[0]
offset += length
if not header:
raise SystemExit('Kernel collection has no com.apple.kernel entry')
magic, cpu, subtype, kind = struct.unpack_from('<4I', data, header)
if (magic, cpu, subtype & 0xff, kind) != (0xfeedfacf, 0x100000c, SUBTYPE, 2):
raise SystemExit(f'Expected a source-built {ARCH} MH_EXECUTE kernel or a collection of one')
if b'_source_corecrypto14_bootstrap\0' not in data:
raise SystemExit('Input does not contain the explicit source-provider bootstrap')
offset = header + 32
code = None
integrity = None
segments = []
sections = {}
symtab = None
chained = False
for _ in range(struct.unpack_from('<I', data, header + 16)[0]):
cmd, length = struct.unpack_from('<II', data, offset)
if cmd == 0x80000034:
chained = True
if cmd == 0x2:
symtab = struct.unpack_from('<4I', data, offset + 8)
if cmd == 0x19:
name, va, _, fileoff, _ = struct.unpack_from('<16s4Q', data, offset + 8)
name = name.rstrip(b'\0')
segments.append((name, va, fileoff))
for index in range(struct.unpack_from('<I', data, offset + 64)[0]):
section = offset + 72 + index * 80
section_name, segment_name, address, size, position = struct.unpack_from('<16s16sQQI', data, section)
sections[(segment_name.rstrip(b'\0'), section_name.rstrip(b'\0'))] = (address, size, position)
if section_name.rstrip(b'\0') == b'__text' and segment_name.rstrip(b'\0') in (b'__TEXT', b'__TEXT_EXEC') and code is None:
code = (position, size, address)
if section_name.rstrip(b'\0') == b'__fips_hmacs':
if integrity is not None or size != 32:
raise SystemExit('Expected exactly one 32-byte POST HMAC field')
integrity = position
offset += length
if code is None or integrity is None:
raise SystemExit('Missing real provider code or POST HMAC field')
base = next(va for name, va, _ in segments if name == b'__TEXT')
if code[2] - base != code[0] - header:
raise SystemExit('Runtime header-relative text mapping differs from the POST file-offset contract')
if code[0] <= integrity < code[0] + code[1]:
raise SystemExit('HMAC field must be outside the hashed instructions')
def symbols(names):
found = {}
if symtab:
symoff, nsyms, stroff, _ = symtab
for index in range(nsyms):
strx, kind, _, _, value = struct.unpack_from('<IBBHQ', data, symoff + index * 16)
name = bytes(data[stroff + strx:data.index(b'\0', stroff + strx)])
if name in names and kind & 0x0e == 0x0e:
found[name] = value
return found
# osfmk/tests/kernel_tests.c static_if_tests_setup: unconditional, one disable and one enable.
flips = [b'_static_if_test_key_true_to_false_jump_key', b'_static_if_test_key_false_to_true_jump_key']
present = symbols({b'_static_if_tests_setup', *flips})
patched = 0
if b'_static_if_tests_setup' in present and not header:
# Threaded rebases keep the key's low address bits; chained kernel-cache pointers keep a 30-bit offset.
target_mask = 0x3fffffff if chained else 0x7ffffffffff
keys = {(present[name] - (base if chained else 0)) & target_mask for name in flips if name in present}
if len(keys) != len(flips):
raise SystemExit('static_if test initializer present without its keys')
entries = sections.get((b'__DATA_CONST', b'__static_if')) or sections.get((b'__BOOTDATA', b'__static_if'))
if entries is None:
raise SystemExit('static_if test initializer present without the entry section')
address, size, position = entries
for entry in range(0, size, 16):
delta, target, link = struct.unpack_from('<iiQ', data, position + entry)
# The link is a rebase of key + flags; bit 0 says the site was emitted as a branch.
if (link & target_mask & ~3) not in keys:
continue
point = address + entry + delta
site = code[0] + point - code[2]
if not code[2] <= point < code[2] + code[1]:
continue
current = struct.unpack_from('<I', data, site)[0]
branch = 0x14000000 | ((target >> 2) & 0x03ffffff)
expected, final = (branch, 0xd503201f) if link & 1 else (0xd503201f, branch)
if current == final:
# Sealed before: the kernel build seals, and packaging seals again with the boot arguments.
patched += 1
continue
if current != expected:
raise SystemExit(f'static_if site {point:#x} holds {current:#010x}, not the emitted instruction')
struct.pack_into('<I', data, site, final)
patched += 1
mac = hmac.digest(b'\0', data[code[0]:code[0] + code[1]], 'sha256')
data[integrity:integrity + 32] = mac
args.kernel.write_bytes(data)
report = {'kernel_sha256': hashlib.sha256(data).hexdigest(), 'code_file_offset': hex(code[0]),
'code_bytes': code[1], 'hmac_file_offset': hex(integrity), 'hmac_sha256': mac.hex(),
'static_if_sites_prepatched': patched, 'runtime_selftests_executed': False}
args.kernel.with_suffix(args.kernel.suffix + '.crypto-hmac.json').write_text(json.dumps(report, indent=2) + '\n')
print('Source kernel POST integrity field populated; runtime selftests are still required')