From 00841e88b74864fd1c287ff27c1dd9387902dcf9 Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Wed, 21 Feb 2024 15:45:23 +0100 Subject: examples/chrootfuse2fs.py: generalize to allow squashfs --- examples/chrootfuse.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100755 examples/chrootfuse.py (limited to 'examples/chrootfuse.py') diff --git a/examples/chrootfuse.py b/examples/chrootfuse.py new file mode 100755 index 0000000..d40e1a0 --- /dev/null +++ b/examples/chrootfuse.py @@ -0,0 +1,100 @@ +#!/usr/bin/python3 +# Copyright 2024 Helmut Grohne +# SPDX-License-Identifier: GPL-3 + +"""Mount a given filesystem image inside a user and mount namespace using +an unprivileged fuse driver and chroot into the mouned filesystem. Supported +drivers are fuse2fs and squashfuse. + +This requires a fuse package with support for /dev/fd/N. Either with fuse2 via +https://bugs.debian.org/1055222 or fuse3. On Debian, squashfuse uses fuse3. +""" + +import argparse +import os +import pathlib +import socket +import sys + +if __file__.split("/")[-2:-1] == ["examples"]: + sys.path.insert(0, "/".join(__file__.split("/")[:-2])) + +import linuxnamespaces + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--fstype", choices=["ext4", "squashfs"]) + parser.add_argument("fsimage", type=pathlib.Path) + args = parser.parse_args() + assert args.fsimage.exists() + if args.fstype is None: + args.fstype = args.fsimage.suffix.removeprefix(".") + if args.fstype not in ("ext4", "squashfs"): + print("Cannot determine filesystem type for image.") + sys.exit(1) + uidmap = linuxnamespaces.IDAllocation.loadsubid("uid").allocatemap(65536) + gidmap = linuxnamespaces.IDAllocation.loadsubid("gid").allocatemap(65536) + mainpid = os.getpid() + mainsock, childsock = socket.socketpair() + @linuxnamespaces.run_in_fork + def setup() -> None: + mainsock.close() + linuxnamespaces.newidmaps(mainpid, [uidmap], [gidmap]) + childsock.send(b"\0") + _, fds, _, _ = socket.recv_fds(childsock, 1, 1, 0) + childsock.close() + os.set_inheritable(fds[0], True) + driver = { + "ext4": "fuse2fs", + "squashfs": "squashfuse", + }[args.fstype] + os.execlp(driver, driver, str(args.fsimage), "/dev/fd/%d" % fds[0]) + linuxnamespaces.unshare( + linuxnamespaces.CloneFlags.NEWUSER | linuxnamespaces.CloneFlags.NEWNS + ) + setup.start() + mainsock.recv(1) + os.setreuid(0, 0) + os.setregid(0, 0) + fusefd = os.open("/dev/fuse", os.O_RDWR) + socket.send_fds(mainsock, [b"\0"], [fusefd]) + mainsock.close() + readonly = { + "ext4": False, + "squashfs": True, + }[args.fstype] + linuxnamespaces.mount( + str(args.fsimage), + "/mnt", + "fuse." + args.fstype, + linuxnamespaces.MountFlags.RDONLY + if readonly + else linuxnamespaces.MountFlags.NONE, + "fd=%d,rootmode=040755,user_id=0,group_id=0,allow_other" % fusefd, + ) + os.chdir("/mnt") + linuxnamespaces.bind_mount("/proc", "proc", recursive=True) + linuxnamespaces.bind_mount("/sys", "sys", recursive=True) + linuxnamespaces.populate_dev("/", ".", pidns=False, tun=False) + if readonly: + linuxnamespaces.mount( + "tmpfs", "tmp", "tmpfs", linuxnamespaces.MountFlags.NODEV + ) + linuxnamespaces.mount( + "tmpfs", + "run", + "tmpfs", + linuxnamespaces.MountFlags.NODEV + | linuxnamespaces.MountFlags.NOSUID + | linuxnamespaces.MountFlags.NOEXEC, + "mode=0755", + ) + linuxnamespaces.pivot_root(".", ".") + linuxnamespaces.umount(".", linuxnamespaces.UmountFlags.DETACH) + os.close(fusefd) + os.execlp(os.environ["SHELL"], os.environ["SHELL"]) + + +if __name__ == "__main__": + main() -- cgit v1.2.3