Skip to content

Commit

Permalink
Merge pull request #2253 from DaanDeMeyer/fix
Browse files Browse the repository at this point in the history
More sandboxing fixes
  • Loading branch information
DaanDeMeyer authored Jan 8, 2024
2 parents e8ee853 + e88394f commit bc2b6cc
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 94 deletions.
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## v20

- The current working directory is not mounted unconditionally to
`/work/src` anymore. Instead, the default value for `BuildSources=`
now mounts the current working directory to `/work/src`. This means
that the current working directory is no longer implicitly included
when `BuildSources=` is explicitly configured.
- Assigning the empty string to a setting that takes a list of values
now overrides any configured default value as well.
- The github action does not build and install systemd from source
anymore. Instead, `ToolsTree=default` can be used to make sure a
recent version of systemd is used to do the image build.
Expand Down
125 changes: 62 additions & 63 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,14 @@ def finalize_source_mounts(config: Config) -> Iterator[list[PathString]]:
mounts = [
(stack.enter_context(mount_overlay([source])) if config.build_sources_ephemeral else source, target)
for source, target
in [(Path.cwd(), Path.cwd())] + [t.with_prefix(Path.cwd()) for t in config.build_sources]
in (t.with_prefix(Path("/work/src")) for t in config.build_sources)
]

yield flatten(["--bind", src, target] for src, target in sorted(set(mounts), key=lambda s: s[1]))
options: list[PathString] = ["--dir", "/work/src"]
for src, target in sorted(set(mounts), key=lambda s: s[1]):
options += ["--bind", src, target]

yield options


@contextlib.contextmanager
Expand Down Expand Up @@ -416,7 +420,7 @@ def run_prepare_scripts(context: Context, build: bool) -> None:
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
SCRIPT="/work/prepare",
SRCDIR=str(Path.cwd()),
SRCDIR="/work/src",
WITH_DOCS=one_zero(context.config.with_docs),
WITH_NETWORK=one_zero(context.config.with_network),
WITH_TESTS=one_zero(context.config.with_tests),
Expand All @@ -440,11 +444,10 @@ def run_prepare_scripts(context: Context, build: bool) -> None:
resolve=True,
tools=context.config.tools(),
options=[
"--bind", script, "/work/prepare",
"--bind", Path.cwd(), "/work/src",
"--bind", cd, "/work/scripts",
"--bind", "/work/prepare", "/work/prepare",
"--bind", "/work/src", "/work/src",
"--bind", "/work/scripts", "/work/scripts",
"--chdir", "/work/src",
"--setenv", "SRCDIR", "/work/src",
"--setenv", "BUILDROOT", "/",
],
)
Expand All @@ -458,18 +461,21 @@ def run_prepare_scripts(context: Context, build: bool) -> None:

