#!/usr/bin/python3 """Normal caller/Bridge/authority-loop integration under the isolated runner. The root coordinator is the production clock_refresh function. NTS measurement, collector namespace transitions and outer cgroup management are fixture seams; peer credentials, processes, issuer handoff, owner queue, storage, expiry and control protocol are real. This is clock mechanism qualification, not actual namespace isolation coverage. """ import json,os,select,signal,socket,subprocess,sys,time from dataclasses import replace from decimal import Decimal from pathlib import Path from types import SimpleNamespace sys.path.insert(0,str(Path(__file__).resolve().parents[2]/'scripts')) import c01_supervisor as s import c01_nts as nts import c01_recovery as recovery import c01_children as lifecycle assert os.geteuid()==0 and os.environ.get('ARCHY_TEST_ISOLATED')=='1' binary,case=sys.argv[1:];cfg=json.loads(Path(case).read_text());root=Path(cfg['root']) APP=61021;AUTH=61022 def drop(uid): os.setgroups([]);os.setgid(uid);os.setuid(uid) def native(role,uid,channel): name='c01_launcher::clock_refresh::tests::normal_refresh_'+role+'_process' env={**os.environ,'C01_NORMAL_REFRESH_CASE':case,'C01_NORMAL_REFRESH_FD':str(channel.fileno())} return subprocess.Popen([binary,'--exact',name,'--ignored','--test-threads=1','--nocapture'], env=env,pass_fds=(channel.fileno(),),preexec_fn=lambda:drop(uid),stdout=subprocess.DEVNULL) def synthetic_measurement(policy,raw=False): # Explicit synthetic issuer input. No live NTS claim or local-clock fallback # exists in production: this replacement is confined to this isolated driver. begin=nts.clock_reading(raw=raw);end=nts.clock_reading(raw=raw) assessment=nts.Assessment('01020304','127.0.0.1',Decimal(end.wall)/nts.NANO,Decimal(0),1_000_000,(),()) return nts.bind_observation(assessment,begin,end,recovery.boot_id(),recovery.boot_id(),policy) nts.collect=synthetic_measurement def fixture_collector_namespace(): # The backend runner deliberately drops SYS_ADMIN. Do not expand its host # capabilities for a synthetic NTS test. Install this seam ONLY in the # forked fixture collector, never in production or the parent coordinator. network=s.c01_network;libc=s.LIBC expected=network.process_namespace(os.getpid()) def same_namespace(pid,role): assert role=='managed' and pid==os.getpid() assert network.process_namespace(pid)==expected return expected def enter_host_worker(actual_libc): assert actual_libc is s.LIBC same_namespace(os.getpid(),'managed') class CollectorLibc: def __getattr__(self,name):return getattr(libc,name) def setns(self,fd,kind): assert kind==network.CLONE_NEWNET and network.identity(fd)==expected same_namespace(os.getpid(),'managed') return 0 s.LIBC=CollectorLibc() s.c01_network=SimpleNamespace(identity=network.identity,CLONE_NEWNET=network.CLONE_NEWNET, enter_host_worker=enter_host_worker,require_private=same_namespace) class FixtureChildren: def __init__(self):self.children={};self.results={};self.reserved={};self.stopping=False;self.spawned=0 def spawn(self,role,body,keep=()): assert role=='collector' and role not in self.children and role not in self.results parent,child=socket.socketpair(socket.AF_UNIX,socket.SOCK_SEQPACKET) parent.setsockopt(socket.SOL_SOCKET,socket.SO_PASSCRED,1) pid=os.fork() if pid==0: try: parent.close();s.close_except({child.fileno()}) fixture_collector_namespace();body(child);os._exit(0) except BaseException: import traceback;traceback.print_exc();os._exit(111) child.close();record=SimpleNamespace(role=role,pid=pid,channel=parent,pidfd=os.pidfd_open(pid),process=None) self.children[role]=record;self.spawned+=1;return record def poll(self): record=self.children.get('collector') if record: pid,status=os.waitpid(record.pid,os.WNOHANG) if pid: self.results['collector']=os.waitstatus_to_exitcode(status) record.channel.close();os.close(record.pidfd);del self.children['collector'] rearm_collector=lifecycle.Children.rearm_collector class Owner: receive=s.Supervisor.receive;wait=s.Supervisor.wait;relay_collector=s.Supervisor.relay_collector def __init__(self): self.children=FixtureChildren();self.clock_sequence=0;self.power_state=None self.completed_one_shots={'collector'};self.wake_read,self.wake_write=os.pipe2(os.O_NONBLOCK|os.O_CLOEXEC) def tick(self): self.children.poll() assert self.children.results.get('collector',0)==0,'collector failed' return True owner=Owner();records=[] try: parent,child=socket.socketpair(socket.AF_UNIX,socket.SOCK_SEQPACKET);parent.setsockopt(socket.SOL_SOCKET,socket.SO_PASSCRED,1) authority_process=native('authority',AUTH,child);child.close() authority=SimpleNamespace(role='authority',pid=authority_process.pid,channel=parent,process=authority_process) owner.children.children['authority']=authority;records.append(authority) end=s.now()+120*10**9;owner.wait(authority,b'LISTENING',AUTH,AUTH,end) parent,child=socket.socketpair(socket.AF_UNIX,socket.SOCK_SEQPACKET);parent.setsockopt(socket.SOL_SOCKET,socket.SO_PASSCRED,1) app_process=native('application',APP,child);child.close() app=SimpleNamespace(role='application',pid=app_process.pid,channel=parent,process=app_process) owner.children.children['application']=app;records.append(app) app_peer=dict(pid=app.pid,uid=APP,gid=APP);authority_peer=dict(pid=authority.pid,uid=AUTH,gid=AUTH) s.packet(authority.channel,app_peer);s.packet(app.channel,authority_peer) owner.wait(app,b'CONNECTED',APP,APP,end) context=json.loads(owner.receive(authority,AUTH,AUTH,end));assert context['status']=='EPOCH_CONTEXT' maximum=cfg['delegation']['max_awake'];maximum=maximum['secs']*10**9+maximum['nanos'] q=recovery.PlatformQualification(os.uname().release,recovery.clocksource(),'ab'*32,10**9,250_000_000,0,maximum,cfg['delegation']['characterized']) prepared=SimpleNamespace(epoch=dict(delegation=cfg['delegation']),value=dict(binding=cfg['binding'],clock_domain='01'*16)) collector=s.start_collector(owner,q,cfg['binding'],bytes([1])*16,authority_peer,'\0'+cfg['address'],end,prepared.epoch,context['challenge']) owner.relay_collector(collector,authority,AUTH,AUTH,end) owner.wait(authority,b'AUTHORIZED',AUTH,AUTH,end);owner.wait(collector,b'ADMIN_SENT',0,0,end);s.packet(collector.channel,b'RELEASE') s.packet(app.channel,b'INITIAL_AUTHORIZED') ready=False;failed=False;deadline=time.monotonic()+25 result_file=root/'data/refresh-result.json' while time.monotonic()