Package: guix-patches;
Reported by: Ludovic Courtès <ludo <at> gnu.org>
Date: Tue, 8 Apr 2025 12:23:02 UTC
Severity: normal
Tags: patch
To reply to this bug, email your comments to 77638 AT debbugs.gnu.org.
Toggle the display of automated, internal messages from the tracker.
View this report as an mbox folder, status mbox, maintainer mbox
guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:23:02 GMT) Full text and rfc822 format available.Ludovic Courtès <ludo <at> gnu.org>
:guix-patches <at> gnu.org
.
(Tue, 08 Apr 2025 12:23:02 GMT) Full text and rfc822 format available.Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: guix-patches <at> gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 0/8] Harden 'call-with-container' Date: Tue, 8 Apr 2025 14:22:06 +0200
Hello Guix, This patch series hardens ‘call-with-container’, largely inspired by the discussions had while working on the unprivileged daemon. This depends on <https://issues.guix.gnu.org/77288> for ‘unshare’. My main test was: make check TESTS="tests/containers.scm tests/guix-home.sh tests/guix-environment-container.sh" … which catches most issues. I also manually tested ‘least-authority-wrapper’. I did not test ‘guix system container’. Note the incompatible change in ‘guix shell -C’, where the root is now read-only by default (it was indirectly documented as being writable before). I think it’s an acceptable change, but we can discuss. :-) Thoughts? Ludo’. Ludovic Courtès (8): linux-container: Add #:mounts to ‘eval/container’. guix home: ‘container’ explicitly mounts $HOME and /run/user/1000. linux-container: Support having a read-only root file system. guix home: ‘container’ provides a read-only root file system. environment: Add ‘--writable-root’ and default to read-only root. syscalls: Add ‘get-user-ns’. linux-container: Set up “lo” and generate /etc/hosts by default. linux-container: Lock mounts by default. doc/guix.texi | 7 +- gnu/build/linux-container.scm | 172 +++++++++++++++++++++------- gnu/system/linux-container.scm | 31 +++-- guix/build/syscalls.scm | 14 +++ guix/scripts/environment.scm | 100 ++++++++-------- guix/scripts/home.scm | 92 +++++++-------- tests/containers.scm | 59 +++++++++- tests/guix-environment-container.sh | 11 +- tests/guix-home.sh | 3 +- 9 files changed, 336 insertions(+), 153 deletions(-) base-commit: b94cf86a89ef0a6bf7ec2c8e52f64c5107888f55 -- 2.49.0
guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:26:02 GMT) Full text and rfc822 format available.Message #8 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 1/8] linux-container: Add #:mounts to ‘eval/container’. Date: Tue, 8 Apr 2025 14:24:41 +0200
* gnu/system/linux-container.scm (eval/container): Add #:mounts parameter and honor it. Change-Id: I1d5970f53a3d67db93e937e392f9bf36e75d1573 --- gnu/system/linux-container.scm | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/gnu/system/linux-container.scm b/gnu/system/linux-container.scm index c1705f491c..3622328500 100644 --- a/gnu/system/linux-container.scm +++ b/gnu/system/linux-container.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2015 David Thompson <davet <at> gnu.org> -;;; Copyright © 2016-2017, 2019-2023 Ludovic Courtès <ludo <at> gnu.org> +;;; Copyright © 2016-2017, 2019-2023, 2025 Ludovic Courtès <ludo <at> gnu.org> ;;; Copyright © 2019 Arun Isaac <arunisaac <at> systemreboot.net> ;;; Copyright © 2020 Efraim Flashner <efraim <at> flashner.co.il> ;;; Copyright © 2020 Google LLC @@ -319,13 +319,14 @@ (define* (container-script os #:key (mappings '()) shared-network?) (define* (eval/container exp #:key (mappings '()) + (mounts '()) (namespaces %namespaces) (guest-uid 0) (guest-gid 0)) "Evaluate EXP, a gexp, in a new process executing in separate namespaces as -listed in NAMESPACES. Add MAPPINGS, a list of <file-system-mapping>, to the -set of directories visible in the process's mount namespace. Inside the -namespaces, run code as GUEST-UID and GUEST-GID. Return the process' exit -status as a monadic value. +listed in NAMESPACES. Add MOUNTS, a list of <file-system>, and MAPPINGS, a +list of <file-system-mapping>, to the set of directories visible in the +process's mount namespace. Inside the namespaces, run code as GUEST-UID and +GUEST-GID. Return the process' exit status as a monadic value. This is useful to implement processes that, unlike derivations, are not entirely pure and need to access the outside world or to perform side @@ -342,13 +343,14 @@ (define* (eval/container exp (mbegin %store-monad (built-derivations inputs) (mlet %store-monad ((closure ((store-lift requisites) items))) - (return (call-with-container (map file-system-mapping->bind-mount - (append (map (lambda (item) - (file-system-mapping - (source item) - (target source))) - closure) - mappings)) + (return (call-with-container (append mounts + (map file-system-mapping->bind-mount + (append (map (lambda (item) + (file-system-mapping + (source item) + (target source))) + closure) + mappings))) (lambda () (apply execl (string-append (derivation-input-output-path -- 2.49.0
andrew <at> trop.in, guix <at> cbaines.net, janneke <at> gnu.org, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, tanguy <at> bioneland.org, me <at> tobias.gr, guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:27:02 GMT) Full text and rfc822 format available.Message #11 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 4/8] guix home: ‘container’ provides a read-only root file system. Date: Tue, 8 Apr 2025 14:24:44 +0200
* guix/scripts/home.scm (spawn-home-container): Move creation of accounts, /etc/hosts, /tmp, and HOME-DIRECTORY from the first argument of ‘eval/container’ to #:populate-file-system. Remove #:writable-root?. * tests/guix-home.sh: Test that the root file system is read-only. Change-Id: Icda54706321d51b95b563c86c3fb2238cc65ee20 --- guix/scripts/home.scm | 79 +++++++++++++++++++++---------------------- tests/guix-home.sh | 3 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/guix/scripts/home.scm b/guix/scripts/home.scm index 7ce6217324..6fcb0ca382 100644 --- a/guix/scripts/home.scm +++ b/guix/scripts/home.scm @@ -34,6 +34,10 @@ (define-module (guix scripts home) home-shepherd-configuration-services shepherd-service-requirement) #:autoload (guix modules) (source-module-closure) + #:autoload (gnu build accounts) (password-entry + group-entry + write-passwd + write-group) #:autoload (gnu build linux-container) (call-with-container %namespaces) #:autoload (gnu system linux-container) (eval/container) #:autoload (gnu system file-systems) (file-system @@ -283,14 +287,13 @@ (define* (spawn-home-container home (with-extensions (list guile-gcrypt) (with-imported-modules `(((guix config) => ,(make-config.scm)) ,@(source-module-closure - '((gnu build accounts) - (guix profiles) + '((guix profiles) (guix build utils) (guix build syscalls)) #:select? not-config?)) #~(begin (use-modules (guix build utils) - (gnu build accounts) + ((guix profiles) #:select (load-profile)) ((guix build syscalls) #:select (set-network-interface-up))) @@ -300,46 +303,10 @@ (define* (spawn-home-container home (define term #$(getenv "TERM")) - (define passwd - (password-entry - (name #$user-name) - (real-name #$user-real-name) - (uid #$uid) (gid #$gid) (shell shell) - (directory #$home-directory))) - - (define groups - (list (group-entry (name "users") (gid #$gid)) - (group-entry (gid 65534) ;the overflow GID - (name "overflow")))) - - ;; (guix profiles) loads (guix utils), which calls 'getpw' from the - ;; top level. Thus, arrange so that it's loaded after /etc/passwd - ;; has been created. - (module-autoload! (current-module) - '(guix profiles) '(load-profile)) - - ;; Create /etc/passwd for applications that need it, such as mcron. - (mkdir-p "/etc") - (write-passwd (list passwd)) - (write-group groups) - - (unless #$network? - ;; When isolated from the network, provide a minimal /etc/hosts - ;; to resolve "localhost". - (call-with-output-file "/etc/hosts" - (lambda (port) - (display "127.0.0.1 localhost\n" port) - (chmod port #o444)))) - - ;; Create /tmp; bits of code expect it, such as - ;; 'least-authority-wrapper'. - (mkdir-p "/tmp") - ;; Set PATH for things that the activation script might expect, such ;; as "env". (load-profile #$system-profile) - (mkdir-p #$home-directory) (setenv "HOME" #$home-directory) (setenv "GUIX_NEW_HOME" #$home) (primitive-load (string-append #$home "/activate")) @@ -359,6 +326,39 @@ (define* (spawn-home-container home ((_ ...) #~("-c" #$(string-join command)))))))) + #:populate-file-system + (lambda () + ;; Create files before the root file system is made read-only. + (define passwd + (password-entry + (name user-name) + (real-name user-real-name) + (uid uid) (gid gid) + (shell "/bin/sh") ;unused, doesn't have to match (user-shell) + (directory home-directory))) + + (define groups + (list (group-entry (name "users") (gid gid)) + (group-entry (gid 65534) ;the overflow GID + (name "overflow")))) + + ;; Create /etc/passwd for applications that need it, such as mcron. + (mkdir-p "/etc") + (write-passwd (list passwd)) + (write-group groups) + + (unless network? + ;; When isolated from the network, provide a minimal /etc/hosts + ;; to resolve "localhost". + (call-with-output-file "/etc/hosts" + (lambda (port) + (display "127.0.0.1 localhost\n" port) + (chmod port #o444)))) + + ;; Create /tmp; bits of code expect it, such as + ;; 'least-authority-wrapper'. + (mkdir-p "/tmp")) + #:namespaces (if network? (delq 'net %namespaces) ; share host network %namespaces) @@ -375,7 +375,6 @@ (define* (spawn-home-container home (type "tmpfs") (check? #f))) #:mappings (append network-mappings mappings) - #:writable-root? #t #:guest-uid uid #:guest-gid gid)) diff --git a/tests/guix-home.sh b/tests/guix-home.sh index 649d811a0c..dbfe7dbd48 100644 --- a/tests/guix-home.sh +++ b/tests/guix-home.sh @@ -1,7 +1,7 @@ # GNU Guix --- Functional package management for GNU # Copyright © 2021-2023 Andrew Tropin <andrew <at> trop.in> # Copyright © 2021 Oleg Pykhalov <go.wigust <at> gmail.com> -# Copyright © 2022, 2023 Ludovic Courtès <ludo <at> gnu.org> +# Copyright © 2022-2023, 2025 Ludovic Courtès <ludo <at> gnu.org> # # This file is part of GNU Guix. # @@ -132,6 +132,7 @@ EOF test -f '$HOME/sample/home.scm' guix home container home.scm --expose="$PWD=$HOME/sample" -- \ rm -v '$HOME/sample/home.scm' && false + guix home container home.scm -- touch /whatever && false else echo "'guix home container' test SKIPPED" >&2 fi -- 2.49.0
andrew <at> trop.in, guix <at> cbaines.net, janneke <at> gnu.org, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, tanguy <at> bioneland.org, me <at> tobias.gr, guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:27:03 GMT) Full text and rfc822 format available.Message #14 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 2/8] guix home: ‘container’ explicitly mounts $HOME and /run/user/1000. Date: Tue, 8 Apr 2025 14:24:42 +0200
* guix/scripts/home.scm (spawn-home-container): Pass #:mounts to ‘eval/container’. Change-Id: I1986c1411711cebaf623f97897d91436d8167037 --- guix/scripts/home.scm | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/guix/scripts/home.scm b/guix/scripts/home.scm index b4c82d275f..56a4b7c7d4 100644 --- a/guix/scripts/home.scm +++ b/guix/scripts/home.scm @@ -3,7 +3,7 @@ ;;; Copyright © 2021 Xinglu Chen <public <at> yoctocell.xyz> ;;; Copyright © 2021 Pierre Langlois <pierre.langlois <at> gmx.com> ;;; Copyright © 2021 Oleg Pykhalov <go.wigust <at> gmail.com> -;;; Copyright © 2022-2023 Ludovic Courtès <ludo <at> gnu.org> +;;; Copyright © 2022-2023, 2025 Ludovic Courtès <ludo <at> gnu.org> ;;; Copyright © 2022 Arun Isaac <arunisaac <at> systemreboot.net> ;;; Copyright © 2022 Antero Mejr <antero <at> mailbox.org> ;;; @@ -36,7 +36,8 @@ (define-module (guix scripts home) #:autoload (guix modules) (source-module-closure) #:autoload (gnu build linux-container) (call-with-container %namespaces) #:autoload (gnu system linux-container) (eval/container) - #:autoload (gnu system file-systems) (file-system-mapping + #:autoload (gnu system file-systems) (file-system + file-system-mapping file-system-mapping-source file-system-mapping->bind-mount specification->file-system-mapping @@ -361,6 +362,18 @@ (define* (spawn-home-container home #:namespaces (if network? (delq 'net %namespaces) ; share host network %namespaces) + #:mounts (list (file-system + (device "none") + (mount-point + (in-vicinity "/run/user" ;for shepherd & co. + (number->string uid))) + (type "tmpfs") + (check? #f)) + (file-system ;writable home + (device "none") + (mount-point home-directory) + (type "tmpfs") + (check? #f))) #:mappings (append network-mappings mappings) #:guest-uid uid #:guest-gid gid)) -- 2.49.0
guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:27:03 GMT) Full text and rfc822 format available.Message #17 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 6/8] syscalls: Add ‘get-user-ns’. Date: Tue, 8 Apr 2025 14:24:46 +0200
* guix/build/syscalls.scm (NS_GET_USERNS): New variable. (get-user-ns): New procedure. Change-Id: I0cfba6a7cdf2ab64ef658b0f821ba4e7c6c89eab --- guix/build/syscalls.scm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm index 42232fc7f1..9cb4b98908 100644 --- a/guix/build/syscalls.scm +++ b/guix/build/syscalls.scm @@ -146,6 +146,7 @@ (define-module (guix build syscalls) CLONE_NEWNET clone setns + get-user-ns kexec-load-file KEXEC_FILE_UNLOAD @@ -1229,6 +1230,19 @@ (define setns (list fdes nstype (strerror err)) (list err)))))))) +(define NS_GET_USERNS #xb701) + +(define (get-user-ns fdes) + "Return an open file descriptor to the user namespace that owns the +namespace pointed to by FDES, a file descriptor obtained by opening +/proc/PID/ns/*." + (let-values (((ret err) (%ioctl fdes NS_GET_USERNS %null-pointer))) + (when (< ret 0) + (throw 'system-error "get-user-ns" "~d: ~A" + (list fdes (strerror err)) + (list err))) + ret)) + (define pivot-root (let ((proc (syscall->procedure int "pivot_root" (list '* '*)))) (lambda (new-root put-old) -- 2.49.0
andrew <at> trop.in, guix <at> cbaines.net, janneke <at> gnu.org, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, tanguy <at> bioneland.org, me <at> tobias.gr, guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:27:04 GMT) Full text and rfc822 format available.Message #20 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 3/8] linux-container: Support having a read-only root file system. Date: Tue, 8 Apr 2025 14:24:43 +0200
Until now, the read-only file system set up by ‘call-with-container’ would always be writable. With this change, it can be made read-only. With this patch, only ‘least-authority-wrapper’ switches to a read-only root file system. * gnu/build/linux-container.scm (remount-read-only): New procedure. (mount-file-systems): Add #:writable-root? and #:populate-file-system and honor them. (run-container): Likewise. (call-with-container): Likewise. * gnu/system/linux-container.scm (container-script): Pass #:writable-root? to ‘call-with-container’. (eval/container): Add #:populate-file-system and #:writable-root? and honor them. * guix/scripts/environment.scm (launch-environment/container): Pass #:writable-root? to ‘call-with-container’. * guix/scripts/home.scm (spawn-home-container): Likewise. * tests/containers.scm ("call-with-container, mnt namespace, read-only root") ("call-with-container, mnt namespace, writable root"): New tests. Change-Id: I603e2fd08851338b737bb16c8af3f765e2538906 --- gnu/build/linux-container.scm | 38 +++++++++++++++++++++++++++++----- gnu/system/linux-container.scm | 5 +++++ guix/scripts/environment.scm | 1 + guix/scripts/home.scm | 1 + tests/containers.scm | 26 +++++++++++++++++++++++ 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/gnu/build/linux-container.scm b/gnu/build/linux-container.scm index a5c5d8962e..4dcdaa8f33 100644 --- a/gnu/build/linux-container.scm +++ b/gnu/build/linux-container.scm @@ -75,10 +75,16 @@ (define (purify-environment) (match (get-environment-variables) (((names . _) ...) names)))) +(define (remount-read-only mount-point) + (mount mount-point mount-point "none" + (logior MS_BIND MS_REMOUNT MS_RDONLY))) + ;; The container setup procedure closely resembles that of the Docker ;; specification: ;; https://raw.githubusercontent.com/docker/libcontainer/master/SPEC.md -(define* (mount-file-systems root mounts #:key mount-/sys? mount-/proc?) +(define* (mount-file-systems root mounts #:key mount-/sys? mount-/proc? + (populate-file-system (const #t)) + writable-root?) "Mount the essential file systems and the those in MOUNTS, a list of <file-system> objects, relative to ROOT; then make ROOT the new root directory for the process." @@ -177,7 +183,10 @@ (define* (mount-file-systems root mounts #:key mount-/sys? mount-/proc?) (chdir "/") (umount "real-root" MNT_DETACH) (rmdir "real-root") - (chmod "/" #o755))) + (populate-file-system) + (chmod "/" #o755) + (unless writable-root? + (remount-read-only "/")))) (define* (initialize-user-namespace pid host-uids #:key (guest-uid 0) (guest-gid 0)) @@ -226,13 +235,19 @@ (define (namespaces->bit-mask namespaces) namespaces))) (define* (run-container root mounts namespaces host-uids thunk - #:key (guest-uid 0) (guest-gid 0)) + #:key (guest-uid 0) (guest-gid 0) + (populate-file-system (const #t)) + writable-root?) "Run THUNK in a new container process and return its PID. ROOT specifies the root directory for the container. MOUNTS is a list of <file-system> objects that specify file systems to mount inside the container. NAMESPACES is a list of symbols that correspond to the possible Linux namespaces: mnt, ipc, uts, user, and net. +When WRITABLE-ROOT? is false, remount the container's root as read-only before +calling THUNK. Call POPULATE-FILE-SYSTEM before the root is (potentially) +made read-only. + HOST-UIDS specifies the number of host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID specify the first UID (respectively GID) that host UIDs (respectively GIDs) map to in the namespace." @@ -258,7 +273,12 @@ (define* (run-container root mounts namespaces host-uids thunk (mount-file-systems root mounts #:mount-/proc? (memq 'pid namespaces) #:mount-/sys? (memq 'net - namespaces))) + namespaces) + #:populate-file-system + populate-file-system + #:writable-root? + (or writable-root? + (not (memq 'mnt namespaces))))) (lambda args ;; Forward the exception to the parent process. ;; FIXME: SRFI-35 conditions and non-trivial objects @@ -329,6 +349,8 @@ (define* (call-with-container mounts thunk #:key (namespaces %namespaces) (host-uids 1) (guest-uid 0) (guest-gid 0) (relayed-signals (list SIGINT SIGTERM)) (child-is-pid1? #t) + (populate-file-system (const #t)) + writable-root? (process-spawned-hook (const #t))) "Run THUNK in a new container process and return its exit status; call PROCESS-SPAWNED-HOOK with the PID of the new process that has been spawned. @@ -349,6 +371,10 @@ (define* (call-with-container mounts thunk #:key (namespaces %namespaces) RELAYED-SIGNALS is the list of signals that are \"relayed\" to the container process when caught by its parent. +When WRITABLE-ROOT? is false, remount the container's root as read-only before +calling THUNK. Call POPULATE-FILE-SYSTEM before the root is (potentially) +made read-only. + When CHILD-IS-PID1? is true, and if NAMESPACES contains 'pid', then the child process runs directly as PID 1. As such, it is responsible for (1) installing signal handlers and (2) reaping terminated processes by calling 'waitpid'. @@ -402,7 +428,9 @@ (define* (call-with-container mounts thunk #:key (namespaces %namespaces) (lambda (root) (let ((pid (run-container root mounts namespaces host-uids thunk* #:guest-uid guest-uid - #:guest-gid guest-gid))) + #:guest-gid guest-gid + #:populate-file-system populate-file-system + #:writable-root? writable-root?))) (install-signal-handlers pid) (process-spawned-hook pid) (match (waitpid pid) diff --git a/gnu/system/linux-container.scm b/gnu/system/linux-container.scm index 3622328500..e7cb90d091 100644 --- a/gnu/system/linux-container.scm +++ b/gnu/system/linux-container.scm @@ -312,12 +312,15 @@ (define* (container-script os #:key (mappings '()) shared-network?) #:namespaces (if #$shared-network? (delq 'net %namespaces) %namespaces) + #:writable-root? #t #:process-spawned-hook explain))))) (gexp->script "run-container" script))) (define* (eval/container exp #:key + (populate-file-system (const #t)) + writable-root? (mappings '()) (mounts '()) (namespaces %namespaces) @@ -367,6 +370,8 @@ (define* (eval/container exp (list "-c" (object->string (lowered-gexp-sexp lowered)))))) + #:writable-root? writable-root? + #:populate-file-system populate-file-system #:namespaces namespaces #:guest-uid guest-uid #:guest-gid guest-gid)))))) diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 648a497743..4be9807163 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -959,6 +959,7 @@ (define* (launch-environment/container #:key command bash user user-mappings #:emulate-fhs? emulate-fhs?))) #:guest-uid uid #:guest-gid gid + #:writable-root? #t ;for backward compatibility #:namespaces (if network? (delq 'net %namespaces) ; share host network %namespaces))))))) diff --git a/guix/scripts/home.scm b/guix/scripts/home.scm index 56a4b7c7d4..7ce6217324 100644 --- a/guix/scripts/home.scm +++ b/guix/scripts/home.scm @@ -375,6 +375,7 @@ (define* (spawn-home-container home (type "tmpfs") (check? #f))) #:mappings (append network-mappings mappings) + #:writable-root? #t #:guest-uid uid #:guest-gid gid)) diff --git a/tests/containers.scm b/tests/containers.scm index 70d5ba2d30..1e915d517e 100644 --- a/tests/containers.scm +++ b/tests/containers.scm @@ -142,6 +142,32 @@ (define (skip-if-unsupported) (assert-exit (= #o755 (stat:perms (lstat "/"))))) #:namespaces '(user mnt)))) +(skip-if-unsupported) +(test-assert "call-with-container, mnt namespace, read-only root" + (zero? + (call-with-container '() + (lambda () + (assert-exit (and (file-is-directory? "/witness") + (catch 'system-error + (lambda () + (mkdir "/whatever") + #f) + (lambda args + (= (system-error-errno args) EROFS)))))) + #:populate-file-system (lambda () + (mkdir "/witness")) + #:namespaces '(user mnt)))) + +(skip-if-unsupported) +(test-assert "call-with-container, mnt namespace, writable root" + (zero? + (call-with-container '() + (lambda () + (mkdir "whatever") + (assert-exit (file-is-directory? "/whatever"))) + #:writable-root? #t + #:namespaces '(user mnt)))) + (skip-if-unsupported) (test-assert "container-excursion" (call-with-temporary-directory -- 2.49.0
guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, maxim.cournoyer <at> gmail.com, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:27:05 GMT) Full text and rfc822 format available.Message #23 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 5/8] environment: Add ‘--writable-root’ and default to read-only root. Date: Tue, 8 Apr 2025 14:24:45 +0200
This is an incompatible change where the root file system in ‘guix shell -C’ is now read-only by default. * guix/scripts/environment.scm (show-environment-options-help) (%options): Add ‘--writable-root’. * guix/scripts/environment.scm (setup-fhs): Invoke /sbin/ldconfig; moved from… (launch-environment): … here. (launch-environment/container): Add #:writable-root? and pass it to ‘call-with-container’. Move root file system setup to #:populate-file-system. (guix-environment*): Honor ‘--writable-root’. * tests/guix-environment-container.sh: Test it. * doc/guix.texi (Invoking guix shell): Document ‘--writable-root’. (Debugging Build Failures): Mention it before “rm /bin/sh”. Change-Id: I2e8517d6f01eb8093160bffc0f9f56071ad6fee6 --- doc/guix.texi | 7 ++- guix/scripts/environment.scm | 98 +++++++++++++++++------------ tests/guix-environment-container.sh | 11 +++- 3 files changed, 73 insertions(+), 43 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index 3d91dfd7b1..44ead7148b 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -6401,6 +6401,10 @@ Invoking guix shell be automatically shared and will change to the user's home directory within the container instead. See also @option{--user}. +@item --writable-root +When using @option{--container}, this option makes the root file system +writable (it is read-only by default). + @item --expose=@var{source}[=@var{target}] @itemx --share=@var{source}[=@var{target}] For containers, @option{--expose} (resp. @option{--share}) exposes the @@ -14043,7 +14047,8 @@ Debugging Build Failures info on grafts). To get closer to a container like that used by the build daemon, we can -remove @file{/bin/sh}: +remove @file{/bin/sh} (you'll first need to pass the +@option{--writable-root} option to @command{guix shell}): @example [env]# rm /bin/sh diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 4be9807163..8f3bea8c30 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2014, 2015, 2018 David Thompson <davet <at> gnu.org> -;;; Copyright © 2015-2024 Ludovic Courtès <ludo <at> gnu.org> +;;; Copyright © 2015-2025 Ludovic Courtès <ludo <at> gnu.org> ;;; Copyright © 2018 Mike Gerwitz <mtg <at> gnu.org> ;;; Copyright © 2022, 2023 John Kehayias <john.kehayias <at> protonmail.com> ;;; @@ -120,6 +120,8 @@ (define (show-environment-options-help) (display (G_ " --no-cwd do not share current working directory with an isolated container")) + (display (G_ " + --writable-root make the container's root file system writable")) (display (G_ " --share=SPEC for containers, share writable host file system @@ -261,6 +263,9 @@ (define %options (option '("no-cwd") #f #f (lambda (opt name arg result) (alist-cons 'no-cwd? #t result))) + (option '("writable-root") #f #f + (lambda (opt name arg result) + (alist-cons 'writable-root? #t result))) (option '("share") #t #f (lambda (opt name arg result) (alist-cons 'file-system-mapping @@ -483,7 +488,10 @@ (define (setup-fhs profile) (newline port)) ;; /lib/nss is needed as Guix's nss puts libraries ;; there rather than in the lib directory. - '("/lib" "/lib/nss"))))) + '("/lib" "/lib/nss")))) + + ;; Create /etc/ld.so.cache. + (invoke "/sbin/ldconfig" "-X")) (define (status->exit-code status) "Compute the exit code made from STATUS, a value as returned by 'waitpid', @@ -525,8 +533,7 @@ (define* (launch-environment command profile manifest (setenv "PATH" (string-append "/bin:/usr/bin:/sbin:/usr/sbin" (if (getenv "PATH") (string-append ":" (getenv "PATH")) - ""))) - (invoke "ldconfig" "-X")) + "")))) (apply execlp program program args)) (lambda _ ;; Report the error from here because the parent process cannot @@ -733,6 +740,7 @@ (define* (launch-environment/fork command profile manifest (define* (launch-environment/container #:key command bash user user-mappings profile manifest link-profile? network? map-cwd? emulate-fhs? nesting? + writable-root? (setup-hook #f) (symlinks '()) (white-list '())) "Run COMMAND within a container that features the software in PROFILE. @@ -879,15 +887,9 @@ (define* (launch-environment/container #:key command bash user user-mappings (exit/status (call-with-container file-systems (lambda () - ;; Setup global shell. - (mkdir-p "/bin") - (symlink bash "/bin/sh") - ;; Set a reasonable default PS1. (setenv "PS1" "\\u@\\h \\w [env]\\$ ") - ;; Setup directory for temporary files. - (mkdir-p "/tmp") (for-each (lambda (var) (setenv var "/tmp")) ;; The same variables as in Nix's 'build.cc'. @@ -897,9 +899,44 @@ (define* (launch-environment/container #:key command bash user user-mappings (setenv "LOGNAME" logname) (setenv "USER" logname) + (setenv "HOME" home-dir) + + (unless network? + ;; Allow local AF_INET communications. + (set-network-interface-up "lo")) + + ;; For convenience, start in the user's current working + ;; directory or, if unmapped, the home directory. + (chdir (if map-cwd? + (override-user-dir user home cwd) + home-dir)) + + ;; Set environment variables that match WHITE-LIST. + (for-each (match-lambda + ((variable . value) + (setenv variable value))) + environ) + + (primitive-exit/status + ;; A container's environment is already purified, so no need to + ;; request it be purified again. + (launch-environment command + (if link-profile? + (string-append home-dir "/.guix-profile") + profile) + manifest #:pure? #f + #:emulate-fhs? emulate-fhs?))) + #:populate-file-system + (lambda () + ;; Setup global shell. + (mkdir-p "/bin") + (symlink bash "/bin/sh") + + ;; Setup directory for temporary files. + (mkdir-p "/tmp") + ;; Create a dummy home directory. (mkdir-p home-dir) - (setenv "HOME" home-dir) ;; Create symlinks. (let ((symlink->directives @@ -910,10 +947,6 @@ (define* (launch-environment/container #:key command bash user user-mappings (for-each (cut evaluate-populate-directive <> ".") (append-map symlink->directives symlinks))) - ;; Call an additional setup procedure, if provided. - (when setup-hook - (setup-hook profile)) - ;; If requested, link $GUIX_ENVIRONMENT to $HOME/.guix-profile; ;; this allows programs expecting that path to continue working as ;; expected within a container. @@ -931,35 +964,14 @@ (define* (launch-environment/container #:key command bash user user-mappings ;; to resolve "localhost". (call-with-output-file "/etc/hosts" (lambda (port) - (display "127.0.0.1 localhost\n" port))) + (display "127.0.0.1 localhost\n" port)))) - ;; Allow local AF_INET communications. - (set-network-interface-up "lo")) - - ;; For convenience, start in the user's current working - ;; directory or, if unmapped, the home directory. - (chdir (if map-cwd? - (override-user-dir user home cwd) - home-dir)) - - ;; Set environment variables that match WHITE-LIST. - (for-each (match-lambda - ((variable . value) - (setenv variable value))) - environ) - - (primitive-exit/status - ;; A container's environment is already purified, so no need to - ;; request it be purified again. - (launch-environment command - (if link-profile? - (string-append home-dir "/.guix-profile") - profile) - manifest #:pure? #f - #:emulate-fhs? emulate-fhs?))) + ;; Call an additional setup procedure, if provided. + (when setup-hook + (setup-hook profile))) #:guest-uid uid #:guest-gid gid - #:writable-root? #t ;for backward compatibility + #:writable-root? writable-root? #:namespaces (if network? (delq 'net %namespaces) ; share host network %namespaces))))))) @@ -1087,6 +1099,7 @@ (define (guix-environment* opts) (symlinks (assoc-ref opts 'symlinks)) (network? (assoc-ref opts 'network?)) (no-cwd? (assoc-ref opts 'no-cwd?)) + (writable-root? (assoc-ref opts 'writable-root?)) (emulate-fhs? (assoc-ref opts 'emulate-fhs?)) (nesting? (assoc-ref opts 'nesting?)) (user (assoc-ref opts 'user)) @@ -1134,6 +1147,8 @@ (define (guix-environment* opts) (leave (G_ "'--user' cannot be used without '--container'~%"))) (when no-cwd? (leave (G_ "--no-cwd cannot be used without '--container'~%"))) + (when writable-root? + (leave (G_ "'--writable-root' cannot be used without '--container'~%"))) (when emulate-fhs? (leave (G_ "'--emulate-fhs' cannot be used without '--container'~%"))) (when nesting? @@ -1219,6 +1234,7 @@ (define (guix-environment* opts) #:link-profile? link-prof? #:network? network? #:map-cwd? (not no-cwd?) + #:writable-root? writable-root? #:emulate-fhs? emulate-fhs? #:nesting? nesting? #:symlinks symlinks diff --git a/tests/guix-environment-container.sh b/tests/guix-environment-container.sh index 09704f751c..d6cb382de9 100644 --- a/tests/guix-environment-container.sh +++ b/tests/guix-environment-container.sh @@ -1,7 +1,7 @@ # GNU Guix --- Functional package management for GNU # Copyright © 2015 David Thompson <davet <at> gnu.org> # Copyright © 2022, 2023 John Kehayias <john.kehayias <at> protonmail.com> -# Copyright © 2023 Ludovic Courtès <ludo <at> gnu.org> +# Copyright © 2023, 2025 Ludovic Courtès <ludo <at> gnu.org> # # This file is part of GNU Guix. # @@ -186,6 +186,15 @@ HOME="$tmpdir" guix environment --bootstrap --container --user=foognu \ -- /bin/sh -c 'test $(pwd) == "/home/foo" -a ! -d '"$tmpdir" ) +# Check that the root file system is read-only by default... +guix environment --bootstrap --container --ad-hoc guile-bootstrap \ + -- guile -c '(mkdir "/whatever")' && false + +# ... and can be made writable. +guix environment --bootstrap --container --ad-hoc guile-bootstrap \ + --writable-root \ + -- guile -c '(mkdir "/whatever")' + # Check the exit code. abnormal_exit_code=" -- 2.49.0
andrew <at> trop.in, guix <at> cbaines.net, janneke <at> gnu.org, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, tanguy <at> bioneland.org, me <at> tobias.gr, guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:27:06 GMT) Full text and rfc822 format available.Message #26 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 7/8] linux-container: Set up “lo” and generate /etc/hosts by default. Date: Tue, 8 Apr 2025 14:24:47 +0200
* gnu/build/linux-container.scm (run-container): Add #:loopback-network? and honor it via #:populate-file-system. (call-with-container): Add #:loopback-network? and pass it to ‘run-container’. * guix/scripts/environment.scm (launch-environment/container): Remove call to ‘set-network-interface-up’ and remove generation of /etc/hosts. * guix/scripts/home.scm (spawn-home-container): Likewise. Change-Id: I5933a4e8dc6d8e19235a79696b62299d74d1ba21 --- gnu/build/linux-container.scm | 25 ++++++++++++++++++++++++- guix/scripts/environment.scm | 11 ----------- guix/scripts/home.scm | 15 ++------------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/gnu/build/linux-container.scm b/gnu/build/linux-container.scm index 4dcdaa8f33..345ce2de08 100644 --- a/gnu/build/linux-container.scm +++ b/gnu/build/linux-container.scm @@ -237,6 +237,7 @@ (define (namespaces->bit-mask namespaces) (define* (run-container root mounts namespaces host-uids thunk #:key (guest-uid 0) (guest-gid 0) (populate-file-system (const #t)) + (loopback-network? #t) writable-root?) "Run THUNK in a new container process and return its PID. ROOT specifies the root directory for the container. MOUNTS is a list of <file-system> @@ -244,6 +245,9 @@ (define* (run-container root mounts namespaces host-uids thunk is a list of symbols that correspond to the possible Linux namespaces: mnt, ipc, uts, user, and net. +When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the +loopback device (\"lo\") and a minimal /etc/hosts. + When WRITABLE-ROOT? is false, remount the container's root as read-only before calling THUNK. Call POPULATE-FILE-SYSTEM before the root is (potentially) made read-only. @@ -275,7 +279,21 @@ (define* (run-container root mounts namespaces host-uids thunk #:mount-/sys? (memq 'net namespaces) #:populate-file-system - populate-file-system + (lambda () + (populate-file-system) + (when (and (memq 'net namespaces) + loopback-network?) + (set-network-interface-up "lo") + + ;; When isolated from the + ;; network, provide a minimal + ;; /etc/hosts to resolve + ;; "localhost". + (mkdir-p "/etc") + (call-with-output-file "/etc/hosts" + (lambda (port) + (display "127.0.0.1 localhost\n" port) + (chmod port #o444))))) #:writable-root? (or writable-root? (not (memq 'mnt namespaces))))) @@ -350,6 +368,7 @@ (define* (call-with-container mounts thunk #:key (namespaces %namespaces) (relayed-signals (list SIGINT SIGTERM)) (child-is-pid1? #t) (populate-file-system (const #t)) + (loopback-network? #t) writable-root? (process-spawned-hook (const #t))) "Run THUNK in a new container process and return its exit status; call @@ -371,6 +390,9 @@ (define* (call-with-container mounts thunk #:key (namespaces %namespaces) RELAYED-SIGNALS is the list of signals that are \"relayed\" to the container process when caught by its parent. +When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the +loopback device (\"lo\") and a minimal /etc/hosts. + When WRITABLE-ROOT? is false, remount the container's root as read-only before calling THUNK. Call POPULATE-FILE-SYSTEM before the root is (potentially) made read-only. @@ -430,6 +452,7 @@ (define* (call-with-container mounts thunk #:key (namespaces %namespaces) #:guest-uid guest-uid #:guest-gid guest-gid #:populate-file-system populate-file-system + #:loopback-network? loopback-network? #:writable-root? writable-root?))) (install-signal-handlers pid) (process-spawned-hook pid) diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 8f3bea8c30..ddd34394dd 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -901,10 +901,6 @@ (define* (launch-environment/container #:key command bash user user-mappings (setenv "HOME" home-dir) - (unless network? - ;; Allow local AF_INET communications. - (set-network-interface-up "lo")) - ;; For convenience, start in the user's current working ;; directory or, if unmapped, the home directory. (chdir (if map-cwd? @@ -959,13 +955,6 @@ (define* (launch-environment/container #:key command bash user user-mappings (write-passwd (list passwd)) (write-group groups) - (unless network? - ;; When isolated from the network, provide a minimal /etc/hosts - ;; to resolve "localhost". - (call-with-output-file "/etc/hosts" - (lambda (port) - (display "127.0.0.1 localhost\n" port)))) - ;; Call an additional setup procedure, if provided. (when setup-hook (setup-hook profile))) diff --git a/guix/scripts/home.scm b/guix/scripts/home.scm index 6fcb0ca382..fbf6d670ac 100644 --- a/guix/scripts/home.scm +++ b/guix/scripts/home.scm @@ -288,14 +288,11 @@ (define* (spawn-home-container home (with-imported-modules `(((guix config) => ,(make-config.scm)) ,@(source-module-closure '((guix profiles) - (guix build utils) - (guix build syscalls)) + (guix build utils)) #:select? not-config?)) #~(begin (use-modules (guix build utils) - ((guix profiles) #:select (load-profile)) - ((guix build syscalls) - #:select (set-network-interface-up))) + ((guix profiles) #:select (load-profile))) (define shell #$(user-shell)) @@ -347,14 +344,6 @@ (define* (spawn-home-container home (write-passwd (list passwd)) (write-group groups) - (unless network? - ;; When isolated from the network, provide a minimal /etc/hosts - ;; to resolve "localhost". - (call-with-output-file "/etc/hosts" - (lambda (port) - (display "127.0.0.1 localhost\n" port) - (chmod port #o444)))) - ;; Create /tmp; bits of code expect it, such as ;; 'least-authority-wrapper'. (mkdir-p "/tmp")) -- 2.49.0
guix-patches <at> gnu.org
:bug#77638
; Package guix-patches
.
(Tue, 08 Apr 2025 12:27:06 GMT) Full text and rfc822 format available.Message #29 received at 77638 <at> debbugs.gnu.org (full text, mbox):
From: Ludovic Courtès <ludo <at> gnu.org> To: 77638 <at> debbugs.gnu.org Cc: Ludovic Courtès <ludo <at> gnu.org> Subject: [PATCH 8/8] linux-container: Lock mounts by default. Date: Tue, 8 Apr 2025 14:24:48 +0200
This makes it impossible to unmount or remount things from within ‘call-with-container’. * gnu/build/linux-container.scm (initialize-user-namespace): Add #:host-uid and #:host-gid. and honor them. (run-container): Add #:lock-mounts?. Honor it by calling ‘unshare’ followed by ‘initialize-user-namespace’. (call-with-container): Add #:lock-mounts? and pass it down. (container-excursion): Get the user namespace owning the PID namespace and join it, then join the remaining namespaces. * tests/containers.scm ("call-with-container, mnt namespace, locked mounts"): New test. ("container-excursion"): Pass #:lock-mounts? #f. Change-Id: I13be982aef99e68a653d472f0e595c81cfcfa392 --- gnu/build/linux-container.scm | 111 ++++++++++++++++++++++------------ tests/containers.scm | 33 ++++++++-- 2 files changed, 103 insertions(+), 41 deletions(-) diff --git a/gnu/build/linux-container.scm b/gnu/build/linux-container.scm index 345ce2de08..51f04bc249 100644 --- a/gnu/build/linux-container.scm +++ b/gnu/build/linux-container.scm @@ -189,7 +189,10 @@ (define* (mount-file-systems root mounts #:key mount-/sys? mount-/proc? (remount-read-only "/")))) (define* (initialize-user-namespace pid host-uids - #:key (guest-uid 0) (guest-gid 0)) + #:key + (host-uid (getuid)) + (host-gid (getgid)) + (guest-uid 0) (guest-gid 0)) "Configure the user namespace for PID. HOST-UIDS specifies the number of host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID specify the first UID (respectively GID) that host UIDs (respectively GIDs) @@ -200,24 +203,21 @@ (define* (initialize-user-namespace pid host-uids (define (scope file) (string-append proc-dir file)) - (let ((uid (getuid)) - (gid (getgid))) - - ;; Only root can write to the gid map without first disabling the - ;; setgroups syscall. - (unless (and (zero? uid) (zero? gid)) - (call-with-output-file (scope "/setgroups") - (lambda (port) - (display "deny" port)))) - - ;; Map the user/group that created the container to the root user - ;; within the container. - (call-with-output-file (scope "/uid_map") + ;; Only root can write to the gid map without first disabling the + ;; setgroups syscall. + (unless (and (zero? host-uid) (zero? host-gid)) + (call-with-output-file (scope "/setgroups") (lambda (port) - (format port "~d ~d ~d" guest-uid uid host-uids))) - (call-with-output-file (scope "/gid_map") - (lambda (port) - (format port "~d ~d ~d" guest-gid gid host-uids))))) + (display "deny" port)))) + + ;; Map the user/group that created the container to the root user + ;; within the container. + (call-with-output-file (scope "/uid_map") + (lambda (port) + (format port "~d ~d ~d" guest-uid host-uid host-uids))) + (call-with-output-file (scope "/gid_map") + (lambda (port) + (format port "~d ~d ~d" guest-gid host-gid host-uids)))) (define (namespaces->bit-mask namespaces) "Return the number suitable for the 'flags' argument of 'clone' that @@ -238,12 +238,14 @@ (define* (run-container root mounts namespaces host-uids thunk #:key (guest-uid 0) (guest-gid 0) (populate-file-system (const #t)) (loopback-network? #t) + (lock-mounts? #t) writable-root?) "Run THUNK in a new container process and return its PID. ROOT specifies the root directory for the container. MOUNTS is a list of <file-system> objects that specify file systems to mount inside the container. NAMESPACES is a list of symbols that correspond to the possible Linux namespaces: mnt, -ipc, uts, user, and net. +ipc, uts, user, and net. When LOCK-MOUNTS? is true, arrange so that none of +MOUNTS can be unmounted or remounted individually from within THUNK. When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the loopback device (\"lo\") and a minimal /etc/hosts. @@ -303,6 +305,28 @@ (define* (run-container root mounts namespaces host-uids thunk ;; cannot be 'read' so they shouldn't be written as is. (write args child) (primitive-exit 3)))) + + (when (and lock-mounts? + (memq 'mnt namespaces) + (memq 'user namespaces)) + ;; Create a new mount namespace owned by a new user + ;; namespace to "lock" together previous mounts, such that + ;; they cannot be unmounted or remounted separately--see + ;; mount_namespaces(7). + ;; + ;; Note: at this point, the process is single-threaded (no + ;; GC mark threads, no finalization thread, etc.) which is + ;; why unshare(CLONE_NEWUSER) can be used. + (let ((uid (getuid)) (gid (getgid))) + (unshare (logior CLONE_NEWUSER CLONE_NEWNS)) + (when (file-exists? "/proc/self") + (initialize-user-namespace (getpid) + host-uids + #:host-uid uid + #:host-gid gid + #:guest-uid guest-uid + #:guest-gid guest-gid)))) + ;; TODO: Manage capabilities. (write 'ready child) (close-port child) @@ -365,6 +389,7 @@ (define (status->exit-status status) (define* (call-with-container mounts thunk #:key (namespaces %namespaces) (host-uids 1) (guest-uid 0) (guest-gid 0) + (lock-mounts? #t) (relayed-signals (list SIGINT SIGTERM)) (child-is-pid1? #t) (populate-file-system (const #t)) @@ -449,6 +474,7 @@ (define* (call-with-container mounts thunk #:key (namespaces %namespaces) (call-with-temporary-directory (lambda (root) (let ((pid (run-container root mounts namespaces host-uids thunk* + #:lock-mounts? lock-mounts? #:guest-uid guest-uid #:guest-gid guest-gid #:populate-file-system populate-file-system @@ -469,24 +495,35 @@ (define (container-excursion pid thunk) (0 (call-with-clean-exit (lambda () - (for-each (lambda (ns) - (let ((source (namespace-file (getpid) ns)) - (target (namespace-file pid ns))) - ;; Joining the namespace that the process already - ;; belongs to would throw an error so avoid that. - ;; XXX: This /proc interface leads to TOCTTOU. - (unless (string=? (readlink source) (readlink target)) - (call-with-input-file source - (lambda (current-ns-port) - (call-with-input-file target - (lambda (new-ns-port) - (setns (fileno new-ns-port) 0)))))))) - ;; It's important that the user namespace is joined first, - ;; so that the user will have the privileges to join the - ;; other namespaces. Furthermore, it's important that the - ;; mount namespace is joined last, otherwise the /proc mount - ;; point would no longer be accessible. - '("user" "ipc" "uts" "net" "pid" "mnt")) + ;; First, determine the user namespace that owns the pid namespace and + ;; join that user namespace (the assumption is that it also owns all + ;; the other namespaces). It's important that the user namespace is + ;; joined first, so that the user will have the privileges to join the + ;; other namespaces. + (let* ((pid-ns (open-fdes (namespace-file pid "pid") + (logior O_CLOEXEC O_RDONLY))) + (user-ns (get-user-ns pid-ns))) + (close-fdes pid-ns) + (unless (equal? (stat user-ns) + (stat (namespace-file (getpid) "user"))) + (setns user-ns 0)) + (close-fdes user-ns) + + ;; Then join all the remaining namespaces. + (for-each (lambda (ns) + (let ((source (namespace-file (getpid) ns)) + (target (namespace-file pid ns))) + ;; Joining the namespace that the process already + ;; belongs to would throw an error so avoid that. + ;; XXX: This /proc interface leads to TOCTTOU. + (unless (string=? (readlink source) (readlink target)) + (call-with-input-file target + (lambda (new-ns-port) + (setns (fileno new-ns-port) 0)))))) + ;; It's important that the mount namespace is joined last, + ;; otherwise the /proc mount point would no longer be + ;; accessible. + '("ipc" "uts" "net" "pid" "mnt"))) (purify-environment) (chdir "/") diff --git a/tests/containers.scm b/tests/containers.scm index 1e915d517e..6edea9631d 100644 --- a/tests/containers.scm +++ b/tests/containers.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2015 David Thompson <davet <at> gnu.org> -;;; Copyright © 2016, 2017, 2019, 2023 Ludovic Courtès <ludo <at> gnu.org> +;;; Copyright © 2016-2017, 2019, 2023, 2025 Ludovic Courtès <ludo <at> gnu.org> ;;; ;;; This file is part of GNU Guix. ;;; @@ -110,6 +110,26 @@ (define (skip-if-unsupported) (assert-exit (file-exists? "/testing"))) #:namespaces '(user mnt)))) +(skip-if-unsupported) +(test-equal "call-with-container, mnt namespace, locked mounts" + EINVAL + ;; umount(2) fails with EINVAL when targeting a mount point that is + ;; "locked". + (status:exit-val + (call-with-container (list (file-system + (device "none") + (mount-point "/testing") + (type "tmpfs") + (check? #f))) + (lambda () + (primitive-exit (catch 'system-error + (lambda () + (umount "/testing") + 0) + (lambda args + (system-error-errno args))))) + #:namespaces '(user mnt)))) + (skip-if-unsupported) (test-equal "call-with-container, mnt namespace, wrong bind mount" `(system-error ,ENOENT) @@ -169,7 +189,8 @@ (define (skip-if-unsupported) #:namespaces '(user mnt)))) (skip-if-unsupported) -(test-assert "container-excursion" +(test-equal "container-excursion" + 0 (call-with-temporary-directory (lambda (root) ;; Two pipes: One for the container to signal that the test can begin, @@ -193,7 +214,11 @@ (define (skip-if-unsupported) (readlink (string-append "/proc/" pid "/ns/" ns))) '("user" "ipc" "uts" "net" "pid" "mnt")))) - (let* ((pid (run-container root '() %namespaces 1 container)) + (let* ((pid (run-container root '() %namespaces 1 container + ;; Do not lock mounts so the user namespace + ;; appears to be the same seen from inside + ;; and from outside. + #:lock-mounts? #f)) (container-namespaces (namespaces pid)) (result (begin @@ -213,7 +238,7 @@ (define (skip-if-unsupported) (write 'done end-out) (close end-out) (waitpid pid) - (zero? result))))))) + result)))))) (skip-if-unsupported) (test-equal "container-excursion, same namespaces" -- 2.49.0
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.