Example #1
0
    async def test_copy(self) -> None:
        cat = await self.store.bin(coreutils_nixdep, "cat")

        local_file = await self.local.task.memfd_create(await
                                                        self.local.ram.ptr(
                                                            Path("source")))
        remote_file = await self.remote.task.memfd_create(await
                                                          self.remote.ram.ptr(
                                                              Path("dest")))

        data = b'hello world'
        await local_file.write(await self.local.ram.ptr(data))
        await local_file.lseek(0, SEEK.SET)

        [(local_sock, remote_sock)] = await self.remote.open_channels(1)

        local_child = await start_cat(self.local, cat, local_file, local_sock)
        await local_sock.close()

        remote_child = await start_cat(self.remote, cat, remote_sock,
                                       remote_file)
        await remote_sock.close()

        await local_child.check()
        await remote_child.check()

        await remote_file.lseek(0, SEEK.SET)
        read, _ = await remote_file.read(await self.remote.ram.malloc(
            bytes, len(data)))
        self.assertEqual(await read.read(), data)
Example #2
0
 def __init__(
     self,
     environment: t.Dict[str, str],
     path: ExecutablePathCache,
     arglist_ptr: t.Optional[WrittenPointer[ArgList]] = None,
 ) -> None:
     self.data = environment
     self.sh = Command(Path("/bin/sh"), ['sh'], {})
     "The POSIX-required `/bin/sh`, as a `rsyscall.Command`"
     self.tmpdir = Path(self.get("TMPDIR", "/tmp"))
     "`TMPDIR`, or `/tmp` if it's not set, as a `rsyscall.path.Path`"
     self.path = path
     self.arglist_ptr = arglist_ptr
Example #3
0
async def write_user_mappings(thr: Process,
                              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.task.ptr(procself / "uid_map"),
                                  O.WRONLY)
    await uid_map.write(await
                        thr.task.ptr(f"{in_namespace_uid} {uid} 1\n".encode()))
    await uid_map.close()

    setgroups = await thr.task.open(await thr.task.ptr(procself / "setgroups"),
                                    O.WRONLY)
    await setgroups.write(await thr.task.ptr(b"deny"))
    await setgroups.close()

    gid_map = await thr.task.open(await thr.task.ptr(procself / "gid_map"),
                                  O.WRONLY)
    await gid_map.write(await
                        thr.task.ptr(f"{in_namespace_gid} {gid} 1\n".encode()))
    await gid_map.close()
Example #4
0
async def _exec_tar_copy_tree(src: ChildProcess,
                              src_paths: t.Sequence[t.Union[str, os.PathLike]],
                              src_fd: FileDescriptor, dest: ChildProcess,
                              dest_path: t.Union[str, os.PathLike],
                              dest_fd: FileDescriptor) -> None:
    "Exec tar to copy files between two paths"
    dest_tar = await dest.environ.which("tar")
    src_tar = await dest.environ.which("tar")

    await dest.task.chdir(await dest.ram.ptr(dest_path))
    await dest.task.inherit_fd(dest_fd).dup2(dest.stdin)
    await dest_fd.close()
    dest_child = await dest.exec(dest_tar.args("--extract"))

    await src.task.inherit_fd(src_fd).dup2(src.stdout)
    await src_fd.close()
    src_child = await src.exec(
        src_tar.args(
            "--create",
            "--to-stdout",
            "--hard-dereference",
            "--owner=0",
            "--group=0",
            "--mode=u+rw,uga+r",
            # suppress a tar warning about trying to compress an absolute path
            "--directory=/",
            *[Path(src_path).relative_to("/") for src_path in src_paths],
        ))
    await src_child.check()
    await dest_child.check()
Example #5
0
async def enter_nix_container(
    src: Process,
    nix: PackageClosure,
    dest: Process,
    dest_dir: Path,
) -> None:
    """Move `dest` into a container in `dest_dir`, deploying Nix inside.

    We can then use `deploy` to deploy other things into this container,
    which we can use from the `dest` process or any of its children.

    """
    # we want to use our own container Nix store, not the global one on the system
    if 'NIX_REMOTE' in dest.environ:
        del dest.environ['NIX_REMOTE']
    # copy the binaries over
    await copy_tree(src, nix.closure, dest, dest_dir)
    # enter the container
    await dest.unshare(CLONE.NEWNS | CLONE.NEWUSER)
    await dest.mount(dest_dir / "nix", "/nix", "none", MS.BIND, "")
    # init the database
    nix_store = Command(nix.path / 'bin/nix-store', ['nix-store'], {})
    await bootstrap_nix_database(src, nix_store, nix.closure, dest, nix_store)
    # add nix.path to PATH; TODO add a real API for this
    dest.environ.path.paths.append(Path(nix.path / 'bin'))
