diff options
Diffstat (limited to 'gnu/build/linux-container.scm')
| -rw-r--r-- | gnu/build/linux-container.scm | 111 |
1 files changed, 74 insertions, 37 deletions
diff --git a/gnu/build/linux-container.scm b/gnu/build/linux-container.scm index 345ce2de08a..51f04bc249b 100644 --- a/gnu/build/linux-container.scm +++ b/gnu/build/linux-container.scm | |||
| @@ -189,7 +189,10 @@ for the process." | |||
| 189 | (remount-read-only "/")))) | 189 | (remount-read-only "/")))) |
| 190 | 190 | ||
| 191 | (define* (initialize-user-namespace pid host-uids | 191 | (define* (initialize-user-namespace pid host-uids |
| 192 | #:key (guest-uid 0) (guest-gid 0)) | 192 | #:key |
| 193 | (host-uid (getuid)) | ||
| 194 | (host-gid (getgid)) | ||
| 195 | (guest-uid 0) (guest-gid 0)) | ||
| 193 | "Configure the user namespace for PID. HOST-UIDS specifies the number of | 196 | "Configure the user namespace for PID. HOST-UIDS specifies the number of |
| 194 | host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID | 197 | host user identifiers to map into the user namespace. GUEST-UID and GUEST-GID |
| 195 | specify the first UID (respectively GID) that host UIDs (respectively GIDs) | 198 | specify the first UID (respectively GID) that host UIDs (respectively GIDs) |
| @@ -200,24 +203,21 @@ map to in the namespace." | |||
| 200 | (define (scope file) | 203 | (define (scope file) |
| 201 | (string-append proc-dir file)) | 204 | (string-append proc-dir file)) |
| 202 | 205 | ||
| 203 | (let ((uid (getuid)) | 206 | ;; Only root can write to the gid map without first disabling the |
| 204 | (gid (getgid))) | 207 | ;; setgroups syscall. |
| 205 | 208 | (unless (and (zero? host-uid) (zero? host-gid)) | |
| 206 | ;; Only root can write to the gid map without first disabling the | 209 | (call-with-output-file (scope "/setgroups") |
| 207 | ;; setgroups syscall. | ||
| 208 | (unless (and (zero? uid) (zero? gid)) | ||
| 209 | (call-with-output-file (scope "/setgroups") | ||
| 210 | (lambda (port) | ||
| 211 | (display "deny" port)))) | ||
| 212 | |||
| 213 | ;; Map the user/group that created the container to the root user | ||
| 214 | ;; within the container. | ||
| 215 | (call-with-output-file (scope "/uid_map") | ||
| 216 | (lambda (port) | ||
| 217 | (format port "~d ~d ~d" guest-uid uid host-uids))) | ||
| 218 | (call-with-output-file (scope "/gid_map") | ||
| 219 | (lambda (port) | 210 | (lambda (port) |
| 220 | (format port "~d ~d ~d" guest-gid gid host-uids))))) | 211 | (display "deny" port)))) |
| 212 | |||
| 213 | ;; Map the user/group that created the container to the root user | ||
| 214 | ;; within the container. | ||
| 215 | (call-with-output-file (scope "/uid_map") | ||
| 216 | (lambda (port) | ||
| 217 | (format port "~d ~d ~d" guest-uid host-uid host-uids))) | ||
| 218 | (call-with-output-file (scope "/gid_map") | ||
| 219 | (lambda (port) | ||
| 220 | (format port "~d ~d ~d" guest-gid host-gid host-uids)))) | ||
| 221 | 221 | ||
| 222 | (define (namespaces->bit-mask namespaces) | 222 | (define (namespaces->bit-mask namespaces) |
| 223 | "Return the number suitable for the 'flags' argument of 'clone' that | 223 | "Return the number suitable for the 'flags' argument of 'clone' that |
| @@ -238,12 +238,14 @@ corresponds to the symbols in NAMESPACES." | |||
| 238 | #:key (guest-uid 0) (guest-gid 0) | 238 | #:key (guest-uid 0) (guest-gid 0) |
| 239 | (populate-file-system (const #t)) | 239 | (populate-file-system (const #t)) |
| 240 | (loopback-network? #t) | 240 | (loopback-network? #t) |
| 241 | (lock-mounts? #t) | ||
| 241 | writable-root?) | 242 | writable-root?) |
| 242 | "Run THUNK in a new container process and return its PID. ROOT specifies | 243 | "Run THUNK in a new container process and return its PID. ROOT specifies |
| 243 | the root directory for the container. MOUNTS is a list of <file-system> | 244 | the root directory for the container. MOUNTS is a list of <file-system> |
| 244 | objects that specify file systems to mount inside the container. NAMESPACES | 245 | objects that specify file systems to mount inside the container. NAMESPACES |
| 245 | is a list of symbols that correspond to the possible Linux namespaces: mnt, | 246 | is a list of symbols that correspond to the possible Linux namespaces: mnt, |
| 246 | ipc, uts, user, and net. | 247 | ipc, uts, user, and net. When LOCK-MOUNTS? is true, arrange so that none of |
| 248 | MOUNTS can be unmounted or remounted individually from within THUNK. | ||
| 247 | 249 | ||
| 248 | When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the | 250 | When LOOPBACK-NETWORK? is true and 'net is amount NAMESPACES, set up the |
| 249 | loopback device (\"lo\") and a minimal /etc/hosts. | 251 | loopback device (\"lo\") and a minimal /etc/hosts. |
| @@ -303,6 +305,28 @@ that host UIDs (respectively GIDs) map to in the namespace." | |||
| 303 | ;; cannot be 'read' so they shouldn't be written as is. | 305 | ;; cannot be 'read' so they shouldn't be written as is. |
| 304 | (write args child) | 306 | (write args child) |
| 305 | (primitive-exit 3)))) | 307 | (primitive-exit 3)))) |
| 308 | |||
| 309 | (when (and lock-mounts? | ||
| 310 | (memq 'mnt namespaces) | ||
| 311 | (memq 'user namespaces)) | ||
| 312 | ;; Create a new mount namespace owned by a new user | ||
| 313 | ;; namespace to "lock" together previous mounts, such that | ||
| 314 | ;; they cannot be unmounted or remounted separately--see | ||
| 315 | ;; mount_namespaces(7). | ||
| 316 | ;; | ||
| 317 | ;; Note: at this point, the process is single-threaded (no | ||
| 318 | ;; GC mark threads, no finalization thread, etc.) which is | ||
| 319 | ;; why unshare(CLONE_NEWUSER) can be used. | ||
| 320 | (let ((uid (getuid)) (gid (getgid))) | ||
| 321 | (unshare (logior CLONE_NEWUSER CLONE_NEWNS)) | ||
| 322 | (when (file-exists? "/proc/self") | ||
| 323 | (initialize-user-namespace (getpid) | ||
| 324 | host-uids | ||
| 325 | #:host-uid uid | ||
| 326 | #:host-gid gid | ||
| 327 | #:guest-uid guest-uid | ||
| 328 | #:guest-gid guest-gid)))) | ||
| 329 | |||
| 306 | ;; TODO: Manage capabilities. | 330 | ;; TODO: Manage capabilities. |
| 307 | (write 'ready child) | 331 | (write 'ready child) |
| 308 | (close-port child) | 332 | (close-port child) |
| @@ -365,6 +389,7 @@ if there are no child processes left." | |||
| 365 | 389 | ||
| 366 | (define* (call-with-container mounts thunk #:key (namespaces %namespaces) | 390 | (define* (call-with-container mounts thunk #:key (namespaces %namespaces) |
| 367 | (host-uids 1) (guest-uid 0) (guest-gid 0) | 391 | (host-uids 1) (guest-uid 0) (guest-gid 0) |
| 392 | (lock-mounts? #t) | ||
| 368 | (relayed-signals (list SIGINT SIGTERM)) | 393 | (relayed-signals (list SIGINT SIGTERM)) |
| 369 | (child-is-pid1? #t) | 394 | (child-is-pid1? #t) |
| 370 | (populate-file-system (const #t)) | 395 | (populate-file-system (const #t)) |
| @@ -449,6 +474,7 @@ load path must be adjusted as needed." | |||
| 449 | (call-with-temporary-directory | 474 | (call-with-temporary-directory |
| 450 | (lambda (root) | 475 | (lambda (root) |
| 451 | (let ((pid (run-container root mounts namespaces host-uids thunk* | 476 | (let ((pid (run-container root mounts namespaces host-uids thunk* |
| 477 | #:lock-mounts? lock-mounts? | ||
| 452 | #:guest-uid guest-uid | 478 | #:guest-uid guest-uid |
| 453 | #:guest-gid guest-gid | 479 | #:guest-gid guest-gid |
| 454 | #:populate-file-system populate-file-system | 480 | #:populate-file-system populate-file-system |
| @@ -469,24 +495,35 @@ return the exit status, an integer as returned by 'waitpid'." | |||
| 469 | (0 | 495 | (0 |
| 470 | (call-with-clean-exit | 496 | (call-with-clean-exit |
| 471 | (lambda () | 497 | (lambda () |
| 472 | (for-each (lambda (ns) | 498 | ;; First, determine the user namespace that owns the pid namespace and |
| 473 | (let ((source (namespace-file (getpid) ns)) | 499 | ;; join that user namespace (the assumption is that it also owns all |
| 474 | (target (namespace-file pid ns))) | 500 | ;; the other namespaces). It's important that the user namespace is |
| 475 | ;; Joining the namespace that the process already | 501 | ;; joined first, so that the user will have the privileges to join the |
| 476 | ;; belongs to would throw an error so avoid that. | 502 | ;; other namespaces. |
| 477 | ;; XXX: This /proc interface leads to TOCTTOU. | 503 | (let* ((pid-ns (open-fdes (namespace-file pid "pid") |
| 478 | (unless (string=? (readlink source) (readlink target)) | 504 | (logior O_CLOEXEC O_RDONLY))) |
| 479 | (call-with-input-file source | 505 | (user-ns (get-user-ns pid-ns))) |
| 480 | (lambda (current-ns-port) | 506 | (close-fdes pid-ns) |
| 481 | (call-with-input-file target | 507 | (unless (equal? (stat user-ns) |
| 482 | (lambda (new-ns-port) | 508 | (stat (namespace-file (getpid) "user"))) |
| 483 | (setns (fileno new-ns-port) 0)))))))) | 509 | (setns user-ns 0)) |
| 484 | ;; It's important that the user namespace is joined first, | 510 | (close-fdes user-ns) |
| 485 | ;; so that the user will have the privileges to join the | 511 | |
| 486 | ;; other namespaces. Furthermore, it's important that the | 512 | ;; Then join all the remaining namespaces. |
| 487 | ;; mount namespace is joined last, otherwise the /proc mount | 513 | (for-each (lambda (ns) |
| 488 | ;; point would no longer be accessible. | 514 | (let ((source (namespace-file (getpid) ns)) |
| 489 | '("user" "ipc" "uts" "net" "pid" "mnt")) | 515 | (target (namespace-file pid ns))) |
| 516 | ;; Joining the namespace that the process already | ||
| 517 | ;; belongs to would throw an error so avoid that. | ||
| 518 | ;; XXX: This /proc interface leads to TOCTTOU. | ||
| 519 | (unless (string=? (readlink source) (readlink target)) | ||
| 520 | (call-with-input-file target | ||
| 521 | (lambda (new-ns-port) | ||
| 522 | (setns (fileno new-ns-port) 0)))))) | ||
| 523 | ;; It's important that the mount namespace is joined last, | ||
| 524 | ;; otherwise the /proc mount point would no longer be | ||
| 525 | ;; accessible. | ||
| 526 | '("ipc" "uts" "net" "pid" "mnt"))) | ||
| 490 | (purify-environment) | 527 | (purify-environment) |
| 491 | (chdir "/") | 528 | (chdir "/") |
| 492 | 529 | ||
