From 823b6754d68b01a6d30431411506cc9084560e68 Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Sat, 28 Jun 2025 09:11:42 +0200 Subject: change/improve populate_dev API populate_dev may be used before unsharing a pid namespace with the intention of unsharing it. Then, /dev/pts should not be mounted and instead that mount needs to happen inside the newly created pid namespace. To allow for this usage, rename the pidns argument to pts and turn it into a literal. It may also be desired to have a /dev without pts, so add that option as well. It's a breaking change, but it does add clarity. --- examples/chrootfuse.py | 2 +- examples/chroottar.py | 2 +- examples/userchroot.py | 2 +- linuxnamespaces/__init__.py | 16 +++++++++++----- tests/test_simple.py | 2 +- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/chrootfuse.py b/examples/chrootfuse.py index 7e79a2e..d8ca965 100755 --- a/examples/chrootfuse.py +++ b/examples/chrootfuse.py @@ -85,7 +85,7 @@ def main() -> None: 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.populate_dev("/", ".", pts="host", tun=False) if readonly: linuxnamespaces.mount( "tmpfs", "tmp", "tmpfs", linuxnamespaces.MountFlags.NODEV diff --git a/examples/chroottar.py b/examples/chroottar.py index 42c09ef..cf0f87e 100755 --- a/examples/chroottar.py +++ b/examples/chroottar.py @@ -83,7 +83,7 @@ def main() -> None: 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.populate_dev("/", ".", pts="host", tun=False) linuxnamespaces.pivot_root(".", ".") linuxnamespaces.umount(".", linuxnamespaces.UmountFlags.DETACH) if args.command: diff --git a/examples/userchroot.py b/examples/userchroot.py index 2caea33..4006dc6 100755 --- a/examples/userchroot.py +++ b/examples/userchroot.py @@ -39,7 +39,7 @@ def main() -> None: linuxnamespaces.bind_mount(chrootdir, "/mnt", recursive=True) linuxnamespaces.bind_mount("/proc", "/mnt/proc", recursive=True) linuxnamespaces.bind_mount("/sys", "/mnt/sys", recursive=True) - linuxnamespaces.populate_dev("/", "/mnt", pidns=False) + linuxnamespaces.populate_dev("/", "/mnt", pts="host") os.chdir("/mnt") linuxnamespaces.pivot_root(".", ".") linuxnamespaces.umount(".", linuxnamespaces.UmountFlags.DETACH) diff --git a/linuxnamespaces/__init__.py b/linuxnamespaces/__init__.py index a1f790e..83358b6 100644 --- a/linuxnamespaces/__init__.py +++ b/linuxnamespaces/__init__.py @@ -245,7 +245,7 @@ def populate_dev( newroot: PathConvertible, *, fuse: bool = True, - pidns: bool = True, + pts: typing.Literal["defer", "host", "new", "absent"] = "new", tun: bool = True, ) -> None: """Mount a tmpfs to the dev directory beneath newroot and populate it with @@ -255,6 +255,12 @@ def populate_dev( Even though a CAP_SYS_ADMIN-enabled process can umount components of the /dev hierarchy, they they cannot gain privileges in doing so as no hierarchies are restricted via tmpfs mounts or read-only bind mounts. + + The /dev/fuse and /dev/net/tun devices are optional and can be enabled or + disabled as desired. /dev/pts (and /dev/ptmx) can be shared with the host + or mounted as a new instance. Since a PID namespace is usually required for + mounting a new instance, it can also be deferred to a later manual mount. + If not desired, it can be left absent. """ origdev = AtLocation(origroot) / "dev" newdev = AtLocation(newroot) / "dev" @@ -278,9 +284,7 @@ def populate_dev( bind_mounts["fuse"] = exitstack.enter_context( open_tree(origdev / "fuse", OpenTreeFlags.CLONE) ) - if pidns: - symlinks["ptmx"] = "pts/ptmx" - else: + if pts == "host": bind_mounts["pts"] = exitstack.enter_context( open_tree( origdev / "pts", @@ -291,6 +295,8 @@ def populate_dev( bind_mounts["ptmx"] = exitstack.enter_context( open_tree(origdev / "ptmx", OpenTreeFlags.CLONE) ) + elif pts != "absent": + symlinks["ptmx"] = "pts/ptmx" if tun: directories.add("net") files.add("net/tun") @@ -320,7 +326,7 @@ def populate_dev( (newdev / fn).mknod(stat.S_IFREG) for fn, target in symlinks.items(): (newdev / fn).symlink_to(target) - if pidns: + if pts == "new": mount( "devpts", newdev / "pts", diff --git a/tests/test_simple.py b/tests/test_simple.py index 38a253d..114b922 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -239,7 +239,7 @@ class UnshareTest(unittest.TestCase): ) linuxnamespaces.mount("tmpfs", "/mnt", "tmpfs", data="mode=0755") os.mkdir("/mnt/dev") - linuxnamespaces.populate_dev("/", "/mnt", pidns=False) + linuxnamespaces.populate_dev("/", "/mnt", pts="host") self.assertTrue(os.access("/mnt/dev/null", os.W_OK)) pathlib.Path("/mnt/dev/null").write_text("") -- cgit v1.2.3