Files
UniXNU/scripts/check-iogpu-checkpoints.py

123 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""Qualify actual original IOGPUFamily loading and native metaclass ABI evidence."""
import hashlib
import json
from pathlib import Path
import re
import struct
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'kits/XnuPrelink'))
from native_prelink import backed_offset, macho
def require(value, message):
if not value:
raise SystemExit(message)
source_hash = '257c6f553e0a30a577e8a53623fc742b46f337f55b912b5ff466c19b4c049828'
positive_hash = 'a66a3c60c82330cba7915a6dfd028cf6b2332e3919021f1fce9c0d0526498f99'
negative_hash = '043ec08b8ab0761412409fd2975e5a7d94ce8153ca1cd149bfe19f3053a72eb6'
directory = ROOT / 'build/xnu-prelink'
plans = {}
for stem, expected in (('native-owner-iogpu-candidate', positive_hash),
('native-owner-iogpu-missing-candidate', negative_hash)):
path = directory / (stem + '.macho')
plan = json.loads(path.with_suffix('.json').read_text())
require(plan['native_sha256'] == source_hash and plan['candidate_sha256'] == expected ==
hashlib.sha256(path.read_bytes()).hexdigest() and plan['original_sha256'] ==
'f18b4df865405f2a287b84977d7aca745e056d656d9fc9df1662970ce2969de7' and
not plan['images_modified'] and not plan['source_inputs_modified'], 'Exact unchanged target and source/candidate identities')
plans[expected] = plan
require(len(plans[positive_hash]['components']) == 29 and len(plans[negative_hash]['components']) == 28 and
set(plans[positive_hash]['components']) - set(plans[negative_hash]['components']) == {'com.apple.iokit.IOGPUFamily'},
'Actual component present/absent closure')
image = (directory / 'native-owner-iogpu-candidate.macho').read_bytes()
segments = macho(image)['segments']
def read(va, size):
at = backed_offset(segments, va, size, len(image))
return image[at:at + size]
def report(label, expected=positive_hash):
d = json.loads((ROOT / ('build/darwinboot-owner-hvf-iogpu-' + label) / 'gdb-stop.json').read_text())
r = {name: int(value, 16) for name, value in d['system_registers'].items()}
require(d['kernel_sha256'] == expected and d['qemu_accelerator'] == 'hvf' and not d['bounded_timeout_interrupted'] and
d['native_4k_mmu_observed'] and 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'] and
d['original_crypto_initialized_observed'] and d['original_crypto_table_owner_observed'] and
not d['sheng_hardware_tested'], 'Actual bounded native-4K XNU and original crypto')
return d
loaded, missing = report('load'), report('missing', negative_hash)
require(loaded['stop_symbol'] == missing['stop_symbol'] == '_native_iogpu_probe_checkpoint' and
loaded['native_iogpu_load_status'] == 0 and missing['native_iogpu_load_status'] == -603946998,
'Real original component load and genuine component-absent refusal')
component = loaded['native_iogpu_component']
require(component['actual_start_observed'] and component['original_entry_bytes_match'] and
component['start'] == '0xfffffff004bde714' and component['original_start'] == '0xfffffff005bde714' and
any(e['pc'] == component['start'] and e['symbol'] == 'com.apple.iokit.IOGPUFamily:kmod_start' for e in loaded['trace_events']),
'Actual original IOGPU kmod start with unchanged entry instructions')
symbols = subprocess.check_output(['xcrun', 'nm', '-U', directory / 'native-owner-iogpu-candidate.macho'], text=True)
def symbol(name):
match = re.search(r'^([0-9a-f]+)\s+\S\s+' + re.escape(name) + '$', symbols, re.M)
require(match, 'Qualified actual native symbol: ' + name)
return int(match[1], 16)
def owner(va):
return any(e['module'] == 'com.apple.iokit.IOGPUFamily' and e['segment'] == '__DATA' and
e['va'] <= va < e['va'] + e['size'] for e in plans[positive_hash]['extents'])
records = {}
for label, name, size, parent in (
('gpu', 'IOGPU', 0x2e0, 0xfffffff006aeb688),
('device', 'IOGPUDevice', 0x100, 0xfffffff006aeb278),
('userclient', 'IOGPUDeviceUserClient', 0x130, symbol('__ZN12IOUserClient10gMetaClassE'))):
d = report(label + '-class')
args = [int(value, 16) for value in d['function_entry_arguments']]
raw = bytes.fromhex(d['entry_regions']['0:0:80']['bytes'])
require(d['function_return_stack_matched'] and d['entry_kernel_bytes_match']['bytes'] == (name + '\0').encode().hex() and
args[2:4] == [parent, size] and struct.unpack_from('<Q', raw, 16)[0] == parent and
struct.unpack_from('<Q', raw, 24)[0] == args[1] and struct.unpack_from('<I', raw, 32)[0] == size and owner(args[0]),
'Actual metaclass constructor, original class storage/name, parent and qualified fields')
records[name] = dict(metaclass=hex(args[0]), parent=hex(parent), instance_bytes=size,
constructor_observed=True, runtime_instance_created=False)
if label != 'userclient':
# These original parents are constructed within the same module before
# postModLoad converts class-name C strings to registered OSSymbols.
parent_name, parent_size, grandparent = (
('IOAcceleratorES', 0x88, symbol('__ZN9IOService10gMetaClassE')) if label == 'gpu' else
('IOGPUObject', 0x10, symbol('__ZN8OSObject10gMetaClassE')))
raw = bytes.fromhex(d['entry_regions']['2:0:80']['bytes'])
name_pointer, = struct.unpack_from('<Q', raw, 24)
require(owner(parent) and read(name_pointer, len(parent_name) + 1) == (parent_name + '\0').encode() and
struct.unpack_from('<Q', raw, 16)[0] == grandparent and struct.unpack_from('<I', raw, 32)[0] == parent_size,
'Actual initialized parent metadata, not an invented inheritance relationship')
records[parent_name] = dict(metaclass=hex(parent), parent=hex(grandparent), instance_bytes=parent_size,
constructor_observed=False, initialized_parent_observed=True, runtime_instance_created=False)
for label, status in (('launchd-main', 0), ('default-off', -1)):
d = report(label)
require(d['native_iogpu_load_status'] == status and d['original_amfi_table_owner_observed'] and
d['original_launchd_main_executed'] and d['followed_main_entry']['first_instruction_completed'] and
int(d['pstate'], 16) & 31 == 0 and
d['direct_user_commpage_shifts'] == {'fffff4025:1': 12, 'fffff4037:1': 12} and
d['owner_root_binding_audit']['boot_root_accepted'] and
d['owner_root_binding_audit']['_native_owner_root_apple_autherr'] == 80 and
d['actual_signature_failures'] == dict(_cs_validate_page_bad_hash=0, _cs_validate_page_no_hash=0) and
d['native_virtio_integrity']['_native_virtio_integrity_failures'] == 0,
'Original EL0 launchd, native user 4K, owner-root binding and genuine signatures with enabled/default-off load')
(ROOT / 'build/gpu-analysis/iogpu-class-abi.json').write_text(json.dumps(dict(classes=records,
gpu_instance_created=False, metal_device_created=False, adreno_execution_qualified=False), indent=2) + '\n')
print('PASS: actual original IOGPUFamily start/load and missing-component refusal; three constructors and two parent metadata records')
print('PASS: original launchd with load enabled/default-off; no GPU instance, Metal device or Adreno execution claim')