#!/usr/bin/python3 # Copyright 2024 Helmut Grohne # SPDX-License-Identifier: GPL-3 """Mount a given ext4 filesystem image inside a user and mount namespace using unprivileged fuse2fs and chroot into the mouned filesystem. This requires a fuse package with support for /dev/fd/N. Either with fuse2 via https://bugs.debian.org/1055222 or when e2fsprogs has been ported to fuse3. """ 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: fsimage = pathlib.Path(sys.argv[1]) assert fsimage.exists() 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) os.execlp("fuse2fs", "fuse2fs", str(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() linuxnamespaces.mount( str(fsimage), "/mnt", "fuse.ext4", 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) linuxnamespaces.pivot_root(".", ".") linuxnamespaces.umount(".", linuxnamespaces.UmountFlags.DETACH) os.close(fusefd) os.execlp(os.environ["SHELL"], os.environ["SHELL"]) if __name__ == "__main__": main()