Files
Snapintosh/tools/augment-trust-cache.py
T
Acelogic 1510d8c6ce Snapintosh public release
An EL2 monitor/hypervisor and toolchain that boots macOS 27 on the ASUS A16
(Snapdragon X2 Elite), with an Adreno Metal plug-in and IOGPU kext, and
air2ir3, an AIR to Mesa ir3 shader compiler.

MIT, with GPL-2.0 components listed in LICENSE and THIRD_PARTY_NOTICES.md.
No Apple, Qualcomm, ASUS or Microsoft binaries are included.
2026-10-21 21:26:36 -04:00

93 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Append our CDHashes to the pinned Apple boot trust cache, offline.
Replaces the adreno prototype `injection/augment_trustcache.py`, whose rows
ended 00 02 02 00 (flags u16 first) where Apple's end 02 00 cc 00 (hash type,
flags, u16 category) and whose collision check was dead code.
# record a signed bundle's CDHash (codesign reads the bundle)
tools/augment-trust-cache.py --additions build/gpu-trust.json \\
--add-bundle build/AdrenoMetal.bundle --category 2
# check the whole cache against the profile and the loader reservation
tools/augment-trust-cache.py --additions build/gpu-trust.json \\
--output build/gpu-trust.trustcache
The loader never reads --output: it rebuilds the same bytes from the pinned
base and the additions file (xnuboot.trust_cache_spec), and audits verify the
resident cache the same way. The file is for inspection and diffing.
"""
import argparse
import hashlib
import json
from pathlib import Path
import re
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'probes'))
import xnuboot as xb
from trustcache import AugmentedTrustCacheSpec, addition_entries, parse_v2, MAX_PAYLOAD
def bundle_cdhash(path):
"""The code directory hash codesign reports for a signed bundle."""
result = subprocess.run(['codesign', '-d', '--verbose=4', str(path)],
capture_output=True, text=True)
hashes = re.findall(r'^CDHash=([0-9a-f]{40})$', result.stderr, re.M)
types = re.findall(r'^Hash type=(\S+)', result.stderr, re.M)
if result.returncode or len(hashes) != 1:
raise ValueError(f'{path}: no single CDHash from codesign:\n{result.stderr}')
if types and types[0] != 'sha256':
raise ValueError(f'{path}: CDHash type {types[0]}, expected sha256')
return hashes[0]
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--guest-profile', default='j700ap-26A428-bootfb-auxkc-gpu')
ap.add_argument('--additions', type=Path, required=True,
help='JSON rows {"cdhash", "category", "name"}; created by --add-*')
ap.add_argument('--add-bundle', type=Path, action='append', default=[],
help='append the CDHash codesign reports for this signed bundle')
ap.add_argument('--add-cdhash', action='append', default=[], metavar='HEX',
help='append a 40-hex-digit (or 64) CDHash directly')
ap.add_argument('--category', type=int, default=2,
help='trust-cache category for appended rows (Apple paravirt plug-in: 2)')
ap.add_argument('--output', type=Path, help='write the augmented cache for inspection')
args = ap.parse_args()
xb.configure_guest(args.guest_profile)
spec = xb.GUEST_PROFILE.trust_cache
if not isinstance(spec, AugmentedTrustCacheSpec):
raise SystemExit(f'profile {args.guest_profile} pins the unmodified Apple cache')
rows = json.loads(args.additions.read_text()) if args.additions.exists() else []
added = [(bundle_cdhash(p), p.name) for p in args.add_bundle]
added += [(h.lower(), 'cdhash') for h in args.add_cdhash]
for digest, name in added:
if not re.fullmatch(r'[0-9a-f]{40}([0-9a-f]{24})?', digest):
raise SystemExit(f'invalid CDHash {digest!r}')
rows.append(dict(cdhash=digest[:40], category=args.category, name=name))
# Everything is checked before the additions file changes.
bound = spec.bind(addition_entries(rows))
payload = bound.load(xb.FIRMWARE_DIR)
bound.verify(payload)
if len(payload) > MAX_PAYLOAD or not xb.static_trust_fits(len(payload)):
raise SystemExit(f'{len(payload)} bytes exceed the monitor/loader trust reservation')
if added:
args.additions.write_text(json.dumps(rows, indent=2) + '\n')
entries = parse_v2(payload)['entries']
positions = {row[:20].hex(): n for n, row in enumerate(entries)}
if args.output:
args.output.write_bytes(payload)
print(json.dumps(dict(profile=args.guest_profile, base_entries=spec.base.entries,
appended=len(bound.appended), entries=bound.entries, bytes=len(payload),
sha256=hashlib.sha256(payload).hexdigest(),
rows=[dict(r, index=positions[r['cdhash'][:40]], trailer=entries[positions[r['cdhash'][:40]]][20:].hex())
for r in rows],
output=str(args.output) if args.output else None), indent=2))
if __name__ == '__main__':
main()