summaryrefslogtreecommitdiff
path: root/gnu/build
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 /gnu/build
parentd0144544ff38c62ee92b6f3b6ee3e6aa6aede812 (diff)
Reinstate "linux-container: Remove #:lock-mounts? and related code."
This reverts commit e0e64be8de3d220a12612b3a2e4aee428277d865.
Diffstat (limited to 'gnu/build')
-rw-r--r--gnu/build/linux-container.scm111
1 files changed, 74 insertions, 37 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