Example #6
0
    async def test_pgid(self) -> None:
        try:
            last_pid = await self.init.task.open(
                await self.init.ram.ptr(Path("/proc/sys/kernel/ns_last_pid")),
                O.WRONLY)
        except FileNotFoundError:
            raise unittest.SkipTest(
                "Requires /proc/sys/kernel/ns_last_pid, which requires CONFIG_CHECKPOINT_RESTORE"
            )

        pgldr = await self.init.fork()
        await pgldr.task.setpgid()
        pgflr = await self.init.fork()
        await pgflr.task.setpgid(pgldr.process.process)
        self.assertEqual(int(await pgflr.task.getpgid()), 2)
        await pgldr.exit(0)
        await pgldr.process.waitpid(W.EXITED)
        self.assertIsNotNone(pgldr.process.process.death_state)
        if pgldr.process.process.death_state is None:
            raise Exception  # for mypy
        self.assertEqual(pgldr.process.process.death_state.pid, 2)
        self.assertTrue(pgldr.process.process.death_state.died())
        self.assertEqual(int(await pgflr.task.getpgid()), 2)

        await self.init.spit(last_pid, b"1\n")

        with self.assertRaises(ProcessLookupError):
            await self.init.task._make_process(2).kill(SIG.NONE)
        pg_two = await self.init.fork()
        with self.assertRaises(ProcessLookupError):
            await self.init.task._make_process(2).kill(SIG.NONE)
        # Linux skips right over process 2, even though it's dead, because it's still used by the process group
        self.assertEqual(int(pg_two.task.process.near), 3)
Example #7
0
 async def test_setns_ownership(self) -> None:
     netnsfd = await self.thr.task.open(
         await self.thr.ram.ptr(Path("/proc/self/ns/net")), O.RDONLY)
     thread = await self.thr.fork()
     await thread.unshare_user()
     with self.assertRaises(PermissionError):
         # we can't setns to a namespace that we don't own, which is fairly lame
         await thread.task.setns(netnsfd, CLONE.NEWNET)
Example #8
0
    def as_proc_path(self) -> Path:
        """Return the /proc/{pid} path pointing to this process.

        This should be used with care, but it's sometimes useful.

        """
        pid = self.near.id
        return Path(f"/proc/{pid}")
Example #9
0
async def deploy_nix_bin(store: Store, dest: Thread) -> Store:
    "Deploy the Nix binaries from `store` to /nix through `dest`"
    # copy the binaries over
    await copy_tree(store.thread, store.nix.closure, dest, Path("/nix"))
    # init the database
    nix_store = Command(store.nix.path / 'bin/nix-store', ['nix-store'], {})
    await bootstrap_nix_database(store.thread, nix_store, store.nix.closure,
                                 dest, nix_store)
    return Store(dest, store.nix)
Example #10
0
 async def test_make_tun(self) -> None:
     tun_fd = await self.thr.task.open(
         await self.thr.ram.ptr(Path("/dev/net/tun")), O.RDWR)
     ptr = await self.thr.ram.ptr(Ifreq(b'tun0', flags=IFF_TUN))
     await tun_fd.ioctl(TUNSETIFF, ptr)
     sock = await self.thr.task.socket(AF.INET, SOCK.STREAM)
     await sock.ioctl(SIOCGIFINDEX, ptr)
     # this is the second interface in an empty netns
     self.assertEqual((await ptr.read()).ifindex, 2)
Example #11
0
    def as_proc_path(self) -> Path:
        """Return the /proc/{pid}/fd/{num} path pointing to this FD.

        This should be used with care, but it's sometimes useful for programs
        which accept paths instead of file descriptors.

        """
        pid = self.task.pid.near.id
        num = self.near.number
        return Path(f"/proc/{pid}/fd/{num}")
