def __init__(self, task: Task, ram: RAM, environment: t.Dict[bytes, bytes]) -> None: self.data = environment self.sh = Command(Path("/bin/sh"), ['sh'], {}) self.tmpdir = Path(self.get("TMPDIR", "/tmp")) self.path = ExecutablePathCache(task, ram, self.get("PATH", "").split(":"))
async def start_recursor(nursery, parent: Thread, path: Path, ipv4_sockets: t.List[t.Tuple[handle.FileDescriptor, handle.FileDescriptor]], ipv6_sockets: t.List[t.Tuple[handle.FileDescriptor, handle.FileDescriptor]], root_hints: dns.zone.Zone=None) -> Powerdns: pdns_recursor = Command(Path("/home/sbaugh/.local/src/pdns/pdns/recursordist/pdns_recursor"), ['pdns_recursor'], {}) thread = await parent.fork() ipv4s = {str(i+1): (udp.move(thread.task), tcp.move(thread.task)) for i, (udp, tcp) in enumerate(ipv4_sockets)} ipv6s = {"::"+str(i+1): (udp.move(thread.task), tcp.move(thread.task)) for i, (udp, tcp) in enumerate(ipv6_sockets)} addresses = {**ipv4s, **ipv6s} await thread.unshare_files(going_to_exec=True) config = { "config-dir": os.fsdecode(path), "socket-dir": os.fsdecode(path), # more logging "loglevel": "9", "log-common-errors": "yes", "quiet": "no", "trace": "yes", "dont-query": "", "logging-facility": "0", # relevant stuff "local-address": ",".join(addresses), "allow-from": "127.0.0.0/8", "local-address-udp-fds": ",".join([f"{i}={await fd.as_argument()}" for i, (fd, _) in addresses.items()]), "local-address-tcp-fds": ",".join([f"{i}={await fd.as_argument()}" for i, (_, fd) in addresses.items()]), } if root_hints is not None: config["hint-file"] = os.fsdecode(await thread.spit(path/'root.hints', root_hints.to_text())) child = await thread.exec(pdns_recursor.args(*[f"--{name}={value}" for name, value in config.items()])) nursery.start_soon(child.check) return Powerdns()
async def make_tun(process: Process, name: str, reqsock: FileDescriptor) -> t.Tuple[FileDescriptor, int]: # open /dev/net/tun tun_fd = await process.task.open(await process.ptr(Path("/dev/net/tun")), O.RDWR) # register TUN interface name for this /dev/net/tun fd ifreq = await process.ptr(Ifreq(name, flags=IFF.TUN)) await tun_fd.ioctl(TUNSETIFF, ifreq) # use reqsock to look up the interface index of the TUN interface by name (reusing the previous Ifreq) await reqsock.ioctl(SIOC.GIFINDEX, ifreq) tun_index = (await ifreq.read()).ifindex return tun_fd, tun_index
async def send_email(self, from_: str, to: str, subject: str, msg: str) -> None: thread = await self.thread.fork() await thread.unshare_files() fd = await thread.task.memfd_create(await thread.ram.ptr(Path('message'))) msg = f'From: {from_}\nSubject: {subject}\nTo: {to}\n\n' + msg await thread.spit(fd, msg) await fd.lseek(0, SEEK.SET) await thread.stdin.replace_with(fd) child = await thread.exec(self.sendmail.args('-t')) await child.check()
async def start_miredo(nursery, miredo_exec: MiredoExecutables, thread: Thread) -> Miredo: inet_sock = await thread.task.socket(AF.INET, SOCK.DGRAM) await inet_sock.bind(await thread.ram.ptr(SockaddrIn(0, 0))) # set a bunch of sockopts one = await thread.ram.ptr(Int32(1)) await inet_sock.setsockopt(SOL.IP, IP.RECVERR, one) await inet_sock.setsockopt(SOL.IP, IP.PKTINFO, one) await inet_sock.setsockopt(SOL.IP, IP.MULTICAST_TTL, one) # hello fragments my old friend await inet_sock.setsockopt(SOL.IP, IP.MTU_DISCOVER, await thread.ram.ptr(Int32(IP.PMTUDISC_DONT))) ns_thread = await thread.fork() await ns_thread.unshare(CLONE.NEWNET | CLONE.NEWUSER) # create icmp6 fd so miredo can relay pings icmp6_fd = await ns_thread.task.socket(AF.INET6, SOCK.RAW, IPPROTO.ICMPV6) # create the TUN interface tun_fd = await ns_thread.task.open( await ns_thread.ram.ptr(Path("/dev/net/tun")), O.RDWR) ptr = await thread.ram.ptr(netif.Ifreq(b'teredo', flags=netif.IFF_TUN)) await tun_fd.ioctl(netif.TUNSETIFF, ptr) # create reqsock for ifreq operations in this network namespace reqsock = await ns_thread.task.socket(AF.INET, SOCK.STREAM) await reqsock.ioctl(netif.SIOCGIFINDEX, ptr) tun_index = (await ptr.read()).ifindex # create socketpair for communication between privileged process and teredo client privproc_pair = await (await ns_thread.task.socketpair( AF.UNIX, SOCK.STREAM, 0, await ns_thread.ram.malloc(Socketpair))).read() privproc_thread = await ns_thread.fork() await add_to_ambient(privproc_thread, {CAP.NET_ADMIN}) privproc_child = await exec_miredo_privproc(miredo_exec, privproc_thread, privproc_pair.first, tun_index) nursery.start_soon(privproc_child.check) # TODO lock down the client thread, it's talking on the network and isn't audited... # should clear out the mount namespace # iterate through / and umount(MNT_DETACH) everything that isn't /nix # ummm and let's use UMOUNT_NOFOLLOW too # ummm no let's just only umount directories client_thread = await ns_thread.fork(CLONE.NEWPID) await client_thread.unshare(CLONE.NEWNET | CLONE.NEWNS) await client_thread.unshare_user() client_child = await exec_miredo_run_client(miredo_exec, client_thread, inet_sock, tun_fd, reqsock, icmp6_fd, privproc_pair.second, "teredo.remlab.net") nursery.start_soon(client_child.check) # we keep the ns thread around so we don't have to mess with setns return Miredo(ns_thread)
async def start_powerdns(nursery, parent: Thread, path: Path, zone: dns.zone.Zone, # tuple is (udpfd, listening tcpfd) ipv4_sockets: t.List[t.Tuple[handle.FileDescriptor, handle.FileDescriptor]], ipv6_sockets: t.List[t.Tuple[handle.FileDescriptor, handle.FileDescriptor]], ) -> Powerdns: pdns_server = Command(Path("/home/sbaugh/.local/src/pdns/pdns/pdns_server"), ['pdns_server'], {}) # pdns_server = await parent.environ.which("pdns_server") thread = await parent.fork() # we pretend to pass addresses like 0.0.0.1 etc # we add one so we don't pass 0.0.0.0 and make powerdns think it's bound to everything ipv4s = {str(i+1): (udp.move(thread.task), tcp.move(thread.task)) for i, (udp, tcp) in enumerate(ipv4_sockets)} ipv6s = {str(i+1): (udp.move(thread.task), tcp.move(thread.task)) for i, (udp, tcp) in enumerate(ipv6_sockets)} await thread.unshare_files(going_to_exec=True) config = { "config-dir": os.fsdecode(path), # TODO control-console seems to be a feature where it will listen on stdin or something? # we should use that instead of this socketdir "socket-dir": os.fsdecode(path), # more logging "loglevel": "9", "log-dns-queries": "yes", # backend "launch": "bind", "bind-config": os.fsdecode(await thread.spit(path/"named.conf", 'zone "%s" { file "%s"; };' % ( zone.origin.to_text(), os.fsdecode(await thread.spit(path/"zone", zone.to_text()))))), "enable-lua-records": "yes", # relevant stuff "local-address": ",".join(ipv4s), "local-address-udp-fds": ",".join([f"{i}={await fd.as_argument()}" for i, (fd, _) in ipv4s.items()]), "local-address-tcp-fds": ",".join([f"{i}={await fd.as_argument()}" for i, (_, fd) in ipv4s.items()]), "local-ipv6": ",".join(ipv6s), "local-ipv6-udp-fds": ",".join([f"{i}={await fd.as_argument()}" for i, (fd, _) in ipv6s.items()]), "local-ipv6-tcp-fds": ",".join([f"{i}={await fd.as_argument()}" for i, (_, fd) in ipv6s.items()]), } print(config['local-address-udp-fds']) child = await thread.exec(pdns_server.args(*[f"--{name}={value}" for name, value in config.items()])) nursery.start_soon(child.check) return Powerdns()
async def do_cloexec_except(thr: RAMThread, excluded_fds: t.Set[near.FileDescriptor]) -> None: "Close all CLOEXEC file descriptors, except for those in a whitelist. Would be nice to have a syscall for this." buf = await thr.ram.malloc(DirentList, 4096) dirfd = await thr.task.open(await thr.ram.ptr(Path("/proc/self/fd")), O.DIRECTORY) async def maybe_close(fd: near.FileDescriptor) -> None: flags = await syscalls.fcntl(thr.task.sysif, fd, F.GETFD) if (flags & FD_CLOEXEC) and (fd not in excluded_fds): await syscalls.close(thr.task.sysif, fd) async with trio.open_nursery() as nursery: while True: valid, rest = await dirfd.getdents(buf) if valid.size() == 0: break dents = await valid.read() for dent in dents: try: num = int(dent.name) except ValueError: continue nursery.start_soon(maybe_close, near.FileDescriptor(num)) buf = valid.merge(rest)
async def write_user_mappings(thr: RAMThread, uid: int, gid: int, in_namespace_uid: int=None, in_namespace_gid: int=None) -> None: """Set up a new user namespace with single-user {uid,gid}_map These are the only valid mappings for unprivileged user namespaces. """ if in_namespace_uid is None: in_namespace_uid = uid if in_namespace_gid is None: in_namespace_gid = gid procself = Path("/proc/self") uid_map = await thr.task.open(await thr.ram.ptr(procself/"uid_map"), O.WRONLY) await uid_map.write(await thr.ram.ptr(f"{in_namespace_uid} {uid} 1\n".encode())) await uid_map.close() setgroups = await thr.task.open(await thr.ram.ptr(procself/"setgroups"), O.WRONLY) await setgroups.write(await thr.ram.ptr(b"deny")) await setgroups.close() gid_map = await thr.task.open(await thr.ram.ptr(procself/"gid_map"), O.WRONLY) await gid_map.write(await thr.ram.ptr(f"{in_namespace_gid} {gid} 1\n".encode())) await gid_map.close()
async def which(self, name: str) -> Command: "Locate an executable with this name on PATH; throw ExecutableNotFound on failure" try: path = self.name_to_path[name] except KeyError: nameptr = await self.ram.ptr(Path(name)) # do the lookup for 64 paths at a time, that seems like a good batching number for paths in chunks(self.paths, 64): thunks = [ functools.partial(self._check, path, nameptr) for path in paths ] results = await run_all(thunks) # type: ignore for path, result in zip(paths, results): if result: # path is set as the loop variable; python has no scope # so we can just break out and use it. break if result: break else: raise ExecutableNotFound(name) self.name_to_path[name] = path return Command(path / name, [name], {})
def __init__(self, task: Task, ram: RAM, paths: t.List[str]) -> None: self.task = task self.ram = ram self.paths = [Path(path) for path in paths] self.fds: t.Dict[Path, t.Optional[FileDescriptor]] = {} self.name_to_path: t.Dict[str, Path] = {}