114 lines
7.0 KiB
Python
114 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify unchanged target cache data through real native-4K file APIs in QEMU."""
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import struct
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
build = ROOT / 'build'
|
|
fixture = build / 'native-cache-file-fixture'
|
|
metadata = json.loads((fixture / 'file-probe.json').read_text())
|
|
candidate = json.loads((build / 'xnu-prelink/native-readonly-file-candidate.json').read_text())
|
|
|
|
|
|
def require(value, message):
|
|
if not value:
|
|
raise SystemExit('FAIL: ' + message)
|
|
|
|
|
|
image = (fixture / 'root.img').read_bytes()
|
|
magic, version, count, own_at, own_size, file_at, file_size = struct.unpack_from('<8sII4Q', image)
|
|
require((magic, version, count) == (b'N4KROOT\0', 1, 2), 'offline input format')
|
|
own = image[own_at:own_at + own_size]
|
|
target = image[file_at:file_at + file_size]
|
|
require(own == (fixture / 'launchd.macho').read_bytes(), 'unchanged own process')
|
|
require(hashlib.sha256(target).hexdigest() == metadata['original_file_sha256'] ==
|
|
'3d923c404fe519184a99042b60a4cb3d37247dcb8b070242ed3e670d4d523d04', 'exact supplied target file')
|
|
require(hashlib.sha256(image).hexdigest() == metadata['root_sha256'], 'input container identity')
|
|
trust = json.loads((fixture / 'offline-file-trust.json').read_text())
|
|
own_trust = json.loads((fixture / 'trustcache.json').read_text())
|
|
require(trust['cdhashes'] == sorted((metadata['original_cdhash'], own_trust['cdhash'])) and
|
|
not trust['apple_trust_chain_verified'] and not trust['original_apple_trust_cache_modified'],
|
|
'explicit offline trust scope')
|
|
|
|
|
|
def report(name, direct=False, own_sha=metadata['own_process_sha256']):
|
|
d = json.loads((build / name / 'gdb-stop.json').read_text())
|
|
require(d['kernel_sha256'] == candidate['candidate_sha256'], 'same exact source/component candidate')
|
|
require(not d['bounded_timeout_interrupted'], name + ': no timeout inference')
|
|
r = {k: int(v, 16) for k, v in d['system_registers'].items()}
|
|
require(r['TCR_EL1'] >> 14 & 3 == 0 and r['TCR_EL1'] >> 30 & 3 == 2 and
|
|
r['SCTLR_EL1'] & 1 and r['TTBR0_EL1'] and r['TTBR1_EL1'], 'actual native 4K MMU')
|
|
if not direct:
|
|
require(d['original_amfi_table_owner_observed'] and d['original_crypto_initialized_observed'],
|
|
'original security providers')
|
|
require(d['native_user_el0_observed'] and d['native_user_4k_abi_observed'] and
|
|
not d['ipados_userspace_boot_demonstrated'], 'own process ABI and scope')
|
|
require(d['native_user_image_sha256'] == own_sha, 'own process identity')
|
|
return d
|
|
|
|
|
|
refusal = report('darwinboot-original-cache-readonly-file')
|
|
require(refusal['offline_root_sha256'] == metadata['root_sha256'] and
|
|
refusal['trust_cache_bundle_sha256'] == own_trust['bundle_sha256'], 'own-only trust control')
|
|
f = refusal['native_file_probe']
|
|
require(f['open_result'] == 0 and f['open_nzcv'] >> 29 & 1 == 0 and f['stage'] == 1 and
|
|
f['fcntl_result'] == 1 and f['fcntl_nzcv'] >> 29 & 1 == 1, 'real registration EPERM control')
|
|
require(refusal['native_signature_hash_cache']['_cs_native4k_ready_blobs'] == 0, 'no untrusted publication')
|
|
|
|
positive = report('darwinboot-original-cache-owner-trust')
|
|
require(positive['offline_root_sha256'] == metadata['root_sha256'] and
|
|
positive['trust_cache_bundle_sha256'] == trust['bundle_sha256'], 'explicit file-test trust')
|
|
f = positive['native_file_probe']
|
|
require(f['stage'] == 4 and f['open_result'] == 0 and f['fcntl_result'] == 0 and f['mmap_result'] != 0,
|
|
'genuine open/signature registration/mmap completed')
|
|
require(not any(f[n] >> 29 & 1 for n in ('open_nzcv', 'fcntl_nzcv', 'mmap_nzcv')), 'no file syscall errors')
|
|
require(f['signature_end'] == metadata['code_limit'] and f['native_pages_read'] == 8 and
|
|
f['page_word_xor'] == metadata['expected_page_word_xor'], 'actual original page reads')
|
|
c = positive['native_signature_hash_cache']
|
|
require((c['_cs_native4k_full_blocks'], c['_cs_native4k_page_checks'], c['_cs_native4k_ready_blobs']) ==
|
|
(2, 8, 1), 'two original signing blocks and eight actual native page checks')
|
|
require(set(positive['code_signing_hash_counters'].values()) == {0}, 'no missing/bad page hashes')
|
|
|
|
inspection = report('darwinboot-original-cache-guest-signature', direct=True)
|
|
require(int(inspection['x0'], 16) == 0 and inspection['skipped_target_hits'] == 1, 'actual second blob authentication')
|
|
captured = inspection['entry_indirect_memory']
|
|
cd = bytes.fromhex(captured['1:80:200']['bytes'])
|
|
length = struct.unpack_from('>I', cd, 4)[0]
|
|
require(length <= len(cd) and cd[39] == 14 and hashlib.sha256(cd[:length]).hexdigest()[:40] ==
|
|
metadata['original_cdhash'], 'unchanged actual guest target CodeDirectory/CDHash')
|
|
cache = captured['1:b8:1000']
|
|
digests = bytes.fromhex(cache['bytes'])
|
|
expected = b''.join(hashlib.sha256(target[o:o + 4096]).digest() for o in range(0, metadata['code_limit'], 4096))
|
|
require(digests[:len(expected)] == expected and not any(digests[len(expected):]), 'actual target derived hashes/padding')
|
|
t = cache['translation']
|
|
require(t['leaf_level'] == 3 and t['leaf_bytes'] == 4096 and t['ap'] == 2 and t['pxn'] and t['uxn'],
|
|
'actual target digest mapping read-only/NX/native4K')
|
|
|
|
negative = report('darwinboot-original-cache-bad-native-page')
|
|
bad = bytearray(image)
|
|
bad[file_at + 4096] ^= 1
|
|
require(hashlib.sha256(bad).hexdigest() == negative['offline_root_sha256'] and
|
|
negative['trust_cache_bundle_sha256'] == trust['bundle_sha256'], 'one-bit corruption, unchanged trust')
|
|
f = negative['native_file_probe']
|
|
require(f['stage'] == 1 and f['fcntl_result'] == 85 and f['fcntl_nzcv'] >> 29 & 1 == 1 and
|
|
f['native_pages_read'] == 0, 'real EBADEXEC before mapping corrupted target file')
|
|
require(negative['code_signing_hash_counters']['_cs_validate_page_bad_hash'] == 1 and
|
|
negative['native_signature_hash_cache']['_cs_native4k_ready_blobs'] == 0, 'original target hash enforced')
|
|
|
|
legacy = report('darwinboot-two-file-legacy-root-regression',
|
|
own_sha='8e6cb80348e018c780e3a8b71bed6015e80e932b23be378bfcdd67846277b9a0')
|
|
malformed = report('darwinboot-two-file-malformed-root', direct=True)
|
|
require(int(malformed['x0'], 16) == 22 and malformed.get('finished_function_entry'),
|
|
'overlapping malformed root returns EINVAL through actual mount function cleanup')
|
|
|
|
result = dict(candidate_sha256=candidate['candidate_sha256'], original_file_sha256=metadata['original_file_sha256'],
|
|
original_cdhash=metadata['original_cdhash'], original_file_bytes_modified=False,
|
|
original_hash_blocks_authenticated=2, native_4k_pages_checked=8, checkpoints=6,
|
|
explicit_offline_file_trust=True, apple_boot_trust_chain_verified=False,
|
|
target_component_code_executed=False, target_ipados_booted=False, sheng_hardware_tested=False)
|
|
(build / 'original-cache-file-checkpoints.json').write_text(json.dumps(result, indent=2) + '\n')
|
|
print('PASS: unchanged original cache file; real registration, native-4K mappings/faults and corruption refusal')
|
|
print('Explicit offline file trust; no original target code execution, iPadOS boot or hardware claim')
|