#!/usr/bin/python3 """Normal native startup boundary in an owned rootless PID1 realm. Exit 77 is BLOCKED, not a readiness pass. The prerequisite driver is retained as a separate historical sub-exit. No grants, leases, APP_READY or observer results are fabricated. Run only with test-c01-normal-systemd-isolated.sh. """ import hashlib import json import os from pathlib import Path import socket import subprocess import time import threading PACKAGE = Path('/opt/c01-fixture') EVIDENCE = Path('/var/lib/c01-normal') def run(*args, timeout=120): return subprocess.run(args, capture_output=True, text=True, timeout=timeout) def record(name, value): (EVIDENCE / name).write_text(json.dumps(value, indent=2) + '\n') def native_boundary(selector, label, expected, slots=(), passcred=False): # An actual ordinary executable; intentionally no libtest entry selector. command = ['/usr/local/bin/archipelago', selector] channels = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET) if slots else () if channels and passcred: channels[0].setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1) def setup(): for slot in slots: os.dup2(channels[0].fileno(), slot, inheritable=True) try: p = subprocess.run(command, capture_output=True, text=True, timeout=30, close_fds=False, preexec_fn=setup) finally: for channel in channels: channel.close() record(label + '.json', {'command': command, 'exit': p.returncode, 'stdout': p.stdout, 'stderr': p.stderr, 'foreign_slots': slots, 'passcred': passcred, 'expected_boundary': expected}) if p.returncode == 0: raise AssertionError('unexpected native capability-free acceptance: ' + label) assert expected in p.stderr, (label, p.stderr) print('NATIVE REFUSAL', label, p.returncode, p.stderr.strip(), flush=True) def main(): assert os.geteuid() == 0 and Path('/run/.containerenv').exists() assert os.environ.get('C01_NORMAL_PID1') == '1' assert socket.gethostname() == 'c01-prerequisite-qualification' assert Path('/proc/1/comm').read_text().strip() == 'systemd' EVIDENCE.mkdir(mode=0o700) manifest = json.loads((PACKAGE / 'source-sha256.json').read_text()) for name, digest in manifest.items(): assert hashlib.sha256((PACKAGE / name).read_bytes()).hexdigest() == digest, name record('source-verified.json', {'files': len(manifest), 'manifest_sha256': hashlib.sha256((PACKAGE / 'source-sha256.json').read_bytes()).hexdigest()}) archive=PACKAGE/('normal-native.tar.gz' if (PACKAGE/'normal-native.tar.gz').exists() else 'normal-native.tar') preinstalled=PACKAGE/'native-preinstalled.json' stack={};derivation=[] if preinstalled.exists(): proof=json.loads(preinstalled.read_text()) with archive.open('rb') as stream:assert hashlib.file_digest(stream,'sha256').hexdigest()==proof['archive_sha256'] if os.environ.get('C01_RUNNING_SERVICES')=='1': with (PACKAGE/'service-stack.tar.gz').open('rb') as stream: assert hashlib.file_digest(stream,'sha256').hexdigest()==proof['service_stack_sha256'] stack=json.loads((PACKAGE/'service-stack-manifest.json').read_text())['files'] assert 'usr/local/bin/archipelago' not in stack for name,item in stack.items():assert hashlib.sha256(Path('/'+name).read_bytes()).hexdigest()==item['sha256'] else: assert os.environ.get('C01_RUNNING_SERVICES')!='1','running profile dependencies must be installed before PID1' installed = run('tar', '-xf', str(archive), '-C', '/') assert installed.returncode == 0, installed.stderr for name, item in json.loads((PACKAGE / 'native-artifact-sha256.json').read_text()).items(): canonical='usr/'+name if name.startswith(('lib/','lib64/')) else name expected=stack.get(canonical,item)['sha256'] assert hashlib.sha256(Path('/' + name).read_bytes()).hexdigest() == expected, name if expected!=item['sha256']:derivation.append({'path':name,'build_dependency':item['sha256'],'installed_node_dependency':expected}) record('native-runtime-derivation.json',{'unchanged_native_elf':True,'captured_public_stack':bool(stack), 'dependency_overrides':derivation}) native_boundary('c01-managed-v6', 'app-v6-missing-fd12', 'required Tor lifecycle FD12 missing') native_boundary('c01-managed-v7', 'app-v7-missing-fd14', 'required v7 radio FD14 missing') native_boundary('c01-managed-v7', 'app-v7-missing-fd12', 'required Tor lifecycle FD12 missing', slots=(14,)) native_boundary('c01-managed-v7', 'app-v7-missing-fd11', 'required network settings FD11 missing', slots=(12,14)) native_boundary('c01-managed-v7', 'app-v7-missing-fd10', 'required D09 FD10 missing', slots=(11,12,14)) native_boundary('c01-managed-v6', 'app-v6-missing-fd11', 'required network settings FD11 missing', slots=(12,)) native_boundary('c01-managed-v6', 'app-v6-missing-fd10', 'required D09 FD10 missing', slots=(11, 12)) native_boundary('c01-managed-v6', 'app-v6-foreign-descriptors', 'D09 FD10 type/passcred', slots=(10, 11, 12)) native_boundary('c01-managed-v6', 'app-v6-parent-is-not-d09', 'FD10 must name independent D09 creator', slots=(10, 11, 12), passcred=True) native_boundary('c01-managed-v5', 'obsolete-app-v5', 'obsolete protected application ABI') native_boundary('c01-managed-v4', 'obsolete-app-v4', 'obsolete protected application ABI') native_boundary('c01-managed-v3', 'obsolete-app-v3', 'obsolete protected application ABI') native_boundary('c01-provision-store-v1', 'store-provision-unconfined-root', 'C01 requires permanently dropped non-root credentials') # Execute the established prerequisite unchanged and retain its independent # real kernel/runtime observations. Its tmux command is NOT default terminal. env = dict(os.environ, C01_DISPOSABLE_SYSTEMD='1') p = subprocess.run(['python3', '-B', str(PACKAGE / 'tests/regression/c01-prerequisite-systemd.py')], env=env, capture_output=True, text=True, timeout=300) (EVIDENCE / 'prerequisite.log').write_text(p.stdout + p.stderr) record('prerequisite-exit.json', {'exit': p.returncode, 'qualification': 'separate prerequisite only'}) print('PREREQUISITE SUB-EXIT', p.returncode, p.stdout, p.stderr, flush=True) if p.returncode: record('result.json',{'status':'BLOCKED_PREREQUISITE','exit':77,'native_ready':False, 'installed_acceptance':False,'evidence':'prerequisite.log'}) return 77 composed = subprocess.run(['python3', '-B', str(PACKAGE / 'tests/regression/c01-compose.py')], capture_output=True, text=True, timeout=600) (EVIDENCE / 'composition.log').write_text(composed.stdout + composed.stderr) print('COMPOSITION SUB-EXIT', composed.returncode, composed.stdout, composed.stderr, flush=True) if composed.returncode: record('result.json',{'status':'BLOCKED_COMPOSITION','exit':77,'native_ready':False, 'installed_acceptance':False,'evidence':'composition.json'}) return 77 if os.environ.get('C01_POWER_COMPONENT') == '1': assert composed.returncode == 0, 'power component prerequisites failed' result = subprocess.run(['python3','-B',str(PACKAGE/'tests/regression/c01-power-systemd.py')], capture_output=True,text=True,timeout=180) (EVIDENCE/'power-test.log').write_text(result.stdout+result.stderr) print(result.stdout,result.stderr,flush=True) return result.returncode # Native root supervisor is itself MainPID (embedded CPython, no Python exec). # Type=notify ensures a successfully executed but unready process cannot pass. Path('/run/archipelago-c01').mkdir(exist_ok=True) Path('/var/lib/archipelago-c01').mkdir(mode=0o700, exist_ok=True) # Access, D09 and network owners authenticate this exact service identity. # A differently named otherwise identical unit cannot compose their leases. unit = Path('/etc/systemd/system/archipelago.service') if not unit.exists(): unit.write_text('[Service]\nType=notify\nNotifyAccess=main\nDelegate=yes\nDelegateSubgroup=supervisor\nNoNewPrivileges=yes\n' 'NetworkNamespacePath=/run/netns/archipelago-managed\n' 'ExecStart=/usr/local/bin/archipelago c01-supervisor-v3\n' 'TimeoutStartSec=150\nTimeoutStopSec=infinity\nRestart=no\n') assert run('systemctl', 'daemon-reload').returncode == 0 composition = json.loads((EVIDENCE / 'composition.json').read_text()) retained_units = ('archipelago-c01-access.service','archipelago-runtime-broker.service', 'archipelago-c01-network.service','archipelago-d09-lifecycle.service', 'archipelago-nts.service','user@1000.service') def owner_states(): return {name:dict(line.split('=',1) for line in run('systemctl','show',name, '-p','MainPID','-p','ActiveState','-p','InvocationID').stdout.splitlines() if '=' in line) for name in retained_units} before_owners = owner_states() properties=run('systemctl','show','archipelago.service','-p','CapabilityBoundingSet','-p','AmbientCapabilities', '-p','SecureBits','-p','NoNewPrivileges','-p','RestrictNamespaces','-p','PrivateUsers', '-p','RestrictSUIDSGID','-p','RootDirectory').stdout try:file_caps=os.getxattr('/usr/local/bin/archipelago','security.capability').hex() except OSError:file_caps=None record('native-execution-attributes.json',{'systemd':properties,'file_capabilities':file_caps}) credential_samples={} stop_samples=threading.Event() def sample_credentials(): while not stop_samples.wait(.005): for role in ('supervisor','c01-authority'): path=Path('/sys/fs/cgroup/system.slice/archipelago.service')/role/'cgroup.procs' try:members=path.read_text().split() except FileNotFoundError:continue for pid in members: try: status=dict(line.split(':',1) for line in Path('/proc',pid,'status').read_text().splitlines() if ':' in line) item={k:status[k].strip() for k in ('Uid','Gid','CapInh','CapPrm','CapEff','CapBnd','CapAmb','NoNewPrivs','Seccomp')} item['exe']=os.readlink('/proc/'+pid+'/exe') item['apparmor']=Path('/proc',pid,'attr/apparmor/current').read_text().strip() credential_samples[(role,pid,json.dumps(item,sort_keys=True))]=item except (FileNotFoundError,ProcessLookupError,PermissionError):pass sampler=threading.Thread(target=sample_credentials,daemon=True);sampler.start() if composition.get('d09_owner', {}).get('status') == 'completed': start = run('/usr/libexec/archipelago-d09-provision', '--activate-prerequisite') else: start = run('systemctl', 'start', '--no-block', unit.name) observations = [] for _ in range(1500): state = run('systemctl', 'show', unit.name, '-p', 'MainPID', '-p', 'ActiveState', '-p', 'SubState', '-p', 'Result', '-p', 'ExecMainStatus', '-p', 'Job').stdout fields = dict(line.split('=', 1) for line in state.splitlines() if '=' in line) pid = int(fields.get('MainPID', '0')) if pid: try: fields['proc_exe'] = os.readlink(f'/proc/{pid}/exe') fields['cgroup'] = Path(f'/proc/{pid}/cgroup').read_text() except FileNotFoundError: pass if not observations or fields != observations[-1]: observations.append(fields) if fields.get('ActiveState') in ('failed', 'active'): break if (fields.get('ActiveState')=='inactive' and fields.get('Job') in ('','0','0 /') and any(item.get('MainPID','0')!='0' for item in observations)): break time.sleep(.1) journal = run('journalctl', '-u', unit.name, '--no-pager').stdout stop_samples.set();sampler.join(timeout=2) record('native-process-credentials.json',[{'role':role,'pid':pid,**item} for (role,pid,_),item in credential_samples.items()]) (EVIDENCE / 'native-supervisor.log').write_text(journal) record('native-supervisor.json', {'start_exit': start.returncode, 'start_reply':start.stdout, 'start_stderr':start.stderr, 'observations': observations}) assert 'IO Safety violation' not in journal, 'embedding retained a descriptor across Python cleanup' assert observations[-1].get('Result') != 'core-dump', 'native startup failure must not abort' if observations[-1].get('ActiveState')=='active': assert all(item['status']=='completed' for item in composition.values()), 'ready despite failed prerequisites' assert 'C01_BASELINE_READY' in journal and 'C01_LIFETIME_CAPABILITIES_ACQUIRED' in journal accepted=subprocess.run(['python3','-B',str(PACKAGE/'tests/regression/c01-normal-acceptance.py')], capture_output=True,text=True,timeout=850) (EVIDENCE/'native-acceptance.log').write_text(accepted.stdout+accepted.stderr) record('result.json',{'status':'PASSED_PRIVATE_BASELINE' if accepted.returncode==0 else 'BLOCKED_LIVE_ACCEPTANCE', 'exit':accepted.returncode,'native_ready':True,'acceptance_log':'native-acceptance.log', 'installed_acceptance':False,'optional_capabilities':json.loads((EVIDENCE/'conditional-admission.json').read_text())['optional_unavailable']}) print('ACTUAL NATIVE ACCEPTANCE',accepted.returncode,accepted.stdout,accepted.stderr,flush=True) return accepted.returncode if all(item['status']=='completed' for item in composition.values()): assert 'C01_LIFETIME_CAPABILITIES_ACQUIRED' in journal, 'complete provisioning did not reach native lifetime acquisition' if (PACKAGE / 'candidate-appointment.json').exists(): record('candidate-startup-outcome.json', {'active':False, 'timing_bound_refusal':'invalid platform qualification bound' in journal, 'native_journal':'native-supervisor.log', 'composed_qualification':False}) else: assert 'invalid platform qualification bound' in journal, 'unexpected earlier native composition failure' if composition.get('d09_owner',{}).get('status')=='completed': ledger_path=Path('/var/lib/archipelago-d09/ledger.json') deadline=time.monotonic()+10 while time.monotonic()1 for v in after_owners.values()), 'independent owner/runtime lifetime lost on management failure' # Record the exact frozen implementation of the outer gate, never replace it. text = (PACKAGE / 'scripts/c01_supervisor.py').read_text() gate = text[text.index('def admit('):text.index('def acquire_admission_leases(')] (EVIDENCE / 'outer-admission-source.txt').write_text(gate) record('result.json', { 'status': 'BLOCKED', 'exit': 77, 'prerequisite_exit': p.returncode, 'native_default_terminal': 'not reached', 'actual_owner_leases': 'acquired and retired by actual native supervisor' if 'C01_LIFETIME_CAPABILITIES_ACQUIRED' in journal else 'not established by this fixture', 'clock_storage_bootstrap': {'store_provisioning':composition.get('stores',{}).get('status'), 'clock_recovery':'not reached'}, 'generation_A_B_C_B': 'not reached', 'recovery_restart': 'not reached', 'physical_platform_qualification': 'not performed', 'native_failure_lines': [line for line in journal.splitlines() if 'Error:' in line or 'C01_ADMISSION_UNAVAILABLE' in line], 'composition_stage_failures': {name:item.get('error') for name,item in composition.items() if item['status']!='completed'}, 'blockers': ['See native-supervisor.log for the actual earliest startup boundary'], 'conditional_candidate_bound':(EVIDENCE/'candidate-binding.json').exists(), 'conditional_admission_measured':'C01_CONDITIONAL_ADMISSION_HELD' in journal, 'source_boundary': 'ordinary native binary and selectors; prerequisite tmux is not native terminal readiness'}) print('BLOCKED normal native composition: see result.json and native-supervisor.log', flush=True) return 77 if __name__ == '__main__': raise SystemExit(main())