]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Use more directories of sandbox trees in the sandbox
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 6 Sep 2024 11:53:23 +0000 (13:53 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 9 Sep 2024 16:15:23 +0000 (18:15 +0200)
Previously, we only picked up /usr and /etc from the sandbox trees.
Let's make this more generic and pick up a bunch of extra directories
as well. To avoid any changes persisting outside of the sandbox, let's
use overlayfs with a temporary writable directory as the upperdir of the
overlayfs (and make sure we use a tmpfs as the upperdir for /tmp and /run).

mkosi/__init__.py
mkosi/config.py
mkosi/context.py
mkosi/run.py

index 5bc54482283cd9f6c405283b760af089981fbd87..11c19ac9fe61b9c18465f8f9b93e6b19956519b8 100644 (file)
@@ -573,7 +573,7 @@ def run_sync_scripts(config: Config) -> None:
                         binary=None,
                         network=True,
                         options=options,
-                        sandbox_tree=Path(sandbox_tree),
+                        overlay=Path(sandbox_tree),
                     ),
                 )
 
index 07022bce270096297e0c9871bbe0d07c155a675f..3fa839ed0875bb78b3f6ec9d2c587ab5bfb6062f 100644 (file)
@@ -1773,7 +1773,7 @@ class Config:
         relaxed: bool = False,
         tools: bool = True,
         scripts: Optional[Path] = None,
-        sandbox_tree: Optional[Path] = None,
+        overlay: Optional[Path] = None,
         options: Sequence[PathString] = (),
         setup: Sequence[PathString] = (),
     ) -> AbstractContextManager[list[PathString]]:
@@ -1792,22 +1792,13 @@ class Config:
             tools = False
             opt += flatten(("--ro-bind", d, d) for d in self.extra_search_paths if not relaxed)
 
-        if sandbox_tree:
-            opt += [
-                # This mount is writable so we can create extra directories or symlinks inside of it as needed.
-                # This isn't a problem as the sandbox tree directory is created by mkosi and thrown away when the
-                # build finishes.
-                *(["--bind", str(p), "/etc"] if (p := sandbox_tree / "etc").exists() else []),
-                *(["--bind", str(p), "/var/log"] if (p := sandbox_tree / "var/log").exists() else []),
-            ]
-
         return sandbox_cmd(
             network=network,
             devices=devices,
             relaxed=relaxed,
             scripts=scripts,
             tools=self.tools() if tools else Path("/"),
-            usroverlaydirs=[sandbox_tree / "usr"] if sandbox_tree and (sandbox_tree / "usr").exists() else [],
+            overlay=overlay,
             options=opt,
             setup=setup,
         )
index 914228ae14f98d73d34b284eb89718bfd7a877e3..a6caed275968c5a51d3c9b4ea0e7685a3db39782 100644 (file)
@@ -74,6 +74,6 @@ class Context:
             network=network,
             devices=devices,
             scripts=scripts,
-            sandbox_tree=self.sandbox_tree,
+            overlay=self.sandbox_tree,
             options=options,
         )
index 8c46e93b05ac6056011aa3ea0c9ed3bd7529ccf4..fd660670f99ab776f599c8735541e08e6611c15e 100644 (file)
@@ -24,7 +24,7 @@ from typing import Any, Callable, NoReturn, Optional, Protocol
 
 import mkosi.sandbox
 from mkosi.log import ARG_DEBUG, ARG_DEBUG_SHELL, die
-from mkosi.sandbox import joinpath
+from mkosi.sandbox import joinpath, umask
 from mkosi.types import _FILE, CompletedProcess, PathString, Popen
 from mkosi.util import flatten, one_zero
 
@@ -424,18 +424,23 @@ def network_options(*, network: bool) -> list[PathString]:
 
 
 @contextlib.contextmanager
-def vartmpdir(condition: bool = True) -> Iterator[Optional[Path]]:
-    if not condition:
-        yield None
-        return
-
+def vartmpdir() -> Iterator[Path]:
     # We want to use an empty subdirectory in the host's temporary directory as the sandbox's /var/tmp.
     d = Path(os.getenv("TMPDIR", "/var/tmp")) / f"mkosi-var-tmp-{uuid.uuid4().hex[:16]}"
-    d.mkdir(mode=0o1777)
+    d.mkdir()
 
     try:
         yield d
     finally:
+        # A directory that's used as an overlayfs workdir will contain a "work" subdirectory after the overlayfs is
+        # unmounted. This "work" subdirectory will have permissions 000 and as such can't be opened or searched unless
+        # the user has the CAP_DAC_OVERRIDE capability. shutil.rmtree() will try to search the "work" subdirectory to
+        # remove anything in it which will fail with a permission error. To circumvent this, let's delete the "work"
+        # subdirectory first and then invoke shutil.rmtree(). Deleting the subdirectory is not a problem because
+        # deleting a subdirectory depends on the permissions of the parent directory and not the directory itself.
+        if (d / "work").exists():
+            (d / "work").rmdir()
+
         shutil.rmtree(d)
 
 
@@ -447,10 +452,12 @@ def sandbox_cmd(
     scripts: Optional[Path] = None,
     tools: Path = Path("/"),
     relaxed: bool = False,
-    usroverlaydirs: Sequence[PathString] = (),
+    overlay: Optional[Path] = None,
     options: Sequence[PathString] = (),
     setup: Sequence[PathString] = (),
 ) -> Iterator[list[PathString]]:
+    assert not (overlay and relaxed)
+
     cmdline: list[PathString] = [
         *setup,
         sys.executable, "-SI", mkosi.sandbox.__file__,
@@ -463,13 +470,12 @@ def sandbox_cmd(
         "--ro-bind", Path(mkosi.sandbox.__file__), "/sandbox.py",
     ]
 
-    if usroverlaydirs:
-        cmdline += ["--overlay-lowerdir", tools / "usr"]
-
-        for d in usroverlaydirs:
-            cmdline += ["--overlay-lowerdir", d]
-
-        cmdline += ["--overlay", "/usr"]
+    if overlay and (overlay / "usr").exists():
+        cmdline += [
+            "--overlay-lowerdir", tools / "usr"
+            "--overlay-lowerdir", overlay / "usr",
+            "--overlay", "/usr",
+        ]
     else:
         cmdline += ["--ro-bind", tools / "usr", "/usr"]
 
@@ -515,7 +521,7 @@ def sandbox_cmd(
         if d and not any(Path(d).is_relative_to(dir) for dir in (*dirs, "/usr", "/nix", "/tmp")):
             cmdline += ["--bind", d, d]
     else:
-        cmdline += ["--dir", "/tmp", "--dir", "/var/tmp", "--unshare-ipc"]
+        cmdline += ["--dir", "/var/tmp", "--unshare-ipc"]
 
         if devices:
             cmdline += ["--bind", "/sys", "/sys", "--bind", "/dev", "/dev"]
@@ -527,16 +533,44 @@ def sandbox_cmd(
 
     path = "/usr/bin:/usr/sbin" if tools != Path("/") else os.environ["PATH"]
 
-    cmdline += ["--setenv", "PATH", f"/scripts:{path}", *options]
+    cmdline += ["--setenv", "PATH", f"/scripts:{path}"]
 
     if scripts:
         cmdline += ["--ro-bind", scripts, "/scripts"]
 
-    with vartmpdir(condition=not relaxed) as dir:
-        if dir:
-            cmdline += ["--bind", dir, "/var/tmp"]
+    with contextlib.ExitStack() as stack:
+        tmp: Optional[Path]
 
-        yield [*cmdline, "--"]
+        if not overlay and not relaxed:
+            tmp = stack.enter_context(vartmpdir())
+            yield [*cmdline, "--bind", tmp, "/var/tmp", *options, "--"]
+            return
+
+        for d in ("etc", "opt", "srv", "media", "mnt", "var", "run", "tmp"):
+            tmp = None
+            if d not in ("run", "tmp"):
+                with umask(~0o755):
+                    tmp = stack.enter_context(vartmpdir())
+
+            if overlay and (overlay / d).exists():
+                work = None
+                if tmp:
+                    with umask(~0o755):
+                        work = stack.enter_context(vartmpdir())
+
+                cmdline += [
+                    "--overlay-lowerdir", overlay / d,
+                    "--overlay-upperdir", tmp or "tmpfs",
+                    *(["--overlay-workdir", str(work)] if work else []),
+                    "--overlay", Path("/") / d,
+                ]
+            elif not relaxed:
+                if tmp:
+                    cmdline += ["--bind", tmp, Path("/") / d]
+                else:
+                    cmdline += ["--tmpfs", Path("/") / d]
+
+        yield [*cmdline, *options, "--"]
 
 
 def apivfs_options(*, root: Path = Path("/buildroot")) -> list[PathString]:
@@ -590,10 +624,7 @@ def chroot_cmd(
         cmdline += ["--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf"]
 
     with vartmpdir() as dir:
-        if dir:
-            cmdline += ["--bind", dir, "/var/tmp"]
-
-        yield [*cmdline, *options, "--"]
+        yield [*cmdline, "--bind", dir, "/var/tmp", *options, "--"]
 
 
 def finalize_interpreter(tools: bool) -> str: