Files

85 lines
3.3 KiB
Python

"""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