with complete_step(step_msg.format(script)):
run(
["/work/prepare" if script.suffix == ".chroot" else script, arg],
["/work/prepare", arg],
env=env | context.config.environment,
stdin=sys.stdin,
sandbox=context.sandbox(
network=True,
options=sources + [
"--ro-bind", script, script,
"--ro-bind", cd, cd,
"--bind", context.root, context.root,
# If the cache dir is /var and the workspace directory is /var/tmp (e.g. in mkosi-initrd),
# then all the files we mount here might be located in the configured cache directory, so
# we have to mount the cache directory first to not override all the other mounts.
"--bind", context.cache_dir, context.cache_dir,
"--ro-bind", script, "/work/prepare",
"--ro-bind", cd, "/work/scripts",
"--bind", context.root, context.root,
*finalize_crypto_mounts(tools=context.config.tools()),
"--chdir", Path.cwd(),
"--chdir", "/work/src",
],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
Expand All @@ -483,24 +489,24 @@ def run_build_scripts(context: Context) -> None:
env = dict(
ARCHITECTURE=str(context.config.architecture),
BUILDROOT=str(context.root),
DESTDIR="/work/dest",
CHROOT_DESTDIR="/work/dest",
OUTPUTDIR="/work/out",
CHROOT_OUTPUTDIR="/work/out",
CHROOT_SCRIPT="/work/build-script",
SRCDIR="/work/src",
CHROOT_SRCDIR="/work/src",
DESTDIR=str(context.install_dir),
SCRIPT="/work/build-script",
CHROOT_SCRIPT="/work/build-script",
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
OUTPUTDIR=str(context.staging),
SCRIPT="/work/build-script",
SRCDIR=str(Path.cwd()),
WITH_DOCS=one_zero(context.config.with_docs),
WITH_NETWORK=one_zero(context.config.with_network),
WITH_TESTS=one_zero(context.config.with_tests),
)

if context.config.build_dir is not None:
env |= dict(
BUILDDIR=str(context.config.build_dir),
BUILDDIR="/work/build",
CHROOT_BUILDDIR="/work/build",
)

Expand All @@ -515,20 +521,17 @@ def run_build_scripts(context: Context) -> None:
resolve=context.config.with_network,
tools=context.config.tools(),
options=[
"--bind", script, "/work/build-script",
"--bind", context.install_dir, "/work/dest",
"--bind", context.staging, "/work/out",
"--bind", Path.cwd(), "/work/src",
"--bind", cd, "/work/scripts",
"--bind", "/work/build-script", "/work/build-script",
"--bind", "/work/dest", "/work/dest",
"--bind", "/work/out", "/work/out",
"--bind", "/work/src", "/work/src",
"--bind", "/work/scripts", "/work/scripts",
*(
["--bind", os.fspath(context.config.build_dir), "/work/build"]
["--bind", "/work/build", "/work/build"]
if context.config.build_dir
else []
),
"--chdir", "/work/src",
"--setenv", "SRCDIR", "/work/src",
"--setenv", "DESTDIR", "/work/dest",
"--setenv", "OUTPUTDIR", "/work/out",
"--setenv", "BUILDROOT", "/",
*(["--setenv", "BUILDDIR", "/work/build"] if context.config.build_dir else []),
],
Expand All @@ -546,24 +549,24 @@ def run_build_scripts(context: Context) -> None:
complete_step(f"Running build script {script}…"),
):
run(
["/work/build-script" if script.suffix == ".chroot" else script, *cmdline],
["/work/build-script", *cmdline],
env=env | context.config.environment,
stdin=sys.stdin,
sandbox=context.sandbox(
network=context.config.with_network,
options=sources + [
"--ro-bind", script, script,
"--ro-bind", cd, cd,
"--ro-bind", script, "/work/build-script",
"--ro-bind", cd, "/work/scripts",
"--bind", context.root, context.root,
"--bind", context.install_dir, context.install_dir,
"--bind", context.staging, context.staging,
"--bind", context.install_dir, "/work/dest",
"--bind", context.staging, "/work/out",
*(
["--bind", os.fspath(context.config.build_dir), os.fspath(context.config.build_dir)]
["--bind", os.fspath(context.config.build_dir), "/work/build"]
if context.config.build_dir
else []
),
*finalize_crypto_mounts(tools=context.config.tools()),
"--chdir", Path.cwd(),
"--chdir", "/work/src",
],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
Expand All @@ -577,14 +580,14 @@ def run_postinst_scripts(context: Context) -> None:
env = dict(
ARCHITECTURE=str(context.config.architecture),
BUILDROOT=str(context.root),
OUTPUTDIR="/work/out",
CHROOT_OUTPUTDIR="/work/out",
SCRIPT="/work/postinst",
CHROOT_SCRIPT="/work/postinst",
SRCDIR="/work/src",
CHROOT_SRCDIR="/work/src",
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
OUTPUTDIR=str(context.staging),
SCRIPT="/work/postinst",
SRCDIR=str(Path.cwd()),
)

with (
Expand All @@ -597,13 +600,11 @@ def run_postinst_scripts(context: Context) -> None:
resolve=context.config.with_network,
tools=context.config.tools(),
options=[
"--bind", script, "/work/postinst",
"--bind", context.staging, "/work/out",
"--bind", Path.cwd(), "/work/src",
"--bind", cd, "/work/scripts",
"--bind", "/work/postinst", "/work/postinst",
"--bind", "/work/out", "/work/out",
"--bind", "/work/src", "/work/src",
"--bind", "/work/scripts", "/work/scripts",
"--chdir", "/work/src",
"--setenv", "SRCDIR", "/work/src",
"--setenv", "OUTPUTDIR", "/work/out",
"--setenv", "BUILDROOT", "/",
],
)
Expand All @@ -618,18 +619,18 @@ def run_postinst_scripts(context: Context) -> None:
complete_step(f"Running postinstall script {script}…"),
):
run(
["/work/postinst" if script.suffix == ".chroot" else script, "final"],
["/work/postinst", "final"],
env=env | context.config.environment,
stdin=sys.stdin,
sandbox=context.sandbox(
network=context.config.with_network,
options=sources + [
"--ro-bind", script, script,
"--ro-bind", cd, cd,
"--ro-bind", script, "/work/postinst",
"--ro-bind", cd, "/work/scripts",
"--bind", context.root, context.root,
"--bind", context.staging, context.staging,
"--bind", context.staging, "/work/out",
*finalize_crypto_mounts(tools=context.config.tools()),
"--chdir", Path.cwd(),
"--chdir", "/work/src",
],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
Expand All @@ -643,14 +644,14 @@ def run_finalize_scripts(context: Context) -> None:
env = dict(
ARCHITECTURE=str(context.config.architecture),
BUILDROOT=str(context.root),
OUTPUTDIR="/work/out",
CHROOT_OUTPUTDIR="/work/out",
CHROOT_SCRIPT="/work/finalize",
SRCDIR="/work/src",
CHROOT_SRCDIR="/work/src",
SCRIPT="/work/finalize",
CHROOT_SCRIPT="/work/finalize",
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
OUTPUTDIR=str(context.staging),
SCRIPT="/work/finalize",
SRCDIR=str(Path.cwd()),
)

with (
Expand All @@ -663,13 +664,11 @@ def run_finalize_scripts(context: Context) -> None:
resolve=context.config.with_network,
tools=context.config.tools(),
options=[
"--bind", script, "/work/finalize",
"--bind", context.staging, "/work/out",
"--bind", Path.cwd(), "/work/src",
"--bind", cd, "/work/scripts",
"--bind", "/work/finalize", "/work/finalize",
"--bind", "/work/out", "/work/out",
"--bind", "/work/src", "/work/src",
"--bind", "/work/scripts", "/work/scripts",
"--chdir", "/work/src",
"--setenv", "SRCDIR", "/work/src",
"--setenv", "OUTPUTDIR", "/work/out",
"--setenv", "BUILDROOT", "/",
],
)
Expand All @@ -684,18 +683,18 @@ def run_finalize_scripts(context: Context) -> None:
complete_step(f"Running finalize script {script}…"),
):
run(
["/work/finalize" if script.suffix == ".chroot" else script],
["/work/finalize"],
env=env | context.config.environment,
stdin=sys.stdin,
sandbox=context.sandbox(
network=context.config.with_network,
options=sources + [
"--ro-bind", script, script,
"--ro-bind", cd, cd,
"--ro-bind", script, "/work/finalize",
"--ro-bind", cd, "/work/scripts",
"--bind", context.root, context.root,
"--bind", context.staging, context.staging,
"--bind", context.staging, "/work/out",
*finalize_crypto_mounts(tools=context.config.tools()),
"--chdir", Path.cwd(),
"--chdir", "/work/src",
],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
Expand Down
8 changes: 4 additions & 4 deletions mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ def config_parse_list(value: Optional[str], old: Optional[list[Any]]) -> Optiona

# Empty strings reset the list.
if reset and len(values) == 1 and values[0] == "":
return None
return []

return new + [parse(v) for v in values if v]

Expand Down Expand Up @@ -1243,9 +1243,8 @@ def default(cls) -> "Config":
This prevents MkosiArgs being generated with defaults values implicitly.
"""
with tempfile.TemporaryDirectory() as tempdir:
with chdir(tempdir):
_, [config] = parse_config([])
with chdir("/"):
_, [config] = parse_config([])

return config

Expand Down Expand Up @@ -1867,6 +1866,7 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple
section="Content",
parse=config_make_list_parser(delimiter=",", parse=make_tree_parser(absolute=False)),
match=config_match_build_sources,
default_factory=lambda _: [ConfigTree(Path.cwd(), None)],
help="Path for sources to build",
),
ConfigSetting(
Expand Down
21 changes: 15 additions & 6 deletions mkosi/distributions/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,26 @@ def install(cls, context: Context) -> None:
(context.root / d).symlink_to(f"usr/{d}")
(context.root / f"usr/{d}").mkdir(parents=True, exist_ok=True)

invoke_apt(context, "apt-get", "update", apivfs=False)

# Next, we invoke apt-get install to download all the essential packages. With DPkg::Pre-Install-Pkgs,
# we specify a shell command that will receive the list of packages that will be installed on stdin.
# By configuring Debug::pkgDpkgPm=1, apt-get install will not actually execute any dpkg commands, so
# all it does is download the essential debs and tell us their full in the apt cache without actually
# installing them.
with tempfile.NamedTemporaryFile(dir="/tmp", mode="r") as f:
cls.install_packages(context, [
"-oDebug::pkgDPkgPm=1",
f"-oDPkg::Pre-Install-Pkgs::=cat >{f.name}",
"?essential", "?name(usr-is-merged)",
], apivfs=False)
with tempfile.NamedTemporaryFile(mode="r") as f:
invoke_apt(
context,
"apt-get",
"install",
[
"-oDebug::pkgDPkgPm=1",
f"-oDPkg::Pre-Install-Pkgs::=cat >{f.name}",
"?essential", "?exact-name(usr-is-merged)",
],
apivfs=False,
mounts=("--bind", f.name, f.name),
)

essential = f.read().strip().splitlines()

Expand Down
6 changes: 5 additions & 1 deletion mkosi/installer/apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ def invoke_apt(
command: str,
operation: str,
packages: Sequence[str] = (),
*,
apivfs: bool = True,
mounts: Sequence[PathString] = (),
) -> None:
run(
apt_cmd(context, command) + [operation, *sort_packages(packages)],
Expand All @@ -105,9 +107,11 @@ def invoke_apt(
network=True,
options=[
"--bind", context.root, context.root,
"--bind", context.cache_dir, context.cache_dir,
"--bind", context.cache_dir / "lib/apt", context.cache_dir / "lib/apt",
"--bind", context.cache_dir / "cache/apt", context.cache_dir / "cache/apt",
"--ro-bind", context.workspace / "apt.conf", context.workspace / "apt.conf",
*finalize_crypto_mounts(tools=context.config.tools()),
*mounts,
],
) + (apivfs_cmd(context.root, tools=context.config.tools()) if apivfs else [])
),
Expand Down
Loading

0 comments on commit bc2b6cc

Please sign in to comment.