85 lines
3.3 KiB
Python
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
|