"""Root-selected Tor capabilities and direct native child launch. No provision/create/chown here: deployment supplies protected directories, and existing-node custody changes require their own admission. Never open key files. """ import ctypes import os import socket import stat from pathlib import Path import c01_network from c01_tor_watch import Watch def directory(path, tor_owned=False): parts = Path(path).parts if not parts or parts[0] != '/' or '..' in parts: raise ValueError('Tor directory must be fixed absolute input') fd = os.open('/', os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC) try: for index, part in enumerate(parts[1:], 1): child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC, dir_fd=fd) os.close(fd) fd = child meta = os.fstat(fd) if not stat.S_ISDIR(meta.st_mode) or meta.st_mode & 0o022 or (meta.st_uid != 0 and not (tor_owned and index == len(parts)-1)): raise ValueError('unprotected Tor directory') return fd except BaseException: os.close(fd) raise def capabilities(runtime): opened = [] try: opened.append(directory('/etc/tor')) opened.append(directory('/var/lib/tor', tor_owned=True)) export = os.open('tor-hostnames', os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC, dir_fd=runtime) opened.append(export) meta = os.fstat(export) if meta.st_uid != 0 or meta.st_mode & 0o022: raise ValueError('Tor public export directory not protected') opened.append(directory('/var/lib/archipelago-c01/tor')) return opened except BaseException: for fd in opened: os.close(fd) raise def listener(): channel = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) try: channel.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1) channel.bind('\0archy-c01-tor-' + os.urandom(16).hex()) channel.listen(1) channel.settimeout(1) return channel except BaseException: channel.close() raise def spawn(owner, executable, accepted, app_peer, dirs, native, packet, env): import struct peer = struct.unpack('3i', accepted.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, 12)) if peer != (app_peer['pid'], app_peer['uid'], app_peer['gid']) or app_peer['uid'] == 0: raise ValueError('Tor application peer mismatch') accepted.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1) def child(control): libc = ctypes.CDLL(None, use_errno=True) control.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1) if libc.prctl(38, 1, 0, 0, 0) != 0: raise ValueError('Tor NNP unavailable') c01_network.enter_host_worker(libc) native(executable, 'tor-owner-v1', {3: control.fileno(), 4: accepted.fileno(), **{5 + index: fd for index, fd in enumerate(dirs)}}, env) worker = owner.children.spawn('tor-owner', child, (accepted.fileno(), executable, *dirs)) owner.tor_watch = Watch(worker.channel, accepted, worker.pid) packet(worker.channel, {'version': 1, 'process_id': worker.pid, 'application_pid': app_peer['pid'], 'application_uid': app_peer['uid'], 'directories': [[os.fstat(fd).st_dev, os.fstat(fd).st_ino] for fd in dirs]}) return worker