From 0b69be3638a856c986e873fe0fbe8facf874f39f Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Wed, 13 Aug 2025 11:13:02 +0200 Subject: examples/chroottar.py: new options --bind and --same-uid This allows bind mounting a home directory into a chroot and accessing it inside. With --same-uid, the need for id translation is lifted and it can be accessed. --- examples/chroottar.py | 63 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 7 deletions(-) (limited to 'examples') diff --git a/examples/chroottar.py b/examples/chroottar.py index ec76813..1fb07be 100755 --- a/examples/chroottar.py +++ b/examples/chroottar.py @@ -34,6 +34,17 @@ def main() -> None: action="store_true", help="save and replace the tarball at the end of the session", ) + parser.add_argument( + "--same-uid", + action="store_true", + help="map the current user to itself in the namespace", + ) + parser.add_argument( + "--bind", + action="append", + help="bind mount the given location after extraction", + default=[], + ) parser.add_argument( "basetar", type=pathlib.Path, @@ -47,8 +58,43 @@ def main() -> None: ) args = parser.parse_args() assert args.basetar.exists() - uidmap = linuxnamespaces.IDAllocation.loadsubid("uid").allocatemap(65536) - gidmap = linuxnamespaces.IDAllocation.loadsubid("gid").allocatemap(65536) + myuid = os.getuid() + mygid = os.getgid() + uidrange = linuxnamespaces.IDAllocation.loadsubid("uid").allocatemap(65536) + gidrange = linuxnamespaces.IDAllocation.loadsubid("gid").allocatemap(65536) + if args.same_uid: + uidmaps = [ + uidrange[:myuid], + linuxnamespaces.IDMapping(uidrange.innerstart + myuid, myuid, 1), + uidrange[myuid + 1:], + ] + gidmaps = [ + gidrange[:mygid], + linuxnamespaces.IDMapping(gidrange.innerstart + mygid, mygid, 1), + gidrange[mygid + 1:], + ] + else: + uidmaps = [uidrange] + gidmaps = [gidrange] + bindmounts = [] + for conf in args.bind: + confparts = conf.split(":") + if not os.path.exists(confparts[0]): + raise ValueError( + f"cannot bind mount {confparts[0]}: does not exist" + ) + if len(confparts) > 2: + raise ValueError(f"bind mount {conf} not understood") + if len(confparts) < 2: + confparts.append(confparts[0]) + bindmounts.append( + ( + os.path.abspath(confparts[0]), + os.path.normpath( + os.path.join("/", confparts[1]) + ).removeprefix("/"), + ), + ) with tempfile.TemporaryDirectory() as tdir: parentsock, childsock = socket.socketpair() pid = os.fork() @@ -84,6 +130,9 @@ def main() -> None: linuxnamespaces.bind_mount("/proc", "proc", recursive=True) linuxnamespaces.bind_mount("/sys", "sys", recursive=True) linuxnamespaces.populate_dev("/", ".", pts="host", tun=False) + for source, target in bindmounts: + os.makedirs(target, exist_ok=True) + linuxnamespaces.bind_mount(source, target, recursive=True) linuxnamespaces.pivot_root(".", ".") linuxnamespaces.umount(".", linuxnamespaces.UmountFlags.DETACH) if args.command: @@ -94,14 +143,14 @@ def main() -> None: childsock.close() comptype = parentsock.recv(10).split(b"\0", 1)[0].decode("ascii") - linuxnamespaces.newidmaps(pid, [uidmap], [gidmap]) + linuxnamespaces.newidmaps(pid, uidmaps, gidmaps) # We still had to be in the initial namespace to call newidmaps and # now we transition to a namespace that can access both the container # and the files of the invoking user. - linuxnamespaces.unshare_user_idmap( - [uidmap, linuxnamespaces.IDMapping(65536, os.getuid(), 1)], - [gidmap, linuxnamespaces.IDMapping(65536, os.getgid(), 1)], - ) + if not args.same_uid: + uidmaps.append(linuxnamespaces.IDMapping(65536, myuid, 1)) + gidmaps.append(linuxnamespaces.IDMapping(65536, mygid, 1)) + linuxnamespaces.unshare_user_idmap(uidmaps, gidmaps) os.chown(tdir, 0, 0) os.chmod(tdir, 0o755) parentsock.send(b"\0") -- cgit v1.2.3