diff options
| -rw-r--r-- | build-aux/test-env.in | 18 | ||||
| -rw-r--r-- | tests/processes.scm | 9 | ||||
| -rw-r--r-- | tests/store.scm | 247 |
3 files changed, 236 insertions, 38 deletions
diff --git a/build-aux/test-env.in b/build-aux/test-env.in index 9caa29da581..86c2e585d73 100644 --- a/build-aux/test-env.in +++ b/build-aux/test-env.in | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | 2 | ||
| 3 | # GNU Guix --- Functional package management for GNU | 3 | # GNU Guix --- Functional package management for GNU |
| 4 | # Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2021 Ludovic Courtès <ludo@gnu.org> | 4 | # Copyright © 2012-2019, 2021, 2025 Ludovic Courtès <ludo@gnu.org> |
| 5 | # | 5 | # |
| 6 | # This file is part of GNU Guix. | 6 | # This file is part of GNU Guix. |
| 7 | # | 7 | # |
| @@ -102,10 +102,24 @@ then | |||
| 102 | rm -rf "$GUIX_STATE_DIRECTORY/daemon-socket" | 102 | rm -rf "$GUIX_STATE_DIRECTORY/daemon-socket" |
| 103 | mkdir -m 0700 "$GUIX_STATE_DIRECTORY/daemon-socket" | 103 | mkdir -m 0700 "$GUIX_STATE_DIRECTORY/daemon-socket" |
| 104 | 104 | ||
| 105 | # If unprivileged user namespaces are not supported, pass | ||
| 106 | # '--disable-chroot'. | ||
| 107 | if [ -f /proc/self/ns/user ] \ | ||
| 108 | && { [ ! -f /proc/sys/kernel/unprivileged_userns_clone ] \ | ||
| 109 | || [ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" -eq 1 ]; } | ||
| 110 | then | ||
| 111 | extra_options="" | ||
| 112 | else | ||
| 113 | extra_options="--disable-chroot" | ||
| 114 | echo "unprivileged user namespaces not supported; \ | ||
| 115 | running 'guix-daemon $extra_options'" >&2 | ||
| 116 | fi | ||
| 117 | |||
| 105 | # Launch the daemon without chroot support because is may be | 118 | # Launch the daemon without chroot support because is may be |
| 106 | # unavailable, for instance if we're not running as root. | 119 | # unavailable, for instance if we're not running as root. |
| 107 | "@abs_top_builddir@/pre-inst-env" \ | 120 | "@abs_top_builddir@/pre-inst-env" \ |
| 108 | "@abs_top_builddir@/guix-daemon" --disable-chroot \ | 121 | "@abs_top_builddir@/guix-daemon" \ |
| 122 | $extra_options \ | ||
| 109 | --substitute-urls="$GUIX_BINARY_SUBSTITUTE_URL" & | 123 | --substitute-urls="$GUIX_BINARY_SUBSTITUTE_URL" & |
| 110 | 124 | ||
| 111 | daemon_pid=$! | 125 | daemon_pid=$! |
diff --git a/tests/processes.scm b/tests/processes.scm index ba518f2d9e3..a72ba16f587 100644 --- a/tests/processes.scm +++ b/tests/processes.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2018, 2025 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; Copyright © 2019 Mathieu Othacehe <m.othacehe@gmail.com> | 3 | ;;; Copyright © 2019 Mathieu Othacehe <m.othacehe@gmail.com> |
| 4 | ;;; | 4 | ;;; |
| 5 | ;;; This file is part of GNU Guix. | 5 | ;;; This file is part of GNU Guix. |
| @@ -25,6 +25,8 @@ | |||
| 25 | #:use-module (guix gexp) | 25 | #:use-module (guix gexp) |
| 26 | #:use-module ((guix utils) #:select (call-with-temporary-directory)) | 26 | #:use-module ((guix utils) #:select (call-with-temporary-directory)) |
| 27 | #:use-module (gnu packages bootstrap) | 27 | #:use-module (gnu packages bootstrap) |
| 28 | #:use-module ((gnu build linux-container) | ||
| 29 | #:select (unprivileged-user-namespace-supported?)) | ||
| 28 | #:use-module (guix tests) | 30 | #:use-module (guix tests) |
| 29 | #:use-module (srfi srfi-1) | 31 | #:use-module (srfi srfi-1) |
| 30 | #:use-module (srfi srfi-64) | 32 | #:use-module (srfi srfi-64) |
| @@ -84,6 +86,11 @@ | |||
| 84 | (and (kill (process-id daemon) 0) | 86 | (and (kill (process-id daemon) 0) |
| 85 | (string-suffix? "guix-daemon" (first (process-command daemon))))))) | 87 | (string-suffix? "guix-daemon" (first (process-command daemon))))))) |
| 86 | 88 | ||
| 89 | (when (unprivileged-user-namespace-supported?) | ||
| 90 | ;; The test below assumes the build process can communicate with the outside | ||
| 91 | ;; world via the TOKEN1 and TOKEN2 files, which is impossible when | ||
| 92 | ;; guix-daemon is set up to build in separate namespaces. | ||
| 93 | (test-skip 1)) | ||
| 87 | (test-assert* "client + lock" | 94 | (test-assert* "client + lock" |
| 88 | (with-store store | 95 | (with-store store |
| 89 | (call-with-temporary-directory | 96 | (call-with-temporary-directory |
diff --git a/tests/store.scm b/tests/store.scm index 45948f4f433..b1ddff2082e 100644 --- a/tests/store.scm +++ b/tests/store.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2012-2021, 2023 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2012-2021, 2023, 2025 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; | 3 | ;;; |
| 4 | ;;; This file is part of GNU Guix. | 4 | ;;; This file is part of GNU Guix. |
| 5 | ;;; | 5 | ;;; |
| @@ -28,8 +28,12 @@ | |||
| 28 | #:use-module (guix base32) | 28 | #:use-module (guix base32) |
| 29 | #:use-module (guix packages) | 29 | #:use-module (guix packages) |
| 30 | #:use-module (guix derivations) | 30 | #:use-module (guix derivations) |
| 31 | #:use-module ((guix modules) | ||
| 32 | #:select (source-module-closure)) | ||
| 31 | #:use-module (guix serialization) | 33 | #:use-module (guix serialization) |
| 32 | #:use-module (guix build utils) | 34 | #:use-module (guix build utils) |
| 35 | #:use-module ((gnu build linux-container) | ||
| 36 | #:select (unprivileged-user-namespace-supported?)) | ||
| 33 | #:use-module (guix gexp) | 37 | #:use-module (guix gexp) |
| 34 | #:use-module (gnu packages) | 38 | #:use-module (gnu packages) |
| 35 | #:use-module (gnu packages bootstrap) | 39 | #:use-module (gnu packages bootstrap) |
| @@ -391,6 +395,188 @@ | |||
| 391 | (equal? (valid-derivers %store o) | 395 | (equal? (valid-derivers %store o) |
| 392 | (list (derivation-file-name d)))))) | 396 | (list (derivation-file-name d)))))) |
| 393 | 397 | ||
| 398 | (test-assert "symlink is symlink" | ||
| 399 | (let* ((a (add-text-to-store %store "hello.txt" (random-text))) | ||
| 400 | (b (build-expression->derivation | ||
| 401 | %store "symlink" | ||
| 402 | '(symlink (assoc-ref %build-inputs "a") %output) | ||
| 403 | #:inputs `(("a" ,a)))) | ||
| 404 | (c (build-expression->derivation | ||
| 405 | %store "symlink-reference" | ||
| 406 | `(call-with-output-file %output | ||
| 407 | (lambda (port) | ||
| 408 | ;; Check that B is indeed visible as a symlink. This should | ||
| 409 | ;; always be the case, both in the '--disable-chroot' and in | ||
| 410 | ;; the user namespace setups. | ||
| 411 | (pk 'stat (lstat (assoc-ref %build-inputs "b"))) | ||
| 412 | (display (readlink (assoc-ref %build-inputs "b")) | ||
| 413 | port))) | ||
| 414 | #:inputs `(("b" ,b))))) | ||
| 415 | (and (build-derivations %store (list c)) | ||
| 416 | (string=? (call-with-input-file (derivation->output-path c) | ||
| 417 | get-string-all) | ||
| 418 | a)))) | ||
| 419 | |||
| 420 | (unless (unprivileged-user-namespace-supported?) | ||
| 421 | (test-skip 1)) | ||
| 422 | (test-equal "isolated environment" | ||
| 423 | (string-join (append | ||
| 424 | '("PID: 1" "UID: 30001") | ||
| 425 | (delete-duplicates | ||
| 426 | (sort (list "/dev" "/tmp" "/proc" "/etc" | ||
| 427 | (match (string-tokenize (%store-prefix) | ||
| 428 | (char-set-complement | ||
| 429 | (char-set #\/))) | ||
| 430 | ((top _ ...) (string-append "/" top)))) | ||
| 431 | string<?)) | ||
| 432 | '("/etc/group" "/etc/hosts" "/etc/passwd"))) | ||
| 433 | (let* ((b (add-text-to-store %store "build.sh" | ||
| 434 | "echo -n PID: $$ UID: $UID /* /etc/* > $out")) | ||
| 435 | (s (add-to-store %store "bash" #t "sha256" | ||
| 436 | (search-bootstrap-binary "bash" | ||
| 437 | (%current-system)))) | ||
| 438 | (d (derivation %store "the-thing" | ||
| 439 | s `("-e" ,b) | ||
| 440 | #:env-vars `(("foo" . ,(random-text))) | ||
| 441 | #:sources (list b s))) | ||
| 442 | (o (derivation->output-path d))) | ||
| 443 | (and (build-derivations %store (list d)) | ||
| 444 | (call-with-input-file o get-string-all)))) | ||
| 445 | |||
| 446 | (unless (unprivileged-user-namespace-supported?) | ||
| 447 | (test-skip 1)) | ||
| 448 | (test-equal "inputs are read-only" | ||
| 449 | "All good!" | ||
| 450 | (let* ((input (plain-file (string-append "might-be-tampered-with-" | ||
| 451 | (number->string | ||
| 452 | (car (gettimeofday)) | ||
| 453 | 16)) | ||
| 454 | "All good!")) | ||
| 455 | (drv | ||
| 456 | (run-with-store %store | ||
| 457 | (gexp->derivation | ||
| 458 | "attempt-to-write-to-input" | ||
| 459 | (with-imported-modules (source-module-closure | ||
| 460 | '((guix build syscalls))) | ||
| 461 | #~(begin | ||
| 462 | (use-modules (guix build syscalls)) | ||
| 463 | |||
| 464 | (let ((input #$input)) | ||
| 465 | (chmod input #o666) | ||
| 466 | (call-with-output-file input | ||
| 467 | (lambda (port) | ||
| 468 | (display "BAD!" port))) | ||
| 469 | (mkdir #$output)))))))) | ||
| 470 | (and (guard (c ((store-protocol-error? c) #t)) | ||
| 471 | (build-derivations %store (list drv))) | ||
| 472 | (call-with-input-file (run-with-store %store | ||
| 473 | (lower-object input)) | ||
| 474 | get-string-all)))) | ||
| 475 | |||
| 476 | (unless (unprivileged-user-namespace-supported?) | ||
| 477 | (test-skip 1)) | ||
| 478 | (test-assert "inputs cannot be remounted read-write" | ||
| 479 | (let ((drv | ||
| 480 | (run-with-store %store | ||
| 481 | (gexp->derivation | ||
| 482 | "attempt-to-remount-input-read-write" | ||
| 483 | (with-imported-modules (source-module-closure | ||
| 484 | '((guix build syscalls))) | ||
| 485 | #~(begin | ||
| 486 | (use-modules (guix build syscalls)) | ||
| 487 | |||
| 488 | (let ((input #$(plain-file "input-that-might-be-tampered-with" | ||
| 489 | "All good!"))) | ||
| 490 | (mount "none" input "none" (logior MS_BIND MS_REMOUNT)) | ||
| 491 | (call-with-output-file input | ||
| 492 | (lambda (port) | ||
| 493 | (display "BAD!" port))) | ||
| 494 | (mkdir #$output)))))))) | ||
| 495 | (guard (c ((store-protocol-error? c) #t)) | ||
| 496 | (build-derivations %store (list drv)) | ||
| 497 | #f))) | ||
| 498 | |||
| 499 | (unless (unprivileged-user-namespace-supported?) | ||
| 500 | (test-skip 1)) | ||
| 501 | (test-assert "build root cannot be made world-readable" | ||
| 502 | (let ((drv | ||
| 503 | (run-with-store %store | ||
| 504 | (gexp->derivation | ||
| 505 | "attempt-to-make-root-world-readable" | ||
| 506 | (with-imported-modules (source-module-closure | ||
| 507 | '((guix build syscalls))) | ||
| 508 | #~(begin | ||
| 509 | (use-modules (guix build syscalls)) | ||
| 510 | |||
| 511 | (catch 'system-error | ||
| 512 | (lambda () | ||
| 513 | (chmod "/" #o777)) | ||
| 514 | (lambda args | ||
| 515 | (format #t "failed to make root writable: ~a~%" | ||
| 516 | (strerror (system-error-errno args))) | ||
| 517 | (format #t "attempting read-write remount~%") | ||
| 518 | (mount "none" "/" "/" (logior MS_BIND MS_REMOUNT)) | ||
| 519 | (chmod "/" #o777))) | ||
| 520 | |||
| 521 | ;; At this point, the build process could create a | ||
| 522 | ;; world-readable setuid binary under its root (so in the | ||
| 523 | ;; store) that would remain visible until the build | ||
| 524 | ;; completes. | ||
| 525 | (mkdir #$output))))))) | ||
| 526 | (guard (c ((store-protocol-error? c) #t)) | ||
| 527 | (build-derivations %store (list drv)) | ||
| 528 | #f))) | ||
| 529 | |||
| 530 | (unless (unprivileged-user-namespace-supported?) | ||
| 531 | (test-skip 1)) | ||
| 532 | (test-assert "/tmp, store, and /dev/{null,full} are writable" | ||
| 533 | ;; All of /tmp and all of the store must be writable (the store is writable | ||
| 534 | ;; so that derivation outputs can be written to it, but in practice it's | ||
| 535 | ;; always been wide open). Things like /dev/null must be writable too. | ||
| 536 | (let ((drv (run-with-store %store | ||
| 537 | (gexp->derivation | ||
| 538 | "check-tmp-and-store-are-writable" | ||
| 539 | #~(begin | ||
| 540 | (mkdir "/tmp/something") | ||
| 541 | (mkdir (in-vicinity (getenv "NIX_STORE") | ||
| 542 | "some-other-thing")) | ||
| 543 | (call-with-output-file "/dev/null" | ||
| 544 | (lambda (port) | ||
| 545 | (display "Welcome to the void." port))) | ||
| 546 | (catch 'system-error | ||
| 547 | (lambda () | ||
| 548 | (call-with-output-file "/dev/full" | ||
| 549 | (lambda (port) | ||
| 550 | (display "No space left!" port))) | ||
| 551 | (error "Should have thrown!")) | ||
| 552 | (lambda args | ||
| 553 | (unless (= ENOSPC (system-error-errno args)) | ||
| 554 | (apply throw args)))) | ||
| 555 | (mkdir #$output)))))) | ||
| 556 | (build-derivations %store (list drv)))) | ||
| 557 | |||
| 558 | (unless (unprivileged-user-namespace-supported?) | ||
| 559 | (test-skip 1)) | ||
| 560 | (test-assert "network is unreachable" | ||
| 561 | (let ((drv (run-with-store %store | ||
| 562 | (gexp->derivation | ||
| 563 | "check-network-unreachable" | ||
| 564 | #~(let ((check-connection-failure | ||
| 565 | (lambda (address expected-code) | ||
| 566 | (let ((s (socket AF_INET SOCK_STREAM 0))) | ||
| 567 | (catch 'system-error | ||
| 568 | (lambda () | ||
| 569 | (connect s AF_INET (inet-pton AF_INET address) 80)) | ||
| 570 | (lambda args | ||
| 571 | (let ((errno (system-error-errno args))) | ||
| 572 | (unless (= expected-code errno) | ||
| 573 | (error "wrong error code" | ||
| 574 | errno (strerror errno)))))))))) | ||
| 575 | (check-connection-failure "127.0.0.1" ECONNREFUSED) | ||
| 576 | (check-connection-failure "9.9.9.9" ENETUNREACH) | ||
| 577 | (mkdir #$output)))))) | ||
| 578 | (build-derivations %store (list drv)))) | ||
| 579 | |||
| 394 | (test-equal "with-build-handler" | 580 | (test-equal "with-build-handler" |
| 395 | 'success | 581 | 'success |
| 396 | (let* ((b (add-text-to-store %store "build" "echo $foo > $out" '())) | 582 | (let* ((b (add-text-to-store %store "build" "echo $foo > $out" '())) |
| @@ -1333,40 +1519,31 @@ System: x86_64-linux~%" | |||
| 1333 | 1519 | ||
| 1334 | (test-assert "build-things, check mode" | 1520 | (test-assert "build-things, check mode" |
| 1335 | (with-store store | 1521 | (with-store store |
| 1336 | (call-with-temporary-output-file | 1522 | (let* ((drv (build-expression->derivation |
| 1337 | (lambda (entropy entropy-port) | 1523 | store "non-deterministic" |
| 1338 | (write (random-text) entropy-port) | 1524 | `(begin |
| 1339 | (force-output entropy-port) | 1525 | (use-modules (rnrs io ports)) |
| 1340 | (let* ((drv (build-expression->derivation | 1526 | (let ((out (assoc-ref %outputs "out"))) |
| 1341 | store "non-deterministic" | 1527 | (call-with-output-file out |
| 1342 | `(begin | 1528 | (lambda (port) |
| 1343 | (use-modules (rnrs io ports)) | 1529 | (let ((now (gettimeofday))) |
| 1344 | (let ((out (assoc-ref %outputs "out"))) | 1530 | (display (+ (car now) (cdr now)) port)))) |
| 1345 | (call-with-output-file out | 1531 | #t)) |
| 1346 | (lambda (port) | 1532 | #:guile-for-build |
| 1347 | ;; Rely on the fact that tests do not use the | 1533 | (package-derivation store %bootstrap-guile (%current-system)))) |
| 1348 | ;; chroot, and thus ENTROPY is readable. | 1534 | (file (derivation->output-path drv))) |
| 1349 | (display (call-with-input-file ,entropy | 1535 | (and (build-things store (list (derivation-file-name drv))) |
| 1350 | get-string-all) | 1536 | (begin |
| 1351 | port))) | 1537 | (guard (c ((store-protocol-error? c) |
| 1352 | #t)) | 1538 | (pk 'determinism-exception c) |
| 1353 | #:guile-for-build | 1539 | (and (not (zero? (store-protocol-error-status c))) |
| 1354 | (package-derivation store %bootstrap-guile (%current-system)))) | 1540 | (string-contains (store-protocol-error-message c) |
| 1355 | (file (derivation->output-path drv))) | 1541 | "deterministic")))) |
| 1356 | (and (build-things store (list (derivation-file-name drv))) | 1542 | ;; This one will produce a different result. Since we're in |
| 1357 | (begin | 1543 | ;; 'check' mode, this must fail. |
| 1358 | (write (random-text) entropy-port) | 1544 | (build-things store (list (derivation-file-name drv)) |
| 1359 | (force-output entropy-port) | 1545 | (build-mode check)) |
| 1360 | (guard (c ((store-protocol-error? c) | 1546 | #f)))))) |
| 1361 | (pk 'determinism-exception c) | ||
| 1362 | (and (not (zero? (store-protocol-error-status c))) | ||
| 1363 | (string-contains (store-protocol-error-message c) | ||
| 1364 | "deterministic")))) | ||
| 1365 | ;; This one will produce a different result. Since we're in | ||
| 1366 | ;; 'check' mode, this must fail. | ||
| 1367 | (build-things store (list (derivation-file-name drv)) | ||
| 1368 | (build-mode check)) | ||
| 1369 | #f)))))))) | ||
| 1370 | 1547 | ||
| 1371 | (test-assert "build-succeeded trace in check mode" | 1548 | (test-assert "build-succeeded trace in check mode" |
| 1372 | (string-contains | 1549 | (string-contains |
