Repository navigation
Conversation
87b5b53 to
ae91f44
Compare
| refused. Previously, `--no-pivot` resulted in host's `/proc` and `/sys` being | ||
| unmounted, and `rootfsPropagation` changed the propagation of host mounts. | ||
| (#5535) | ||
| - runc now uses `pivot_root(2)` when running from initramfs on kernels older |
There was a problem hiding this comment.
The behavior on >= v7.0 should be described too?
There was a problem hiding this comment.
added "(on v7.0+, pivot_root(2) works in initramfs natively)"
There was a problem hiding this comment.
(and I checked it on 7.2 -- it works without --no-pivot even before this PR)
ae91f44 to
3716f65
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Mount cleanup can retain full procfs/sysfs beneath stacked mounts and abort startup when inherited mounts are locked by a user namespace.
2 open issues
What changed in this PR
This PR adds an initramfs workaround for pivot_root, replacing the insecure --no-pivot path while retaining deprecated CLI and configuration compatibility.
Changes:
- Adds root-mount cloning and cleanup for initramfs on older kernels.
- Makes
--no-pivotignored and hidden, with a warning and updated documentation. - Adds QEMU-based integration coverage and installs QEMU in CI.
| File | Description |
|---|---|
utils_linux.go |
Warns when --no-pivot is supplied. |
tests/integration/no_pivot.bats |
Checks the warning and removes obsolete validation coverage. |
tests/integration/initramfs.bats |
Adds VM tests for startup, propagation, and mount isolation. |
tests/integration/helpers.bash |
Adds QEMU setup, kernel unpacking, and execution helpers. |
run.go |
Hides and deprecates --no-pivot. |
restore.go |
Hides and deprecates --no-pivot. |
man/runc-run.8.md |
Documents the ignored option. |
man/runc-restore.8.md |
Documents the ignored option. |
man/runc-create.8.md |
Documents the ignored option. |
libcontainer/specconv/spec_linux.go |
Stops applying NoPivotRoot and removes its propagation restriction. |
libcontainer/rootfs_linux.go |
Replaces MS_MOVE-based root switching with the initramfs pivot workaround. |
libcontainer/configs/validate/validator.go |
Removes obsolete no-pivot validation. |
libcontainer/configs/config.go |
Marks NoPivotRoot deprecated and ignored. |
create.go |
Hides and deprecates --no-pivot. |
CHANGELOG.md |
Records initramfs support and no-pivot deprecation. |
.github/workflows/test.yml |
Installs architecture-appropriate QEMU packages. |
Review effort: Balanced
| // mounted first. Note that paths starting with / are resolved from | ||
| // our current root (the initramfs), not from the clone on top of it. | ||
| for _, m := range slices.Backward(mounts) { | ||
| if m.Parent != rootID || m.ID == rootID || m.Mountpoint == "/" { |
There was a problem hiding this comment.
Fixed by re-running umount until we get EINVAL (the first EINVAL is still considered fatal, the second means this is no longer a mount point).
| if err := unmount(m.Mountpoint, unix.MNT_DETACH); err != nil { | ||
| return true, err |
There was a problem hiding this comment.
Yes, this is a limitation of the new code. It looks like no one is using this combination (runc on initramfs root with user namespace and kernel < 7.0), but if I'm wrong, we may need to carry msMoveMount.
3716f65 to
0860950
Compare
0860950 to
859d350
Compare
Before Linux 7.0 (which added nullfs underneath the initramfs), pivot_root(2) fails with EINVAL if the current root is the initramfs, because the initramfs mount has no parent. Users were told to use --no-pivot instead, which is far less secure. Work around it by creating a recursive bind-mount clone of / (which does have a parent), moving it on top of /, and chroot(2)-ing into it, after which pivot_root(2) can be retried. The chroot(2) is only temporary; once pivot_root(2) succeeds, the clone is detached as usual. The container rootfs is used as a temporary mount point for the clone, so this works with read-only initramfs images. Variants of this hack are fairly well known (see [1], [2]). The workaround is only used when pivot_root(2) fails with EINVAL and the current root mount is its own parent (i.e. it is the initramfs), rather than for any EINVAL (which can also mean e.g. shared propagation). Before the move, / is made a slave so the move does not propagate to the host if / is shared. Since the initramfs can never be unmounted, any mounts on top of it would stay in the container's mount namespace forever (hidden underneath the container root). Those include full procfs and sysfs mounts, which can be used to bypass procfs/sysfs mount restrictions (i.e. a container could mount a fresh procfs from within a user namespace). To prevent that, detach all the original mounts on top of the initramfs (we only use their clones), including any mounts stacked on top of them. If the container has a user namespace, the original mounts are locked (as the mount namespace is created after the user namespace), and can't be detached, so such a container can't be run from initramfs on kernels older than v7.0. [1]: containers/bubblewrap#592 (comment) [2]: https://aconz2.github.io/2024/07/29/container-from-initramfs.html Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> [kolyshkin: rebase onto preopened rootfs; only use the workaround for initramfs; make / a slave before the move; detach original mounts; reword commit message] Assisted-by: Claude Opus 5.5 Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
The main use case for --no-pivot was running from initramfs, where pivot_root(2) used to fail. Now pivot_root(2) works there (natively since Linux 7.0, and with a workaround added by the previous commit for older kernels). MS_MOVE+chroot(2) is insecure (even with all the hardening we added over the years), so remove it, and always use pivot_root(2) when a mount namespace is used. If pivot_root(2) fails, it is an error. Keep configs.Config.NoPivotRoot and specconv.CreateOpts.NoPivotRoot for compatibility, but deprecate them, as they are now ignored. Make runc --no-pivot a no-op, printing a warning. Remove the checks which are no longer needed, as they were specific to --no-pivot: - no-pivot with no mount namespace (added by commit 66e3067e); - rootfsPropagation of [r]private with no-pivot. Drop the "runc run --no-pivot [host mount ns] must fail" test, and amend the "runc run --no-pivot must not expose bare /proc" test to check for the warning. Assisted-by: Claude Opus 5.5 Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Now that pivot_root(2) works in initramfs, --no-pivot is not needed, and it is ignored. Mark it as deprecated, hide it from runc create, run and restore --help, and update the man pages and the changelog. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> [kolyshkin: also hide it for runc restore; update man pages and changelog] Assisted-by: Claude Opus 5.5 Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Add tests which run runc from initramfs, using a qemu VM with the host kernel, and an initramfs made of busybox image, runc binary (with its libraries), and a container bundle. The tests check that: - a container can be run (and its root is the container rootfs); - host mounts are not changed (including the case of rshared root); - --no-pivot is ignored; - container can't mount fresh procfs or sysfs from a user namespace (including the case of stacked /proc and /sys mounts); - a container with a user namespace works on v7.0+, and fails with a clear error on older kernels. Note that the pivot_root workaround is only used on kernels < v7.0. Both x86_64 and aarch64 are supported. Since qemu can't load EFI zboot kernel images unless they are gzip-compressed (and Ubuntu arm64 kernels since v7.0 are zstd-compressed), unpack those. The qemu and initramfs helpers are added to helpers.bash, so they can be reused by other tests. Install qemu in CI jobs (the tests are skipped if qemu or the host kernel image is not available). Based on the test originally written by Aleksa Sarai. Co-Authored-By: Aleksa Sarai <cyphar@cyphar.com> Assisted-by: Claude Opus 5.5 Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
859d350 to
1882783
Compare


This is a carry/rework of #4434.
1. pivot_root in initramfs
Before Linux 7.0 (which added nullfs underneath the initramfs),
pivot_root(2)fails withEINVALwhen running from initramfs, as the initramfs mount has no parent. Users were told to use--no-pivot, which is insecure.The workaround (as in #4434) is to make a recursive bind-mount clone of
/, move it on top of/, and chroot into it, after whichpivot_root(2)works. Changes compared to #4434:pivotRootnow takes*os.File);EINVAL(which can also mean e.g. shared propagation);/is made a slave before the move, so it does not propagate to the host if/is shared;2. Deprecating --no-pivot, making it a no-op
Since the main use case for
--no-pivotis now covered, and MS_MOVE+chroot is insecure, the MS_MOVE+chroot code (msMoveRoot) is removed, and--no-pivotis now a no-op (with a warning), deprecated and hidden from--help. Ifpivot_root(2)fails, it is an error.configs.Config.NoPivotRootandspecconv.CreateOpts.NoPivotRootare kept for compatibility, but are deprecated and ignored. The checks specific to--no-pivotare removed.See #4435 for the deprecation tracking issue.
3. Tests
New
tests/integration/initramfs.batsboots a qemu VM with the host kernel and an initramfs made of busybox image, runc, and a bundle, and checks that:--no-pivotis ignored;Both x86_64 and aarch64 are supported (EFI zboot kernel images are unpacked, as qemu can only load gzip-compressed ones). The tests do not require root, but are skipped if qemu or a readable host kernel image is not available. CI now installs qemu in the
testjobs; ubuntu-24.04 (kernel 6.17) exercises the workaround, and ubuntu-26.04 (kernel 7.0) the native path.Closes: #4434
Fixes: #1961
Closes: #5532