Example #12
0
File: un.py Project: gc-ss/rsyscall
    async def from_path(thr: Thread, path: t.Union[str,
                                                   os.PathLike]) -> SockaddrUn:
        """Turn this path into a SockaddrUn, hacking around the 108 byte limit on socket addresses.

        If the passed path is too long to fit in an address, this function will open the parent
        directory with O_PATH and return SockaddrUn("/proc/self/fd/n/name").

        """
        try:
            return SockaddrUn(os.fsencode(path))
        except PathTooLongError:
            ppath = Path(path)
            fd = await thr.task.open(await thr.ram.ptr(ppath.parent), O.PATH)
            return SockaddrUnProcFd(fd, ppath.name)
Example #13
0
    async def test_rtnetlink(self) -> None:
        netsock = await self.thr.task.socket(AF.NETLINK, SOCK.DGRAM,
                                             NETLINK.ROUTE)
        await netsock.bind(await self.thr.ram.ptr(SockaddrNl(0, RTMGRP.LINK)))

        tun_fd = await self.thr.task.open(
            await self.thr.ram.ptr(Path("/dev/net/tun")), O.RDWR)
        ptr = await self.thr.ram.ptr(Ifreq(b'tun0', flags=IFF_TUN))
        await tun_fd.ioctl(TUNSETIFF, ptr)
        sock = await self.thr.task.socket(AF.INET, SOCK.STREAM)
        await sock.ioctl(SIOCGIFINDEX, ptr)
        # this is the second interface in an empty netns
        self.assertEqual((await ptr.read()).ifindex, 2)

        valid, _ = await netsock.read(await self.thr.ram.malloc(bytes, 4096))
        batch = IPBatch()
        evs = batch.marshal.parse(await valid.read())
        self.assertEqual(len(evs), 1)
        self.assertEqual(evs[0]['event'], 'RTM_NEWLINK')
Example #14
0
 async def _mount(self, process: Process, path: Path) -> None:
     # TODO does a pid namespace kill a process in D-wait?
     # TODO we could make a better API here with the new mount API
     self.path = path
     # /dev/fuse doesn't seem to behave nicely when used with EPOLLET, so we'll just do blocking
     # IO on it in a dedicated process - we need the dedicated process anyway (see comment below
     # about private fd table) so it's not extra overhead.
     devfuse = await process.task.open(await process.ptr(Path("/dev/fuse")),
                                       O.RDWR)
     self.uid = await process.task.getuid()
     self.gid = await process.task.getgid()
     self.parent_process = process
     await process.mount(
         "ignored", self.path, "fuse", MS.NONE,
         f"fd={int(devfuse)},rootmode=40777,user_id={self.uid},group_id={self.gid}"
     )
     # We'll keep devfuse open *only* in the dedicated server process's private fd table, so that
     # other processes accessing the filesystem don't deadlock when we abort the FUSE server loop -
     # instead their syscalls will be aborted with ENOTCONN.
     self.process = await process.fork()
     self.devfuse = self.process.task.inherit_fd(devfuse)
     await devfuse.close()
     # Respond to FUSE init message to sanity-check things are set up right.
     self.buf = await self.process.malloc(FuseInList, FUSE_MIN_READ_BUFFER)
     [init] = await self.read()
     if not isinstance(init, FuseInitOp):
         raise Exception("oops, got non-init as first message", init)
     flags = init.msg.flags & ~(FUSE_INIT.EXPORT_SUPPORT)
     # /dev/fuse doesn't ever do partial writes, nice.
     await self.write(
         init.respond(
             FuseInitOut(major=init.msg.major,
                         minor=init.msg.minor,
                         max_readahead=init.msg.max_readahead,
                         flags=flags,
                         max_background=16,
                         congestion_threshold=16,
                         max_write=128,
                         time_gran=128)))
Example #15
0
    async def test_getdents(self) -> None:
        dirfd = await self.thr.task.open(await self.thr.ram.ptr(self.path),
                                         O.DIRECTORY)
        dent_buf = await self.thr.ram.malloc(DirentList, 4096)
        valid, rest = await dirfd.getdents(dent_buf)
        self.assertCountEqual([dirent.name for dirent in await valid.read()],
                              ['.', '..'])
        dent_buf = valid + rest

        text = b"Hello world!"
        name = await self.thr.ram.ptr(Path("hello"))

        write_fd = await dirfd.openat(name, O.WRONLY | O.CREAT)
        buf = await self.thr.ram.ptr(text)
        written, _ = await write_fd.write(buf)

        read_fd = await dirfd.openat(name, O.RDONLY)
        read, _ = await read_fd.read(written)
        self.assertEqual(await read.read(), text)

        await dirfd.lseek(0, SEEK.SET)
        valid, rest = await dirfd.getdents(dent_buf)
        self.assertCountEqual([dirent.name for dirent in await valid.read()],
                              ['.', '..', str(name.value)])
