140 lines
16 KiB
Python
140 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""Clone our mutable APFS Data fixture and stage only own code plus exact AIR input."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import plistlib
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--broker-processes',action='store_true');p.add_argument('--broker-policy',choices=['full','without-container','without-client','parent-client','unrelated-client'],default='full');p.add_argument('--legacy-swap',action='store_true');p.add_argument('--legacy-swap-wait',action='store_true');p.add_argument('--framebuffer-geometry',action='store_true');p.add_argument('--framebuffer-power',action='store_true');p.add_argument('--framebuffer-protocol',action='store_true');p.add_argument('--empty-end',action='store_true');p.add_argument('--display-environment',action='store_true');p.add_argument('--fp-context',action='store_true');p.add_argument('--compose',action='store_true');p.add_argument('--resources',action='store_true');p.add_argument('--present',action='store_true');p.add_argument('--bgra',action='store_true');p.add_argument('--iosurface',action='store_true');p.add_argument('--offline-metal-api',action='store_true');p.add_argument('--offline-gpu-transport',action='store_true');p.add_argument('--metal-runtime',action='store_true');p.add_argument('--library-input',action='store_true');p.add_argument('--fragment-inputs',action='store_true');p.add_argument('--library-tests',action='store_true');p.add_argument('--vpc-tests',action='store_true');p.add_argument('--case',choices=['positive','bad-input'],default='positive');p.add_argument('--async-commands',action='store_true');p.add_argument('--fences',action='store_true');p.add_argument('--sandbox-extension',action='store_true');p.add_argument('--metal-layer',action='store_true');p.add_argument('--renderbox-present',type=int,choices=[1,2]);p.add_argument('--depth-stencil',action='store_true');p.add_argument('--private-texture',action='store_true');p.add_argument('--renderbox-draw',action='store_true');p.add_argument('--renderbox-device',action='store_true');p.add_argument('--plugin-control',choices=['full','without-mount-permission','tampered-code'],default='full');p.add_argument('--plugin-addon',action='store_true');p.add_argument('--plugin-bundle',action='store_true');p.add_argument('--shared-surfaces',action='store_true',help='Pass actual IOSurface Mach rights between two GPU processes')
|
|
p.add_argument('--shared-events',action='store_true',help='Real native shared-event Mach capabilities between two processes')
|
|
p.add_argument('--shared-notifications',action='store_true',help='Original listener queue delivery and bounded context cancellation')
|
|
p.add_argument('--shared-coding',action='store_true',help='Original NSXPC shared-handle secure coding, separate from cross-process authority')
|
|
p.add_argument('--shared-cross-process',action='store_true',help='Actual original NSXPC endpoint and handle coding between two native GPU workers')
|
|
p.add_argument('--vertex-inputs',action='store_true',help='Actual original QuartzCore vertex layouts and native GPU buffers')
|
|
args=p.parse_args()
|
|
if args.vertex_inputs and any((args.shared_events, args.shared_notifications, args.shared_coding, args.shared_cross_process, args.async_commands, args.fences, args.metal_layer, args.renderbox_present, args.depth_stencil, args.private_texture, args.renderbox_draw, args.renderbox_device, args.plugin_addon, args.plugin_bundle, args.shared_surfaces, args.sandbox_extension, args.legacy_swap, args.legacy_swap_wait, args.compose, args.resources, args.present)):p.error("Select the independent vertex input instrument")
|
|
if args.vertex_inputs:args.broker_processes=True
|
|
if args.shared_notifications and (args.shared_coding or args.shared_cross_process):p.error('Select notifications or secure coding as independent instruments')
|
|
if args.shared_cross_process:args.shared_coding=True
|
|
if args.shared_coding:args.shared_events=True
|
|
if args.shared_notifications:args.shared_events=True
|
|
if args.shared_events:args.broker_processes=True
|
|
if args.async_commands:args.fences=True
|
|
if args.fences:args.metal_layer=True
|
|
if args.sandbox_extension:args.broker_processes=True
|
|
if args.metal_layer:
|
|
if args.renderbox_present or args.depth_stencil or args.private_texture or args.renderbox_draw:p.error('Select one independent layer/renderer instrument')
|
|
args.renderbox_device=True
|
|
if args.renderbox_present:
|
|
if args.depth_stencil or args.private_texture or args.renderbox_draw:p.error('Select one independent renderer/presentation instrument')
|
|
args.renderbox_device=True
|
|
if args.depth_stencil:
|
|
if args.private_texture or args.renderbox_draw:p.error('Select one independent resource or renderer instrument')
|
|
args.renderbox_device=True
|
|
if args.private_texture:
|
|
if args.renderbox_draw:p.error('Select one independent resource or original renderer instrument')
|
|
args.renderbox_device=True
|
|
if args.renderbox_draw:args.renderbox_device=True
|
|
if args.renderbox_device:
|
|
if args.plugin_control!='full':p.error('RenderBox device observation is a separate instrument')
|
|
args.plugin_addon=True
|
|
if args.plugin_control!='full':args.plugin_addon=True
|
|
if args.plugin_addon:args.plugin_bundle=True
|
|
if args.plugin_bundle:args.shared_surfaces=True
|
|
if args.shared_surfaces:args.broker_processes=True
|
|
if args.shared_surfaces and args.broker_policy!='full':p.error('Sharing instrument requires both existing scoped diagnostic permissions')
|
|
if args.broker_policy!='full' and not args.broker_processes:p.error('Permission controls require the GPU process instrument')
|
|
if args.broker_processes:
|
|
if not args.offline_metal_api:p.error('GPU process instrument requires --offline-metal-api')
|
|
if args.legacy_swap or args.legacy_swap_wait or args.compose or args.resources or args.present:p.error('Select one GPU runtime instrument')
|
|
args.bgra=True
|
|
if args.legacy_swap_wait:args.legacy_swap=True
|
|
if args.empty_end and not args.framebuffer_protocol:p.error('--empty-end requires the framebuffer protocol instrument')
|
|
if args.legacy_swap and (args.compose or args.resources or args.present):p.error('Select one presentation instrument')
|
|
if args.legacy_swap:
|
|
if not args.offline_metal_api:p.error('--legacy-swap requires --offline-metal-api')
|
|
args.bgra=True
|
|
if args.compose and args.resources:p.error('Select composition or resource instrument')
|
|
if args.compose:args.present=True
|
|
if args.resources:args.present=True
|
|
if args.present:args.bgra=True
|
|
if args.bgra:args.iosurface=True
|
|
if args.iosurface and not args.offline_metal_api:raise SystemExit('IOSurface requires the Metal API instrument')
|
|
if args.fragment_inputs and not args.library_input:raise SystemExit('Fragment inputs require direct libraries')
|
|
if sum((args.framebuffer_geometry,args.framebuffer_power,args.framebuffer_protocol,args.display_environment,args.fp_context,args.library_input,args.library_tests,args.metal_runtime,args.vpc_tests,args.offline_gpu_transport,args.offline_metal_api))>1:raise SystemExit('Select one independent instrument')
|
|
broker_kind='offline-metal-vertex-inputs' if args.vertex_inputs else 'offline-metal-shared-cross-process' if args.shared_cross_process else 'offline-metal-shared-coding' if args.shared_coding else 'offline-metal-shared-notifications' if args.shared_notifications else 'offline-metal-shared-events' if args.shared_events else 'offline-metal-async' if args.async_commands else 'offline-metal-fences' if args.fences else 'offline-sandbox-extension' if args.sandbox_extension else 'offline-metal-layer' if args.metal_layer else ('offline-renderbox-present-'+str(args.renderbox_present)) if args.renderbox_present else 'offline-metal-depth-stencil' if args.depth_stencil else 'offline-metal-private-texture' if args.private_texture else 'offline-renderbox-draw' if args.renderbox_draw else 'offline-renderbox-device' if args.renderbox_device else ('offline-metal-addon'+('' if args.plugin_control=='full' else '-'+args.plugin_control)) if args.plugin_addon else 'offline-metal-plugin' if args.plugin_bundle else 'offline-metal-shared-surfaces' if args.shared_surfaces else 'offline-metal-processes'
|
|
OUT=ROOT/(('build/'+broker_kind+('' if args.broker_policy=='full' else '-'+args.broker_policy)) if args.broker_processes else 'build/framebuffer-geometry-diagnostic' if args.framebuffer_geometry else 'build/framebuffer-power-diagnostic' if args.framebuffer_power else 'build/offline-legacy-swap-wait' if args.legacy_swap_wait else 'build/offline-legacy-swap' if args.legacy_swap else 'build/framebuffer-empty-protocol-diagnostic' if args.empty_end else 'build/framebuffer-protocol-diagnostic' if args.framebuffer_protocol else 'build/display-environment-diagnostic' if args.display_environment else 'build/native-fp-context' if args.fp_context else 'build/offline-gop-composition' if args.compose else 'build/offline-gop-resources' if args.resources else 'build/offline-gop-presentation' if args.present else 'build/offline-metal-bgra' if args.bgra else 'build/offline-metal-iosurface' if args.iosurface else 'build/offline-metal-api' if args.offline_metal_api else 'build/offline-gpu-transport' if args.offline_gpu_transport else 'build/adreno-vpc-native-tests' if args.vpc_tests else 'build/metal-runtime-diagnostic' if args.metal_runtime else 'build/air-library-native-tests' if args.library_tests else 'build/air-fragment-inputs-diagnostic' if args.fragment_inputs else 'build/air-library-diagnostic' if args.library_input else 'build/air-nir-diagnostic')
|
|
suffix='' if args.case=='positive' else '-bad-input'
|
|
def sha(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
|
source=ROOT/'build/owner-data-fixture/owner-data-32.cdr';seed=json.loads(source.with_name('seed.json').read_text())
|
|
if sha(source)!=seed['sha256']:raise SystemExit('Own Data seed identity mismatch')
|
|
process=OUT/'air-native';report=json.loads((OUT/'build-report.json').read_text())
|
|
if sha(process)!=report['process_sha256']:raise SystemExit('Own signed compiler changed')
|
|
manifest=(OUT/'test-inputs.json') if (args.bgra or args.fp_context or args.display_environment or args.framebuffer_protocol or args.framebuffer_power or args.framebuffer_geometry) else ROOT/('build/offline-metal-iosurface/test-inputs.json' if args.iosurface else 'build/offline-metal-api/test-inputs.json' if args.offline_metal_api else 'build/offline-gpu-transport/test-inputs.json' if args.offline_gpu_transport else 'build/adreno-vpc-native-tests/test-inputs.json' if args.vpc_tests else 'build/gpu-analysis/metal-device-abi.json' if args.metal_runtime else 'build/air-library-native-tests/test-inputs.json' if args.library_tests else 'scripts/data/native-air-fragment-inputs.json' if args.fragment_inputs else 'scripts/data/native-air-diagnostic-inputs.json')
|
|
if sha(manifest)!=report['manifest_sha256']:raise SystemExit('Input manifest differs from signed instrument')
|
|
cases=[] if (args.framebuffer_geometry or args.framebuffer_power or args.framebuffer_protocol or args.display_environment or args.fp_context or args.metal_runtime or args.library_tests or args.vpc_tests or args.offline_gpu_transport or args.offline_metal_api) else json.loads(manifest.read_text())['cases']
|
|
inputs=[]
|
|
for case in ([] if args.library_input else cases):
|
|
bitcode=ROOT/f"build/gpu-analysis/air/{case['library']}/module-{case['index']}.bc"
|
|
if sha(bitcode)!=case['input_sha256']:raise SystemExit('Original AIR differs')
|
|
inputs.append((case,bitcode))
|
|
image=OUT/('compiler-data'+suffix+'-32.cdr')
|
|
if image.exists():raise SystemExit('Refuse to overwrite a diagnostic Data image')
|
|
shutil.copyfile(source,image);mount=OUT/'data-mount';mount.mkdir(exist_ok=True)
|
|
attached=plistlib.loads(subprocess.check_output(['hdiutil','attach',str(image),'-readwrite','-nobrowse','-owners','off','-mountpoint',str(mount),'-plist'],timeout=30))
|
|
device=attached['system-entities'][0]['dev-entry']
|
|
addon_report={}
|
|
try:
|
|
info=plistlib.loads(subprocess.check_output(['diskutil','info','-plist',str(mount)],timeout=30))
|
|
if info['VolumeUUID']!=seed['volume_uuid']:raise SystemExit('Cloned Data UUID differs')
|
|
if shutil.disk_usage(mount).free<process.stat().st_size+sum(p.stat().st_size for _,p in inputs)+1024*1024:raise SystemExit('Insufficient scratch Data space')
|
|
(mount/'tmp').mkdir(exist_ok=True)
|
|
if args.plugin_bundle:
|
|
bundle=OUT/'NativeOfflineMetal.bundle'
|
|
for member,key in [('NativeOfflineMetal','plugin_sha256'),('Info.plist','plugin_info_sha256'),('_CodeSignature/CodeResources','plugin_resources_sha256')]:
|
|
if sha(bundle/member)!=report[key]:raise SystemExit('Own signed plugin changed')
|
|
shutil.copytree(bundle,mount/'tmp/NativeOfflineMetal.bundle')
|
|
if args.plugin_addon:
|
|
addon_root=mount/'tmp/native-metal-addons';addon_root.mkdir()
|
|
destination=addon_root/'NativeOfflineMetal.bundle';shutil.copytree(bundle,destination)
|
|
for member,key in [('NativeOfflineMetal','plugin_sha256'),('Info.plist','plugin_info_sha256'),('_CodeSignature/CodeResources','plugin_resources_sha256')]:
|
|
if sha(destination/member)!=report[key]:raise SystemExit('Own addon plugin copy changed')
|
|
addon_report=dict(volume_uuid=seed['volume_uuid'],plugin_sha256=report['plugin_sha256'],offline_only=True,
|
|
mutable_backing=True,read_only_bindfs_view=True,production_root_authorization=False)
|
|
if args.plugin_control=='tampered-code':
|
|
# Corrupt only our own factory's actual instruction page,
|
|
# retaining its original signature and explicit trusted hash.
|
|
target=destination/'NativeOfflineMetal';symbols=subprocess.check_output(['xcrun','nm','-n',target],text=True)
|
|
method=re.search(r'^([0-9a-f]{16})\s+\S\s+-\[NativeOfflineMetalPlugin initWithAcceleratorPort:\]$',symbols,re.M)
|
|
if not method:raise SystemExit('Cannot identify actual own factory method')
|
|
sys.path.insert(0,str(ROOT/'kits/XnuPrelink'));from native_prelink import macho,backed_offset
|
|
pristine=target.read_bytes();offset=backed_offset(macho(pristine)['segments'],int(method[1],16),4,len(pristine))
|
|
broken=bytearray(pristine);broken[offset]^=1;target.write_bytes(broken)
|
|
if sum(a!=b for a,b in zip(pristine,broken))!=1:raise SystemExit('Own negative copy must change exactly one instruction byte')
|
|
addon_report.update(tamper_file_offset=offset,actual_plugin_sha256=sha(target),original_signature_retained=True)
|
|
(mount/'tmp/native-metal-addon.plist').write_bytes(plistlib.dumps(addon_report))
|
|
if args.sandbox_extension:
|
|
consumer=OUT/'sandbox-consumer'
|
|
if sha(consumer)!=report['consumer_sha256']:raise SystemExit('Exact signed consumer required')
|
|
shutil.copyfile(consumer,mount/'tmp/sandbox-consumer');(mount/'tmp/sandbox-consumer').chmod(0o755)
|
|
shutil.copyfile(process,mount/'tmp/air-native');(mount/'tmp/air-native').chmod(0o755)
|
|
for case,bitcode in inputs:
|
|
target=mount/('tmp/'+case['name']+'.bc');shutil.copyfile(bitcode,target)
|
|
if sha(target)!=case['input_sha256']:raise SystemExit('Staged input differs')
|
|
if sha(mount/'tmp/air-native')!=report['process_sha256']:raise SystemExit('Staged process differs')
|
|
if args.case=='bad-input':
|
|
if not cases:raise SystemExit('Bad AIR input requires the compiler instrument')
|
|
target=mount/('tmp/'+cases[0]['name']+'.bc');raw=bytearray(target.read_bytes());raw[0]^=1;target.write_bytes(raw)
|
|
if sha(target)==cases[0]['input_sha256']:raise SystemExit('Negative input must actually differ')
|
|
finally:subprocess.run(['hdiutil','detach',device],check=True,timeout=30)
|
|
(OUT/('data'+suffix+'-report.json')).write_text(json.dumps(dict(bytes=image.stat().st_size,sha256=sha(image),volume_uuid=seed['volume_uuid'],
|
|
process_sha256=report['process_sha256'],manifest_sha256=sha(manifest),cases=len(cases),seed_sha256=seed['sha256'],
|
|
addon=addon_report,
|
|
original_os_modified=False,negative_input_copy_modified=args.case=='bad-input',own_mutable_data=True,production_storage_integrity=False),indent=2)+'\n')
|
|
print('PASS: own cloned Data fixture staged; original OS and SystemOS remain unchanged')
|