diff options
| author | Maxim Cournoyer <maxim@guixotic.coop> | 2025-10-16 16:36:43 +0900 |
|---|---|---|
| committer | Maxim Cournoyer <maxim@guixotic.coop> | 2025-10-29 11:34:28 +0900 |
| commit | 10edda5aa5f92e7bfcff4902d4f3c11373d1e725 (patch) | |
| tree | 95cff7d390183e858722a085d13fb00a5a67863f /gnu | |
| parent | d0144544ff38c62ee92b6f3b6ee3e6aa6aede812 (diff) | |
Reinstate "linux-container: Remove #:lock-mounts? and related code."
This reverts commit e0e64be8de3d220a12612b3a2e4aee428277d865.
Diffstat (limited to 'gnu')
| -rw-r--r-- | gnu/build/linux-container.scm | 111 | ||||
| -rw-r--r-- | gnu/system/linux-container.scm | 4 |
2 files changed, 78 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 |
| 195 | host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID | 198 | host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID |
| 196 | specify the first UID (respectively GID) that host UIDs (respectively GIDs) | 199 | specify 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 |
| 244 | the root directory for the container. MOUNTS is a list of <file-system> | 245 | the root directory for the container. MOUNTS is a list of <file-system> |
| 245 | objects that specify file systems to mount inside the container. NAMESPACES | 246 | objects that specify file systems to mount inside the container. NAMESPACES |
| 246 | is a list of symbols that correspond to the possible Linux namespaces: mnt, | 247 | is a list of symbols that correspond to the possible Linux namespaces: mnt, |
| 247 | ipc, uts, user, and net. | 248 | ipc, uts, user, and net. When LOCK-MOUNTS? is true, arrange so that none of |
| 249 | MOUNTS can be unmounted or remounted individually from within THUNK. | ||
| 248 | 250 | ||
| 249 | When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the | 251 | When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the |
| 250 | loopback device (\"lo\") and a minimal /etc/hosts. | 252 | loopback 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 | ||
