summaryrefslogtreecommitdiff
path: root/examples/unschroot.py
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2024-06-11 17:47:16 +0200
committerHelmut Grohne <helmut@subdivi.de>2024-06-11 18:09:04 +0200
commite7f8500644717d3e4049cb981472e376c8984055 (patch)
treed7116aa2f5c5d183f6884e3812402c0d8d671f6b /examples/unschroot.py
parenta457a6c7c8fa17e0cec137c68a6a090edeb23e57 (diff)
downloadpython-linuxnamespaces-e7f8500644717d3e4049cb981472e376c8984055.tar.gz
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.
Diffstat (limited to 'examples/unschroot.py')
-rwxr-xr-xexamples/unschroot.py61
1 files changed, 48 insertions, 13 deletions
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])