summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Cournoyer <maxim@guixotic.coop>2025-10-16 16:36:43 +0900
committerMaxim Cournoyer <maxim@guixotic.coop>2025-10-29 11:34:28 +0900
commit10edda5aa5f92e7bfcff4902d4f3c11373d1e725 (patch)
tree95cff7d390183e858722a085d13fb00a5a67863f
parentd0144544ff38c62ee92b6f3b6ee3e6aa6aede812 (diff)
Reinstate "linux-container: Remove #:lock-mounts? and related code."
This reverts commit e0e64be8de3d220a12612b3a2e4aee428277d865.
-rw-r--r--gnu/build/linux-container.scm111
-rw-r--r--gnu/system/linux-container.scm4
-rw-r--r--tests/containers.scm33
3 files changed, 107 insertions, 41 deletions
diff --git a/gnu/build/linux-container.scm b/gnu/build/linux-container.scm
index 0df51c390b5..b6f8563f7d0 100644
--- a/gnu/build/linux-container.scm
+++ b/gnu/build/linux-container.scm
@@ -190,7 +190,10 @@ for the process."
190 (remount-read-only "/")))) 190 (remount-read-only "/"))))
191 191
192(define* (initialize-user-namespace pid host-uids 192(define* (initialize-user-namespace pid host-uids
193 #:key (guest-uid 0) (guest-gid 0)) 193 #:key
194 (host-uid (getuid))
195 (host-gid (getgid))
196 (guest-uid 0) (guest-gid 0))
194 "Configure the user namespace for PID. HOST-UIDS specifies the number of 197 "Configure the user namespace for PID. HOST-UIDS specifies the number of
195host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID 198host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID
196specify the first UID (respectively GID) that host UIDs (respectively GIDs) 199specify the first UID (respectively GID) that host UIDs (respectively GIDs)
@@ -201,24 +204,21 @@ map to in the namespace."
201 (define (scope file) 204 (define (scope file)
202 (string-append proc-dir file)) 205 (string-append proc-dir file))
203 206
204 (let ((uid (getuid)) 207 ;; Only root can write to the gid map without first disabling the
205 (gid (getgid))) 208 ;; setgroups syscall.
206 209 (unless (and (zero? host-uid) (zero? host-gid))
207 ;; Only root can write to the gid map without first disabling the 210 (call-with-output-file (scope "/setgroups")
208 ;; setgroups syscall.
209 (unless (and (zero? uid) (zero? gid))
210 (call-with-output-file (scope "/setgroups")
211 (lambda (port)
212 (display "deny" port))))
213
214 ;; Map the user/group that created the container to the root user
215 ;; within the container.
216 (call-with-output-file (scope "/uid_map")
217 (lambda (port)
218 (format port "~d ~d ~d" guest-uid uid host-uids)))
219 (call-with-output-file (scope "/gid_map")
220 (lambda (port) 211 (lambda (port)
221 (format port "~d ~d ~d" guest-gid gid host-uids))))) 212 (display "deny" port))))
213
214 ;; Map the user/group that created the container to the root user
215 ;; within the container.
216 (call-with-output-file (scope "/uid_map")
217 (lambda (port)
218 (format port "~d ~d ~d" guest-uid host-uid host-uids)))
219 (call-with-output-file (scope "/gid_map")
220 (lambda (port)
221 (format port "~d ~d ~d" guest-gid host-gid host-uids))))
222 222
223(define (namespaces->bit-mask namespaces) 223(define (namespaces->bit-mask namespaces)
224 "Return the number suitable for the 'flags' argument of 'clone' that 224 "Return the number suitable for the 'flags' argument of 'clone' that
@@ -239,12 +239,14 @@ corresponds to the symbols in NAMESPACES."
239 #:key (guest-uid 0) (guest-gid 0) 239 #:key (guest-uid 0) (guest-gid 0)
240 (populate-file-system (const #t)) 240 (populate-file-system (const #t))
241 (loopback-network? #t) 241 (loopback-network? #t)
242 (lock-mounts? #t)
242 writable-root?) 243 writable-root?)
243 "Run THUNK in a new container process and return its PID. ROOT specifies 244 "Run THUNK in a new container process and return its PID. ROOT specifies
244the root directory for the container. MOUNTS is a list of <file-system> 245the root directory for the container. MOUNTS is a list of <file-system>
245objects that specify file systems to mount inside the container. NAMESPACES 246objects that specify file systems to mount inside the container. NAMESPACES
246is a list of symbols that correspond to the possible Linux namespaces: mnt, 247is a list of symbols that correspond to the possible Linux namespaces: mnt,
247ipc, uts, user, and net. 248ipc, uts, user, and net. When LOCK-MOUNTS? is true, arrange so that none of
249MOUNTS can be unmounted or remounted individually from within THUNK.
248 250
249When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the 251When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the
250loopback device (\"lo\") and a minimal /etc/hosts. 252loopback device (\"lo\") and a minimal /etc/hosts.
@@ -304,6 +306,28 @@ that host UIDs (respectively GIDs) map to in the namespace."
304 ;; cannot be 'read' so they shouldn't be written as is. 306 ;; cannot be 'read' so they shouldn't be written as is.
305 (write args child) 307 (write args child)
306 (primitive-exit 3)))) 308 (primitive-exit 3))))
309
310 (when (and lock-mounts?
311 (memq 'mnt namespaces)
312 (memq 'user namespaces))
313 ;; Create a new mount namespace owned by a new user
314 ;; namespace to "lock" together previous mounts, such that
315 ;; they cannot be unmounted or remounted separately--see
316 ;; mount_namespaces(7).
317 ;;
318 ;; Note: at this point, the process is single-threaded (no
319 ;; GC mark threads, no finalization thread, etc.) which is
320 ;; why unshare(CLONE_NEWUSER) can be used.
321 (let ((uid (getuid)) (gid (getgid)))
322 (unshare (logior CLONE_NEWUSER CLONE_NEWNS))
323 (when (file-exists? "/proc/self")
324 (initialize-user-namespace (getpid)
325 host-uids
326 #:host-uid uid
327 #:host-gid gid
328 #:guest-uid guest-uid
329 #:guest-gid guest-gid))))
330
307 ;; TODO: Manage capabilities. 331 ;; TODO: Manage capabilities.
308 (write 'ready child) 332 (write 'ready child)
309 (close-port child) 333 (close-port child)
@@ -376,6 +400,7 @@ if there are no child processes left."
376 400
377(define* (call-with-container mounts thunk #:key (namespaces %namespaces) 401(define* (call-with-container mounts thunk #:key (namespaces %namespaces)
378 (host-uids 1) (guest-uid 0) (guest-gid 0) 402 (host-uids 1) (guest-uid 0) (guest-gid 0)
403 (lock-mounts? #t)
379 (relayed-signals (list SIGINT SIGTERM)) 404 (relayed-signals (list SIGINT SIGTERM))
380 (child-is-pid1? #t) 405 (child-is-pid1? #t)
381 (populate-file-system (const #t)) 406 (populate-file-system (const #t))
@@ -460,6 +485,7 @@ load path must be adjusted as needed."
460 (call-with-temporary-directory 485 (call-with-temporary-directory
461 (lambda (root) 486 (lambda (root)
462 (let ((pid (run-container root mounts namespaces host-uids thunk* 487 (let ((pid (run-container root mounts namespaces host-uids thunk*
488 #:lock-mounts? lock-mounts?
463 #:guest-uid guest-uid 489 #:guest-uid guest-uid
464 #:guest-gid guest-gid 490 #:guest-gid guest-gid
465 #:populate-file-system populate-file-system 491 #:populate-file-system populate-file-system
@@ -480,24 +506,35 @@ return the exit status, an integer as returned by 'waitpid'."
480 (0 506 (0
481 (call-with-clean-exit 507 (call-with-clean-exit
482 (lambda () 508 (lambda ()
483 (for-each (lambda (ns) 509 ;; First, determine the user namespace that owns the pid namespace and
484 (let ((source (namespace-file (getpid) ns)) 510 ;; join that user namespace (the assumption is that it also owns all
485 (target (namespace-file pid ns))) 511 ;; the other namespaces). It's important that the user namespace is
486 ;; Joining the namespace that the process already 512 ;; joined first, so that the user will have the privileges to join the
487 ;; belongs to would throw an error so avoid that. 513 ;; other namespaces.
488 ;; XXX: This /proc interface leads to TOCTTOU. 514 (let* ((pid-ns (open-fdes (namespace-file pid "pid")
489 (unless (string=? (readlink source) (readlink target)) 515 (logior O_CLOEXEC O_RDONLY)))
490 (call-with-input-file source 516 (user-ns (get-user-ns pid-ns)))
491 (lambda (current-ns-port) 517 (close-fdes pid-ns)
492 (call-with-input-file target 518 (unless (equal? (stat user-ns)
493 (lambda (new-ns-port) 519 (stat (namespace-file (getpid) "user")))
494 (setns (fileno new-ns-port) 0)))))))) 520 (setns user-ns 0))
495 ;; It's important that the user namespace is joined first, 521 (close-fdes user-ns)
496 ;; so that the user will have the privileges to join the 522
497 ;; other namespaces. Furthermore, it's important that the 523 ;; Then join all the remaining namespaces.
498 ;; mount namespace is joined last, otherwise the /proc mount 524 (for-each (lambda (ns)
499 ;; point would no longer be accessible. 525 (let ((source (namespace-file (getpid) ns))
500 '("user" "ipc" "uts" "net" "pid" "mnt")) 526 (target (namespace-file pid ns)))
527 ;; Joining the namespace that the process already
528 ;; belongs to would throw an error so avoid that.
529 ;; XXX: This /proc interface leads to TOCTTOU.
530 (unless (string=? (readlink source) (readlink target))
531 (call-with-input-file target
532 (lambda (new-ns-port)
533 (setns (fileno new-ns-port) 0))))))
534 ;; It's important that the mount namespace is joined last,
535 ;; otherwise the /proc mount point would no longer be
536 ;; accessible.
537 '("ipc" "uts" "net" "pid" "mnt")))
501 (purify-environment) 538 (purify-environment)
502 (chdir "/") 539 (chdir "/")
503 540
diff --git a/gnu/system/linux-container.scm b/gnu/system/linux-container.scm
index 9bcdf24a7e0..d16d1e78b56 100644
--- a/gnu/system/linux-container.scm
+++ b/gnu/system/linux-container.scm
@@ -317,6 +317,10 @@ Run the container with the given options."))
317 #:namespaces (if #$shared-network? 317 #:namespaces (if #$shared-network?
318 (delq 'net %namespaces) 318 (delq 'net %namespaces)
319 %namespaces) 319 %namespaces)
320
321 ;; XXX: Work around <https://issues.guix.gnu.org/78356>.
322 #:lock-mounts? #f
323
320 #:writable-root? #t 324 #:writable-root? #t
321 #:process-spawned-hook explain))))) 325 #:process-spawned-hook explain)))))
322 326
diff --git a/tests/containers.scm b/tests/containers.scm
index 1e915d517e8..6edea9631dc 100644
--- a/tests/containers.scm
+++ b/tests/containers.scm
@@ -1,6 +1,6 @@
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 © 2016, 2017, 2019, 2023 Ludovic Courtès <ludo@gnu.org> 3;;; Copyright © 2016-2017, 2019, 2023, 2025 Ludovic Courtès <ludo@gnu.org>
4;;; 4;;;
5;;; This file is part of GNU Guix. 5;;; This file is part of GNU Guix.
6;;; 6;;;
@@ -111,6 +111,26 @@
111 #:namespaces '(user mnt)))) 111 #:namespaces '(user mnt))))
112 112
113(skip-if-unsupported) 113(skip-if-unsupported)
114(test-equal "call-with-container, mnt namespace, locked mounts"
115 EINVAL
116 ;; umount(2) fails with EINVAL when targeting a mount point that is
117 ;; "locked".
118 (status:exit-val
119 (call-with-container (list (file-system
120 (device "none")
121 (mount-point "/testing")
122 (type "tmpfs")
123 (check? #f)))
124 (lambda ()
125 (primitive-exit (catch 'system-error
126 (lambda ()
127 (umount "/testing")
128 0)
129 (lambda args
130 (system-error-errno args)))))
131 #:namespaces '(user mnt))))
132
133(skip-if-unsupported)
114(test-equal "call-with-container, mnt namespace, wrong bind mount" 134(test-equal "call-with-container, mnt namespace, wrong bind mount"
115 `(system-error ,ENOENT) 135 `(system-error ,ENOENT)
116 ;; An exception should be raised; see <http://bugs.gnu.org/23306>. 136 ;; An exception should be raised; see <http://bugs.gnu.org/23306>.
@@ -169,7 +189,8 @@
169 #:namespaces '(user mnt)))) 189 #:namespaces '(user mnt))))
170 190
171(skip-if-unsupported) 191(skip-if-unsupported)
172(test-assert "container-excursion" 192(test-equal "container-excursion"
193 0
173 (call-with-temporary-directory 194 (call-with-temporary-directory
174 (lambda (root) 195 (lambda (root)
175 ;; Two pipes: One for the container to signal that the test can begin, 196 ;; Two pipes: One for the container to signal that the test can begin,
@@ -193,7 +214,11 @@
193 (readlink (string-append "/proc/" pid "/ns/" ns))) 214 (readlink (string-append "/proc/" pid "/ns/" ns)))
194 '("user" "ipc" "uts" "net" "pid" "mnt")))) 215 '("user" "ipc" "uts" "net" "pid" "mnt"))))
195 216
196 (let* ((pid (run-container root '() %namespaces 1 container)) 217 (let* ((pid (run-container root '() %namespaces 1 container
218 ;; Do not lock mounts so the user namespace
219 ;; appears to be the same seen from inside
220 ;; and from outside.
221 #:lock-mounts? #f))
197 (container-namespaces (namespaces pid)) 222 (container-namespaces (namespaces pid))
198 (result 223 (result
199 (begin 224 (begin
@@ -213,7 +238,7 @@
213 (write 'done end-out) 238 (write 'done end-out)
214 (close end-out) 239 (close end-out)
215 (waitpid pid) 240 (waitpid pid)
216 (zero? result))))))) 241 result))))))
217 242
218(skip-if-unsupported) 243(skip-if-unsupported)
219(test-equal "container-excursion, same namespaces" 244(test-equal "container-excursion, same namespaces"