"""Reviewed, rename-only separation of manager metadata from runtime data. Called by access retirement only after every inventoried old writer exited. Private trees retain every inode/UID/GID; modes are preserved except explicitly reviewed identity-directory hardening to 0700. Runtime trees are never moved. An interrupted transition remains fenced. This module never copies data, chowns existing data, generates identities, thaws writers, or initializes storage. """ import ctypes import hashlib import json import os from pathlib import Path import re import stat import c01_access_policy as policy_module require=policy_module.require LIBC=ctypes.CDLL(None,use_errno=True) LIBC.renameat2.argtypes=[ctypes.c_int,ctypes.c_char_p,ctypes.c_int,ctypes.c_char_p,ctypes.c_uint] LIBC.renameat2.restype=ctypes.c_int def identity(value):return policy_module.identity(value) def validate(plan, launch, runtime): require(type(plan) is dict and set(plan)=={'version','source_root','source_identity','target_root', 'target_identity','private_parent','entries','quarantine_parents'},'layout exact fields') require(type(plan['version']) is int and plan['version']==1,'layout version') require(plan['source_root']==runtime['runtime_data_dir'] and plan['target_root']==launch['manager_backing'], 'layout must separate the appointed manager/runtime roots') source=policy_module.canonical(plan['source_root']);target=policy_module.canonical(plan['target_root']) require(source!=target and not source.is_relative_to(target) and not target.is_relative_to(source), 'layout requires an independently protected target alias') parent=plan['private_parent'] require(type(parent) is dict and set(parent)=={'path','identity'} and policy_module.canonical(parent['path']).parent==source,'private parent binding') for value in (plan['source_identity'],plan['target_identity'],parent['identity']): require(type(value) is list and len(value)==5 and all(type(n) is int and n>=0 for n in value), 'layout identity encoding') require(plan['source_identity'][0]==plan['target_identity'][0]==parent['identity'][0] and plan['source_identity'][2:4]==[runtime['runtime_uid'],runtime['runtime_gid']] and plan['target_identity'][2:]==[runtime['runtime_uid'],runtime['runtime_gid'],0o700] and parent['identity'][2:]==[0,0,0o700], 'same-filesystem private custody required') require(type(plan['entries']) is list and 1<=len(plan['entries'])<=1024,'complete bounded root inventory') names=set();private=set() for entry in plan['entries']: require(type(entry) is dict and set(entry)=={'name','disposition','identity','mode_after','tree_metadata_sha256'},'layout entry fields') name=entry['name'] require(type(name) is str and name not in ('','.','..',Path(parent['path']).name) and '/' not in name and '\0' not in name and len(os.fsencode(name))<=255 and name not in names, 'layout entry name/alias') names.add(name) require(entry['disposition'] in ('private','runtime'),'unclassified data blocks migration') value=entry['identity'] require(type(value) is list and len(value)==5 and all(type(n) is int and n>=0 for n in value), 'entry identity') require(type(entry['mode_after']) is int and (entry['mode_after']==value[4] or (name=='identity' and entry['disposition']=='private' and value[4] in (0o750,0o755) and entry['mode_after']==0o700)), 'only explicit identity-directory mode hardening is permitted') if name=='identity': require(entry['disposition']=='private' and entry['mode_after']==0o700 and value[2:4]==[runtime['runtime_uid'],runtime['runtime_gid']], 'identity directory must meet the existing private-owner contract') if entry['disposition']=='private': private.add(name) require(policy_module.hex_digest(entry['tree_metadata_sha256']),'private metadata inventory required') else:require(entry['tree_metadata_sha256'] is None,'runtime contents must not be copied/reinventoried') require({'identity','federation'}<=private,'existing identity and history must move intact') require(type(plan['quarantine_parents']) is list and len(plan['quarantine_parents'])<=16 and len(set(plan['quarantine_parents']))==len(plan['quarantine_parents']),'quarantine parent inventory') for path in plan['quarantine_parents']:policy_module.canonical(path) def readable(path): pin=policy_module.open_path(path,True) try:return os.open('.',os.O_RDONLY|os.O_DIRECTORY|os.O_CLOEXEC,dir_fd=pin) finally:os.close(pin) def private_metadata(path, normalized_root_mode=None): """Bounded metadata inventory, without reading wallet/cache/private-key bytes. Hard links are permitted only when every link is inside this moved tree. Symlinks/sockets/devices require explicit resolution, never guessed rewrites. """ path=Path(path) for line in Path('/proc/self/mountinfo').read_text().splitlines(): fields=line.split() target=Path(re.sub(r'\\([0-7]{3})',lambda m:chr(int(m[1],8)),fields[4])) require(not target.is_relative_to(path), 'private nested mount requires explicit disposition') records=[];links={};expected={};device=path.lstat().st_dev def visit(parent,name,relative,depth): require(depth<=64 and len(records)<100000,'private metadata inventory bound') m=os.stat(name,dir_fd=parent,follow_symlinks=False) require(m.st_dev==device,'private tree crosses filesystem boundary') require(stat.S_ISDIR(m.st_mode) or stat.S_ISREG(m.st_mode),'unresolved private alias/object') recorded=identity(m) if relative=='' and normalized_root_mode is not None:recorded[4]=normalized_root_mode records.append([relative,recorded,m.st_size,m.st_mtime_ns,stat.S_IFMT(m.st_mode),m.st_nlink]) if stat.S_ISREG(m.st_mode): key=(m.st_dev,m.st_ino);links[key]=links.get(key,0)+1;expected[key]=m.st_nlink return fd=os.open(name,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW|os.O_CLOEXEC,dir_fd=parent) try: require(identity(os.fstat(fd))==identity(m),'private directory changed') for child in sorted(os.listdir(fd)):visit(fd,child,relative+'/'+child,depth+1) after=os.fstat(fd) require((identity(after),after.st_size,after.st_mtime_ns)==(identity(m),m.st_size,m.st_mtime_ns), 'private metadata changed during inventory') finally:os.close(fd) parent=readable(Path(path).parent) try:visit(parent,Path(path).name,'',0) finally:os.close(parent) require(links==expected,'private file has an unaccounted hard-link alias') return hashlib.sha256(json.dumps(records,separators=(',',':'),ensure_ascii=True).encode()).hexdigest() def roots(plan): parent=Path(plan['private_parent']['path']) m=parent.lstat() require(stat.S_ISDIR(m.st_mode) and identity(m)==plan['private_parent']['identity'], 'private custody parent changed') target=Path(plan['target_root']) policy_module.protected(target.parent,private=True,directory=True) require(identity(target.parent.stat())==identity(m), 'private target alias differs from original custody inode') source_fd=readable(plan['source_root']) try: # renameat2 requires the same VFS mount, not merely st_dev equality. # Authenticate against the protected alias, but perform the move using # the explicitly appointed original directory on the source mount. alias_fd=readable(target) try: alias_identity=identity(os.fstat(alias_fd));alias_flags=os.fstatvfs(alias_fd).f_flag parent_fd=os.open(parent.name,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW|os.O_CLOEXEC,dir_fd=source_fd) try: require(identity(os.fstat(parent_fd))==plan['private_parent']['identity'], 'original private parent replaced') target_fd=os.open(target.name,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW|os.O_CLOEXEC,dir_fd=parent_fd) finally:os.close(parent_fd) finally:os.close(alias_fd) try: require(alias_identity==identity(os.fstat(target_fd)), 'private alias/original mismatch') require(identity(os.fstat(source_fd))==plan['source_identity'] and identity(os.fstat(target_fd))==plan['target_identity'], 'layout root identity changed') flags=os.ST_RDONLY|os.ST_NOSUID|os.ST_NODEV|os.ST_NOEXEC original=os.fstatvfs(source_fd).f_flag & flags require(os.fstatvfs(target_fd).f_flag & original == original and alias_flags & original == original, 'layout weakens original mount protections') return source_fd,target_fd except BaseException:os.close(target_fd);raise except BaseException:os.close(source_fd);raise def preflight(policy): plan=policy.value['migration_layout'];validate(plan,policy.launch,policy.runtime) source,target=roots(plan) try: require(set(os.listdir(source))=={e['name'] for e in plan['entries']}|{Path(plan['private_parent']['path']).name}, 'unclassified/new root entry blocks layout migration') require(not os.listdir(target),'existing manager target cannot be overwritten') for entry in plan['entries']: m=os.stat(entry['name'],dir_fd=source,follow_symlinks=False) require(identity(m)==entry['identity'],'layout source identity changed') if entry['disposition']=='private': require(entry['name']!='identity' or stat.S_ISDIR(m.st_mode),'identity must be a directory') require(private_metadata(Path(plan['source_root'])/entry['name'])==entry['tree_metadata_sha256'], 'private source metadata changed') finally:os.close(source);os.close(target) def apply_retired(policy, resume=False): if not resume:preflight(policy) plan=policy.value['migration_layout'];source,target=roots(plan) try: moved=relocate_entries(plan,source,target,resume) verify_live(policy) return {'source_identity':plan['source_identity'],'target_identity':plan['target_identity'],'moved':moved} finally:os.close(source);os.close(target) def relocate_entries(plan,source,target,resume): """Filesystem primitive; caller owns validated roots and writer retirement.""" moved=[] if resume: source_names=set(os.listdir(source));target_names=set(os.listdir(target)) private={e['name'] for e in plan['entries'] if e['disposition']=='private'} runtime={e['name'] for e in plan['entries'] if e['disposition']=='runtime'} parent=Path(plan['private_parent']['path']).name require(target_names<=private and source_names|target_names==private|runtime|{parent} and not (source_names & target_names), 'ambiguous partial layout requires explicit disposition') for entry in plan['entries']: if entry['disposition']!='private':continue name=os.fsencode(entry['name']) if not resume or entry['name'] in source_names: require(identity(os.stat(entry['name'],dir_fd=source,follow_symlinks=False))==entry['identity'] and private_metadata(Path(plan['source_root'])/entry['name'])==entry['tree_metadata_sha256'], 'retired source identity/metadata changed') if LIBC.renameat2(source,name,target,name,1): error=ctypes.get_errno();raise OSError(error,os.strerror(error)) os.fsync(source);os.fsync(target) current=os.stat(entry['name'],dir_fd=target,follow_symlinks=False) expected=list(entry['identity']);expected[4]=entry['mode_after'] require(identity(current) in (entry['identity'],expected) and private_metadata(Path(plan['target_root'])/entry['name'],entry['identity'][4])==entry['tree_metadata_sha256'], 'layout preservation proof differs') if stat.S_IMODE(current.st_mode)!=entry['mode_after']: require(entry['name']=='identity' and stat.S_ISDIR(current.st_mode),'unapproved metadata mode change') fd=os.open(entry['name'],os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW|os.O_CLOEXEC,dir_fd=target) try:os.fchmod(fd,entry['mode_after']);os.fsync(fd) finally:os.close(fd) os.fsync(target) require(identity(os.stat(entry['name'],dir_fd=target,follow_symlinks=False))==expected, 'identity hardening postimage differs') moved.append({'name':entry['name'],'identity':entry['identity'],'mode_after':entry['mode_after'], 'tree_metadata_sha256':entry['tree_metadata_sha256']}) return moved def verify_live(policy): plan=policy.value['migration_layout'];source,target=roots(plan) try: for entry in plan['entries']: if entry['disposition']=='private': require(not os.path.lexists(Path(plan['source_root'])/entry['name']), 'legacy private locator reappeared') # Runtime data may legitimately evolve after activation. Its root # preservation is separately checked by the existing access policy. finally:os.close(source);os.close(target) def custody(policy,index,item): if 'custody' not in item:return policy_module.STATE/'quarantine'/str(index) require(policy.value['version']==3 and item['disposition']=='quarantine-known-copy','unappointed custody override') target=policy_module.canonical(item['custody']) require(str(target.parent) in policy.value['migration_layout']['quarantine_parents'] and target.name==str(index), 'quarantine destination differs from reviewed per-filesystem custody') return target def custody_directory(policy,index,item): target=custody(policy,index,item) policy_module.protected(target.parent,private=True,directory=True) if 'custody' in item:return readable(target.parent) plan=policy.value['migration_layout'] # The default access-state alias is deliberately provisioned on the data # filesystem. Compare both capabilities before using the original mount. source,manager=roots(plan) os.close(source);os.close(manager) physical=Path(plan['private_parent']['path'])/'access/quarantine' alias=readable(target.parent) try: fd=readable(physical) try: require(identity(os.fstat(fd))==identity(os.fstat(alias)), 'quarantine alias/original mismatch') return fd except BaseException:os.close(fd);raise finally:os.close(alias)