Example #16
0
            async def exec_server() -> None:
                """Accepts connections from the stub executables

                We just check the command and stat stdin/stdout/stderr, then exit the stub.

                """
                while True:
                    args, process = await stub_server.accept()
                    buf = await process.malloc(Stat)

                    async def stat(fd: FileDescriptor) -> Stat:
                        nonlocal buf
                        buf = await fd.fstat(buf)
                        return await buf.read()

                    name, *rest = args
                    commands.append(
                        CommandData(
                            args=[Path(name).name, *rest],
                            stdin=await stat(process.stdin),
                            stdout=await stat(process.stdout),
                            stderr=await stat(process.stderr),
                        ))
                    await process.exit(0)
Example #17
0
async def amain(argv: t.List[str]) -> None:
    # hmm I should write a more TFS/direct style argument parser for python
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subcommand', required=True)

    example_parser = subparsers.add_parser('example', help='Example')
    exec_parser = subparsers.add_parser(
        'exec', help="Exec the provided executable, no arguments allowed")
    exec_parser.add_argument(dest='executable')

    args = parser.parse_args(argv[1:])
    from rsyscall import local_process
    if args.subcommand == 'example':
        script = """ls; which ls
stat /
foo|bar
"""
        print("======================= running script =======================")
        print(script, end='')
        await symsh_main(local_process,
                         local_process.environ.sh.args("-c", script))
    elif args.subcommand == 'exec':
        await symsh_main(local_process,
                         Command(Path(args.executable), [args.executable], {}))
Example #18
0
async def rsyscall_exec(
    parent: Thread,
    child: ChildThread,
    executable: RsyscallServerExecutable,
) -> None:
    """exec rsyscall-server and repair the thread to continue working after the exec

    This is of fairly limited use except as a stress-test for our primitives.

    We need to know about our parent thread because we need to create a new futex child
    process to wait for the child calling exec. We can't have the child itself create this
    futex process because the whole point of the futex process is to monitor for the child
    calling exec or exit; see ChildSyscallInterface. That futex process can be a child of
    anyone, so technically `parent` doesn't have to be our parent, it just needs to
    currently share its fd table with us.

    For the new futex process, we need to use a robust futex, registered on the
    robust_list.  The robust_list is, unfortunately, the only truly robust way to get
    notified of a process calling exec. We use ctid elsewhere, but the kernel has an
    irritating check where it only does a futex wakeup on ctid if the process's memory
    space is shared. The kernel always does the robust_list wakeups, so we can rely on the
    robust list even when we're working with processes that don't share address space.

    """
    [(access_data_sock, passed_data_sock)] = await child.open_async_channels(1)
    # create this guy and pass him down to the new thread
    child_futex_memfd = await child.task.memfd_create(await child.ram.ptr(
        Path("child_robust_futex_list")))
    parent_futex_memfd = child_futex_memfd.for_task(parent.task)
    if isinstance(child.task.sysif, ChildSyscallInterface):
        syscall = child.task.sysif
    else:
        raise Exception("can only exec in ChildSyscallInterface sysifs, not",
                        child.task.sysif)
    # unshare files so we can unset cloexec on fds to inherit
    await child.unshare_files(going_to_exec=True)
    # unset cloexec on all the fds we want to copy to the new space
    for fd in child.task.fd_handles:
        await fd.fcntl(F.SETFD, 0)

    def encode(fd: FileDescriptor) -> bytes:
        return str(int(fd.near)).encode()

    #### call exec and set up the new task
    await child.exec(
        executable.command.args(
            encode(passed_data_sock),
            encode(syscall.infd),
            encode(syscall.outfd),
            *[encode(fd) for fd in child.task.fd_handles],
        ), [child.monitor.sigfd.signal_block])
    if len(syscall.rsyscall_connection.pending_responses) == 1:
        # remove execve from pending_responses, we're never going to get a response to it
        syscall.rsyscall_connection.pending_responses = []
    else:
        raise Exception(
            "syscall connection in bad state; " +
            "expected one pending response for execve, instead got",
            syscall.rsyscall_connection.pending_responses)
    # a new address space needs a new allocator and transport; we mutate the RAM so things
    # that have stored the RAM continue to work.
    child.ram.allocator = memory.AllocatorClient.make_allocator(child.task)
    child.ram.transport = SocketMemoryTransport(access_data_sock,
                                                passed_data_sock,
                                                child.ram.allocator)
    # rsyscall-server will write the symbol table to passed_data_sock, and we'll read it
    # from access_data sock to set up the symbol table for the new address space
    child.loader = NativeLoader.make_from_symbols(
        child.task, await AsyncReadBuffer(access_data_sock).read_cffi(
            'struct rsyscall_symbol_table'))

    #### make new futex process
    # The futex process we used before is dead now that we've exec'd. We need to make some
    # syscalls in the child to set up the new futex process. ChildSyscallInterface would
    # throw immediately on seeing the current dead futex_process, so we need to null it out.
    syscall.futex_process = None
    # We have to use a robust futex now for our futex_process, see docstring
    parent_futex_ptr, child_futex_ptr = await setup_shared_memory_robust_futex(
        parent, parent_futex_memfd, child, child_futex_memfd)
    syscall.futex_process = await launch_futex_monitor(parent.ram,
                                                       parent.loader,
                                                       parent.monitor,
                                                       parent_futex_ptr)
    # now we are alive and fully working again, we can be used for GC
    child.task._add_to_active_fd_table_tasks()
