From e7f8500644717d3e4049cb981472e376c8984055 Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Tue, 11 Jun 2024 17:47:16 +0200 Subject: unschroot: add a pid 1 that reaps zombies While sbuild --chroot-mode=unshare opted for installing dumb-init, we'll keep the environment minimal and have a dumb-init written in perl-base, which happens to be essential still. Unfortunately, we cannot wait for our target process from our main process as the target process is a child of our perl init. Therefore our perl init must forward the exit code. --- examples/unschroot.py | 61 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 13 deletions(-) (limited to 'examples/unschroot.py') diff --git a/examples/unschroot.py b/examples/unschroot.py index 3e3cc3a..682b5b0 100755 --- a/examples/unschroot.py +++ b/examples/unschroot.py @@ -183,6 +183,23 @@ def do_begin_session(args: argparse.Namespace) -> None: sys.exit(ret) +def exec_perl_dumb_init(pid: int) -> typing.NoReturn: + """Roughly impement dumb-init in perl: Wait for all children until we + receive an exit from the given pid and forward its status. + """ + os.execlp( + "perl", + "perl", + "-e", + "$r=255<<8;" # exit 255 when we run out of children + "do{" + "$p=wait;" + f"$r=$?,$p=0 if $p=={pid};" + "}while($p>0);" + "exit(0<$r<256?128|$r:$r>>8);", # sig -> 128+sig; exit -> exit + ) + + def do_run_session(args: argparse.Namespace) -> None: """Run an existing session""" session = Chroot.searchsession(args.chroot) @@ -215,7 +232,7 @@ def do_run_session(args: argparse.Namespace) -> None: linuxnamespaces.populate_dev("/", ".") linuxnamespaces.pivot_root(".", ".") linuxnamespaces.umount(".", linuxnamespaces.UmountFlags.DETACH) - os.chdir(args.directory or "/") + os.chdir("/") if args.user.isdigit(): spw = pwd.getpwuid(int(args.user)) else: @@ -223,19 +240,37 @@ def do_run_session(args: argparse.Namespace) -> None: supplementary = [ sgr.gr_gid for sgr in grp.getgrall() if spw.pw_name in sgr.gr_mem ] - os.setgroups(supplementary) - os.setgid(spw.pw_gid) - os.setuid(spw.pw_uid) - if not args.command: - args.command.append("bash") + childsock.recv(1) - linuxnamespaces.prctl_set_pdeathsig(signal.SIGTERM) - if "PATH" not in os.environ: - if spw.pw_uid == 0: - os.environ["PATH"] = "/usr/sbin:/sbin:/usr/bin:/bin" - else: - os.environ["PATH"] = "/usr/bin:/bin" - os.execvp(args.command[0], args.command) + childsock.close() + rfd, wfd = linuxnamespaces.FileDescriptor.pipe(inheritable=False) + pid = os.fork() + if pid == 0: + wfd.close() + if args.directory: + os.chdir(args.directory) + os.setgroups(supplementary) + os.setgid(spw.pw_gid) + os.setuid(spw.pw_uid) + if "PATH" not in os.environ: + if spw.pw_uid == 0: + os.environ["PATH"] = "/usr/sbin:/sbin:/usr/bin:/bin" + else: + os.environ["PATH"] = "/usr/bin:/bin" + if not args.command: + args.command.append("bash") + # Wait until Python has handed off to Perl. + os.read(rfd, 1) + os.execvp(args.command[0], args.command) + else: + rfd.close() + linuxnamespaces.prctl_set_pdeathsig(signal.SIGKILL) + os.close(0) + # It is important that we now exec to get rid of our previous + # execution context that carries pieces such as memory maps from + # different namespaces that could allow escalating privileges. The + # exec will close wfd and allow the target process to exec. + exec_perl_dumb_init(pid) childsock.close() mainsock.recv(1) linuxnamespaces.newidmaps(pid, [uidmap], [gidmap]) -- cgit v1.2.3