diff options
| author | Giacomo Leidi <therewasa@fishinthecalculator.me> | 2026-06-07 23:45:09 +0200 |
|---|---|---|
| committer | Giacomo Leidi <therewasa@fishinthecalculator.me> | 2026-06-15 11:38:46 +0200 |
| commit | 518129e1130f6cc6e128af0729d678e94b7dfefd (patch) | |
| tree | 4fbfbc76fc6348c753802c2dcd6a4b5c7e7fbefc | |
| parent | 1fa2e6739e6d37d5b4234dc065222c29309a1da8 (diff) | |
linux-container: Remount / recursively as MS_PRIVATE, immediately after entering the new mount namespace.
Apply the same workaround that runc, crun, and buildah apply
unconditionally: immediately after entering the new mount namespace,
remount / recursively as MS_PRIVATE. Concretely, the call we will
make in Guile mirrors the verbatim C call already present in crun at
https://github.com/containers/crun/blob/99a8512ef50c5af1b8cfff28eacddf870220ca9f/src/libcrun/linux.c#L4677:
ret = mount (NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL);
and the Go equivalents in:
- runc (defaults to MS_SLAVE for reasons documented in its pivotRoot):
https://github.com/opencontainers/runc/blob/a7e766484c38be1f0cf9b7d960c9324316cb7204/libcontainer/rootfs_linux.go#L1106
- buildah (expands to mount("", "/", "none", MS_REC|MS_PRIVATE, "")):
https://github.com/podman-container-tools/buildah/blob/769d311d739dd4bbde496a9480879ce6aadaa1e0/bind/mount.go#L41
Our implementation:
1. Detaches the container's mount namespace from the host's propagation
peer group, so subsequent mount/umount events inside the container
do not propagate to the host.
2. Makes the kernel's pivot_root(2) preconditions satisfied: both
new_root (the tmpfs we then create) and its parent (/) have propagation
type MS_PRIVATE.
3. Should have no effect on processes outside the new mount namespace. The
host's / retains whatever propagation type it had.
mount-file-systems is only invoked from run-container when both
'mnt namespace is requested and root != "/", so we are
guaranteed to be inside a fresh mount namespace at this point and that
the remount only affects this namespace. The remount must happen before
the tmpfs is created. Once the tmpfs exists as a child of the (shared)
host /, fixing only the host / does not retroactively change the
propagation type the tmpfs already inherited. Doing the remount first
means every subsequent mount this function performs inherits MS_PRIVATE
from its parent.
* guix/build/syscalls.scm (MS_PRIVATE): New variable.
* gnu/build/linux-container.scm (mount-file-systems): Remount / recursively as MS_PRIVATE, immediately after entering the new mount namespace.
* gnu/services/containers.scm (%miniflux-create-admin-credentials,
%rootless-podman-os-with-least-authority-wrapper,
run-rootless-podman-test-with-least-authority-wrapper): New bindings.
(%test-rootless-podman-with-least-authority-wrapper): New regression
test making sure the rootless-podman-service-type does not break again
least-authority-wrapper bases services.
Fixes: https://codeberg.org/guix/guix/issues/3233
Merges: https://codeberg.org/guix/guix/pulls/9140
| -rw-r--r-- | gnu/build/linux-container.scm | 11 | ||||
| -rw-r--r-- | gnu/tests/containers.scm | 97 | ||||
| -rw-r--r-- | guix/build/syscalls.scm | 2 |
3 files changed, 109 insertions, 1 deletions
diff --git a/gnu/build/linux-container.scm b/gnu/build/linux-container.scm index bc1f8851dd2..32990f4dac5 100644 --- a/gnu/build/linux-container.scm +++ b/gnu/build/linux-container.scm | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2015 David Thompson <davet@gnu.org> | 2 | ;;; Copyright © 2015 David Thompson <davet@gnu.org> |
| 3 | ;;; Copyright © 2017-2019, 2022-2023, 2025 Ludovic Courtès <ludo@gnu.org> | 3 | ;;; Copyright © 2017-2019, 2022-2023, 2025 Ludovic Courtès <ludo@gnu.org> |
| 4 | ;;; Copyright © 2026 Giacomo Leidi <therewasa@fishinthecalculator.me> | ||
| 4 | ;;; | 5 | ;;; |
| 5 | ;;; This file is part of GNU Guix. | 6 | ;;; This file is part of GNU Guix. |
| 6 | ;;; | 7 | ;;; |
| @@ -104,6 +105,16 @@ for the process." | |||
| 104 | (mkdir-p target) | 105 | (mkdir-p target) |
| 105 | (mount source target type flags options #:update-mtab? update-mtab?)) | 106 | (mount source target type flags options #:update-mtab? update-mtab?)) |
| 106 | 107 | ||
| 108 | ;; Detach our mount namespace from the host's propagation peer | ||
| 109 | ;; group. Without this, when the host's / is MS_SHARED - as it | ||
| 110 | ;; is on a system running rootless-podman-service-type, and as it | ||
| 111 | ;; is by default on systemd hosts - 'pivot_root' below fails | ||
| 112 | ;; with EINVAL because the new root and its parent must not have | ||
| 113 | ;; propagation type MS_SHARED (pivot_root(2)). This is the same | ||
| 114 | ;; remount that runc, crun and buildah perform immediately after | ||
| 115 | ;; unshare(CLONE_NEWNS). | ||
| 116 | (mount #f "/" #f (logior MS_REC MS_PRIVATE)) | ||
| 117 | |||
| 107 | ;; The container's file system is completely ephemeral, sans directories | 118 | ;; The container's file system is completely ephemeral, sans directories |
| 108 | ;; bind-mounted from the host. | 119 | ;; bind-mounted from the host. |
| 109 | (mount "none" root "tmpfs") | 120 | (mount "none" root "tmpfs") |
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm index 6fb7d6d5463..6b7dd7f355f 100644 --- a/gnu/tests/containers.scm +++ b/gnu/tests/containers.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2024, 2025 Giacomo Leidi <therewasa@fishinthecalculator.me> | 2 | ;;; Copyright © 2024-2026 Giacomo Leidi <therewasa@fishinthecalculator.me> |
| 3 | ;;; | 3 | ;;; |
| 4 | ;;; This file is part of GNU Guix. | 4 | ;;; This file is part of GNU Guix. |
| 5 | ;;; | 5 | ;;; |
| @@ -22,16 +22,19 @@ | |||
| 22 | #:use-module (guix build-system trivial) | 22 | #:use-module (guix build-system trivial) |
| 23 | #:use-module (gnu packages bash) | 23 | #:use-module (gnu packages bash) |
| 24 | #:use-module (gnu packages containers) | 24 | #:use-module (gnu packages containers) |
| 25 | #:use-module (gnu packages databases) | ||
| 25 | #:use-module (gnu packages guile) | 26 | #:use-module (gnu packages guile) |
| 26 | #:use-module (gnu packages guile-xyz) | 27 | #:use-module (gnu packages guile-xyz) |
| 27 | #:use-module (gnu services) | 28 | #:use-module (gnu services) |
| 28 | #:use-module (gnu services containers) | 29 | #:use-module (gnu services containers) |
| 30 | #:use-module (gnu services databases) | ||
| 29 | #:use-module (gnu services desktop) | 31 | #:use-module (gnu services desktop) |
| 30 | #:use-module ((gnu services docker) | 32 | #:use-module ((gnu services docker) |
| 31 | #:select (containerd-service-type | 33 | #:select (containerd-service-type |
| 32 | docker-service-type)) | 34 | docker-service-type)) |
| 33 | #:use-module (gnu services dbus) | 35 | #:use-module (gnu services dbus) |
| 34 | #:use-module (gnu services networking) | 36 | #:use-module (gnu services networking) |
| 37 | #:use-module (gnu services web) | ||
| 35 | #:use-module (gnu system) | 38 | #:use-module (gnu system) |
| 36 | #:use-module (gnu system accounts) | 39 | #:use-module (gnu system accounts) |
| 37 | #:use-module (gnu system vm) | 40 | #:use-module (gnu system vm) |
| @@ -43,6 +46,7 @@ | |||
| 43 | #:use-module ((guix scripts pack) #:prefix pack:) | 46 | #:use-module ((guix scripts pack) #:prefix pack:) |
| 44 | #:use-module (guix store) | 47 | #:use-module (guix store) |
| 45 | #:export (%test-rootless-podman | 48 | #:export (%test-rootless-podman |
| 49 | %test-rootless-podman-with-least-authority-wrapper | ||
| 46 | %test-oci-service-rootless-podman | 50 | %test-oci-service-rootless-podman |
| 47 | %test-oci-service-docker)) | 51 | %test-oci-service-docker)) |
| 48 | 52 | ||
| @@ -69,6 +73,42 @@ | |||
| 69 | (supplementary-groups '("wheel" "netdev" "cgroup" | 73 | (supplementary-groups '("wheel" "netdev" "cgroup" |
| 70 | "audio" "video"))))))) | 74 | "audio" "video"))))))) |
| 71 | 75 | ||
| 76 | (define %miniflux-create-admin-credentials | ||
| 77 | #~(begin | ||
| 78 | (mkdir "/var/miniflux") | ||
| 79 | (call-with-output-file "/var/miniflux/admin-username" | ||
| 80 | (lambda (port) | ||
| 81 | (display "test" port))) | ||
| 82 | (call-with-output-file "/var/miniflux/admin-password" | ||
| 83 | (lambda (port) | ||
| 84 | (display "testpassword" port))))) | ||
| 85 | |||
| 86 | ;; A separate operating-system definition is necessary to test the interactions | ||
| 87 | ;; between rootless podman and least-authority-wrapper backed services, miniflux | ||
| 88 | ;; in this case. For the purpose of the test, any service using it should be | ||
| 89 | ;; fine though. | ||
| 90 | (define %rootless-podman-os-with-least-authority-wrapper | ||
| 91 | (simple-operating-system | ||
| 92 | (service rootless-podman-service-type | ||
| 93 | (rootless-podman-configuration | ||
| 94 | (subgids | ||
| 95 | (list (subid-range (name "alice")))) | ||
| 96 | (subuids | ||
| 97 | (list (subid-range (name "alice")))))) | ||
| 98 | (service dhcpcd-service-type) | ||
| 99 | (service dbus-root-service-type) | ||
| 100 | (service polkit-service-type) | ||
| 101 | (service elogind-service-type) | ||
| 102 | (simple-service 'create-miniflux-admin-credentials | ||
| 103 | activation-service-type | ||
| 104 | %miniflux-create-admin-credentials) | ||
| 105 | (service postgresql-service-type | ||
| 106 | (postgresql-configuration | ||
| 107 | (postgresql postgresql))) | ||
| 108 | (service miniflux-service-type | ||
| 109 | (miniflux-configuration | ||
| 110 | (listen-address "/var/run/miniflux/miniflux.sock"))))) | ||
| 111 | |||
| 72 | (define (run-rootless-podman-test oci-tarball) | 112 | (define (run-rootless-podman-test oci-tarball) |
| 73 | 113 | ||
| 74 | (define os | 114 | (define os |
| @@ -345,12 +385,67 @@ standard output device and then enters a new line.") | |||
| 345 | #:localstatedir? #t))) | 385 | #:localstatedir? #t))) |
| 346 | (run-rootless-podman-test tarball))) | 386 | (run-rootless-podman-test tarball))) |
| 347 | 387 | ||
| 388 | (define (run-rootless-podman-test-with-least-authority-wrapper) | ||
| 389 | |||
| 390 | (define os | ||
| 391 | (marionette-operating-system | ||
| 392 | %rootless-podman-os-with-least-authority-wrapper | ||
| 393 | #:imported-modules '((gnu services herd) | ||
| 394 | (guix combinators)))) | ||
| 395 | |||
| 396 | (define vm | ||
| 397 | (virtual-machine | ||
| 398 | (operating-system os) | ||
| 399 | (volatile? #t) | ||
| 400 | (memory-size 1024) | ||
| 401 | (disk-image-size (* 3000 (expt 2 20))) | ||
| 402 | (port-forwardings '()))) | ||
| 403 | |||
| 404 | (define test | ||
| 405 | (with-imported-modules '((gnu build marionette) | ||
| 406 | (gnu services herd)) | ||
| 407 | #~(begin | ||
| 408 | (use-modules (srfi srfi-11) (srfi srfi-64) | ||
| 409 | (gnu build marionette)) | ||
| 410 | |||
| 411 | (define marionette | ||
| 412 | ;; Relax timeout to accommodate older systems and | ||
| 413 | ;; allow for pulling the image. | ||
| 414 | (make-marionette (list #$vm) #:timeout 60)) | ||
| 415 | |||
| 416 | (test-runner-current (system-test-runner #$output)) | ||
| 417 | |||
| 418 | (test-begin "rootless-podman-with-least-authority-wrapper") | ||
| 419 | |||
| 420 | (test-assert "services start correctly" | ||
| 421 | (marionette-eval | ||
| 422 | '(begin | ||
| 423 | (use-modules (gnu services herd)) | ||
| 424 | (wait-for-service 'file-system-/sys/fs/cgroup) | ||
| 425 | (wait-for-service 'miniflux)) | ||
| 426 | marionette)) | ||
| 427 | |||
| 428 | (test-assert "Check miniflux socket file is created" | ||
| 429 | (wait-for-unix-socket "/var/run/miniflux/miniflux.sock" marionette)) | ||
| 430 | |||
| 431 | (test-end)))) | ||
| 432 | (gexp->derivation "rootless-podman-with-least-authority-wrapper-test" test)) | ||
| 433 | |||
| 348 | (define %test-rootless-podman | 434 | (define %test-rootless-podman |
| 349 | (system-test | 435 | (system-test |
| 350 | (name "rootless-podman") | 436 | (name "rootless-podman") |
| 351 | (description "Test rootless Podman service.") | 437 | (description "Test rootless Podman service.") |
| 352 | (value (build-tarball&run-rootless-podman-test)))) | 438 | (value (build-tarball&run-rootless-podman-test)))) |
| 353 | 439 | ||
| 440 | (define %test-rootless-podman-with-least-authority-wrapper | ||
| 441 | (system-test | ||
| 442 | (name "rootless-podman-with-least-authority-wrapper") | ||
| 443 | (description "Test rootless Podman service, making sure services based on | ||
| 444 | least-authority-wrapper keep working. See | ||
| 445 | https://codeberg.org/guix/guix/issues/3233 for an example of how it has broken | ||
| 446 | in the past.") | ||
| 447 | (value (run-rootless-podman-test-with-least-authority-wrapper)))) | ||
| 448 | |||
| 354 | (define %guile-oci-image | 449 | (define %guile-oci-image |
| 355 | (oci-image | 450 | (oci-image |
| 356 | (repository "guile") | 451 | (repository "guile") |
diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm index 7a5727a8d46..e92bca7aaa3 100644 --- a/guix/build/syscalls.scm +++ b/guix/build/syscalls.scm | |||
| @@ -55,6 +55,7 @@ | |||
| 55 | MS_BIND | 55 | MS_BIND |
| 56 | MS_MOVE | 56 | MS_MOVE |
| 57 | MS_REC | 57 | MS_REC |
| 58 | MS_PRIVATE | ||
| 58 | MS_SHARED | 59 | MS_SHARED |
| 59 | MS_LAZYTIME | 60 | MS_LAZYTIME |
| 60 | MNT_FORCE | 61 | MNT_FORCE |
| @@ -564,6 +565,7 @@ the last argument of `mknod'." | |||
| 564 | (define MS_BIND 4096) | 565 | (define MS_BIND 4096) |
| 565 | (define MS_MOVE 8192) | 566 | (define MS_MOVE 8192) |
| 566 | (define MS_REC 16384) | 567 | (define MS_REC 16384) |
| 568 | (define MS_PRIVATE 262144) | ||
| 567 | (define MS_SHARED 1048576) | 569 | (define MS_SHARED 1048576) |
| 568 | (define MS_RELATIME 2097152) | 570 | (define MS_RELATIME 2097152) |
| 569 | (define MS_STRICTATIME 16777216) | 571 | (define MS_STRICTATIME 16777216) |