Example #19
0
 async def test_readlinkat_non_symlink(self) -> None:
     f = await self.thr.task.open(await self.thr.ram.ptr(Path(".")), O.PATH)
     empty_ptr = await self.thr.ram.ptr(EmptyPath())
     ptr = await self.thr.ram.malloc(Path, 4096)
     with self.assertRaises(FileNotFoundError):
         await f.readlinkat(empty_ptr, ptr)
Example #20
0
 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.path_cache: t.Dict[str, Path] = {}
Example #21
0
 def __init__(self, task: Task, paths: t.Sequence[str | Path]) -> None:
     self.task = task
     self.paths = [Path(path) for path in paths]
     self.fds: t.Dict[Path, t.Optional[FileDescriptor]] = {}
     self.path_cache: t.Dict[str, Path] = {}
Example #22
0
 def _load_without_registering(self, name: str) -> StorePath:
     dep = nixdeps.import_nixdep('rsyscall._nixdeps', name)
     path = Path(dep.path)
     closure = [Path(elem) for elem in dep.closure]
     return StorePath(path, closure)
Example #23
0
 def make_path_from_bytes(self, path: t.Union[str, bytes]) -> Path:
     return Path(os.fsdecode(path))
Example #24
0
 def as_proc_path(self) -> Path:
     pid = self.task.process.near.id
     num = self.near.number
     return Path(f"/proc/{pid}/fd/{num}")
Example #25
0
 def as_proc_self_path(self) -> Path:
     num = self.near.number
     return Path(f"/proc/self/fd/{num}")
Example #26
0
 async def test_readlink_proc(self) -> None:
     f = await self.thr.task.open(await self.thr.ram.ptr(Path(".")), O.PATH)
     path_ptr = await self.thr.ram.ptr(f.as_proc_self_path())
     ptr = await self.thr.ram.malloc(Path, 4096)
     await f.readlinkat(path_ptr, ptr)
Example #27
0
async def stdin_bootstrap(
        parent: Thread,
        bootstrap_command: Command,
) -> t.Tuple[AsyncChildProcess, Thread]:
    """Create a thread from running an arbitrary command which must run rsyscall-stdin-bootstrap

    bootstrap_command can be any arbitrary command, but it must eventually exec
    rsyscall-stdin-bootstrap, and pass down stdin when it does.

    We'll fork and exec bootstrap_command, passing down a socketpair for stdin, and try to
    bootstrap over the other end of the socketpair. Once rsyscall-stdin-bootstrap starts,
    it will respond to our bootstrap and we'll create a new thread.

    """
    #### fork and exec into the bootstrap command
    child = await parent.fork()
    # create the socketpair that will be used as stdin
    stdin_pair = await (await parent.task.socketpair(
        AF.UNIX, SOCK.STREAM, 0, await parent.ram.malloc(Socketpair))).read()
    parent_sock = stdin_pair.first
    child_sock = stdin_pair.second.move(child.task)
    # set up stdin with socketpair
    await child.unshare_files(going_to_exec=True)
    await child.stdin.replace_with(child_sock)
    # exec
    child_process = await child.exec(bootstrap_command)
    #### set up all the fds we'll want to pass over
    # the basic connections
    [(access_syscall_sock, passed_syscall_sock),
     (access_data_sock, passed_data_sock)] = await parent.open_async_channels(2)
    # memfd for setting up the futex
    futex_memfd = await parent.task.memfd_create(
        await parent.ram.ptr(Path("child_robust_futex_list")))
    # send the fds to the new process
    connection_fd, make_connection = await parent.connection.prep_fd_transfer()
    async def sendmsg_op(sem: RAM) -> WrittenPointer[SendMsghdr]:
        iovec = await sem.ptr(IovecList([await sem.malloc(bytes, 1)]))
        cmsgs = await sem.ptr(CmsgList([CmsgSCMRights([
            passed_syscall_sock, passed_data_sock, futex_memfd, connection_fd])]))
        return await sem.ptr(SendMsghdr(None, iovec, cmsgs))
    _, [] = await parent_sock.sendmsg(await parent.ram.perform_batch(sendmsg_op), SendmsgFlags.NONE)
    # close our reference to fds that only the new process needs
    await passed_syscall_sock.close()
    await passed_data_sock.close()
    # close the socketpair
    await parent_sock.close()
    #### read describe to get all the information we need from the new process
    describe_buf = AsyncReadBuffer(access_data_sock)
    describe_struct = await describe_buf.read_cffi('struct rsyscall_stdin_bootstrap')
    environ = await describe_buf.read_envp(describe_struct.envp_count)
    #### build the new task
    pid = describe_struct.pid
    fd_table = far.FDTable(pid)
    address_space = far.AddressSpace(pid)
    # we assume pid namespace is shared
    # TODO include namespace inode numbers numbers in describe
    # note: if we start dealing with namespace numbers then we need to
    # have a Kernel namespace which tells us which kernel we get those
    # numbers from.
    # oh hey we can conveniently dump the inode numbers with getdents!
    pidns = parent.task.pidns
    process = near.Process(pid)
    remote_syscall_fd = near.FileDescriptor(describe_struct.syscall_fd)
    syscall = NonChildSyscallInterface(SyscallConnection(access_syscall_sock, access_syscall_sock), process)
    base_task = Task(syscall, process, fd_table, address_space, pidns)
    handle_remote_syscall_fd = base_task.make_fd_handle(remote_syscall_fd)
    syscall.store_remote_side_handles(handle_remote_syscall_fd, handle_remote_syscall_fd)
    allocator = memory.AllocatorClient.make_allocator(base_task)
    # we assume our SignalMask is zero'd before being started, so we don't inherit it
    ram = RAM(base_task,
               SocketMemoryTransport(access_data_sock,
                                     base_task.make_fd_handle(near.FileDescriptor(describe_struct.data_fd)),
                                     allocator),
               allocator)
    # TODO I think I can maybe elide creating this epollcenter and instead inherit it or share it, maybe?
    epoller = await Epoller.make_root(ram, base_task)
    child_monitor = await ChildProcessMonitor.make(ram, base_task, epoller)
    connection = make_connection(base_task, ram,
                                 base_task.make_fd_handle(near.FileDescriptor(describe_struct.connecting_fd)))
    new_parent = Thread(
        task=base_task,
        ram=ram,
        connection=connection,
        loader=NativeLoader.make_from_symbols(base_task, describe_struct.symbols),
        epoller=epoller,
        child_monitor=child_monitor,
        environ=Environment(base_task, ram, environ),
        stdin=base_task.make_fd_handle(near.FileDescriptor(0)),
        stdout=base_task.make_fd_handle(near.FileDescriptor(1)),
        stderr=base_task.make_fd_handle(near.FileDescriptor(2)),
    )
    #### TODO set up futex I guess
    remote_futex_memfd = near.FileDescriptor(describe_struct.futex_memfd)
    return child_process, new_parent
Example #28
0
async def _setup_stub(
    thread: Thread,
    bootstrap_sock: FileDescriptor,
) -> t.Tuple[t.List[str], Thread]:
    "Setup a stub thread"
    [(access_syscall_sock, passed_syscall_sock),
     (access_data_sock, passed_data_sock)
     ] = await thread.open_async_channels(2)
    # memfd for setting up the futex
    futex_memfd = await thread.task.memfd_create(await thread.ram.ptr(
        Path("child_robust_futex_list")))
    # send the fds to the new process
    connection_fd, make_connection = await thread.connection.prep_fd_transfer()

    async def sendmsg_op(sem: RAM) -> WrittenPointer[SendMsghdr]:
        iovec = await sem.ptr(IovecList([await sem.malloc(bytes, 1)]))
        cmsgs = await sem.ptr(
            CmsgList([
                CmsgSCMRights([
                    passed_syscall_sock, passed_data_sock, futex_memfd,
                    connection_fd
                ])
            ]))
        return await sem.ptr(SendMsghdr(None, iovec, cmsgs))

    _, [] = await bootstrap_sock.sendmsg(
        await thread.ram.perform_batch(sendmsg_op), SendmsgFlags.NONE)
    # close our reference to fds that only the new process needs
    await passed_syscall_sock.invalidate()
    await passed_data_sock.invalidate()
    # close the socketpair
    await bootstrap_sock.invalidate()
    #### read describe to get all the information we need from the new process
    describe_buf = AsyncReadBuffer(access_data_sock)
    describe_struct = await describe_buf.read_cffi('struct rsyscall_unix_stub')
    argv_raw = await describe_buf.read_length_prefixed_array(
        describe_struct.argc)
    argv = [os.fsdecode(arg) for arg in argv_raw]
    environ = await describe_buf.read_envp(describe_struct.envp_count)
    #### build the new task
    pid = describe_struct.pid
    fd_table = handle.FDTable(pid)
    address_space = far.AddressSpace(pid)
    # we assume pid namespace is shared
    pidns = thread.task.pidns
    process = near.Process(pid)
    # we assume net namespace is shared - that's dubious...
    # we should make it possible to control the namespace sharing more, hmm.
    # TODO maybe the describe should contain the net namespace number? and we can store our own as well?
    # then we can automatically do it right
    base_task = Task(process, fd_table, address_space, pidns)
    remote_syscall_fd = base_task.make_fd_handle(
        near.FileDescriptor(describe_struct.syscall_fd))
    base_task.sysif = SyscallConnection(
        logger.getChild(str(process)),
        access_syscall_sock,
        access_syscall_sock,
        remote_syscall_fd,
        remote_syscall_fd,
    )
    allocator = memory.AllocatorClient.make_allocator(base_task)
    base_task.sigmask = Sigset(
        {SIG(bit)
         for bit in rsyscall.struct.bits(describe_struct.sigmask)})
    ram = RAM(
        base_task,
        SocketMemoryTransport(
            access_data_sock,
            base_task.make_fd_handle(
                near.FileDescriptor(describe_struct.data_fd))), allocator)
    # TODO I think I can maybe elide creating this epollcenter and instead inherit it or share it, maybe?
    # I guess I need to write out the set too in describe
    epoller = await Epoller.make_root(ram, base_task)
    child_monitor = await ChildProcessMonitor.make(ram, base_task, epoller)
    connection = make_connection(
        base_task, ram,
        base_task.make_fd_handle(
            near.FileDescriptor(describe_struct.connecting_fd)))
    new_thread = Thread(
        task=base_task,
        ram=ram,
        connection=connection,
        loader=NativeLoader.make_from_symbols(base_task,
                                              describe_struct.symbols),
        epoller=epoller,
        child_monitor=child_monitor,
        environ=Environment.make_from_environ(base_task, ram, environ),
        stdin=base_task.make_fd_handle(near.FileDescriptor(0)),
        stdout=base_task.make_fd_handle(near.FileDescriptor(1)),
        stderr=base_task.make_fd_handle(near.FileDescriptor(2)),
    )
    #### TODO set up futex I guess
    remote_futex_memfd = near.FileDescriptor(describe_struct.futex_memfd)
    return argv, new_thread