diff options
| author | Maxime Devos <maximedevos@telenet.be> | 2022-10-28 18:04:09 +0200 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2024-09-06 11:46:26 +0200 |
| commit | c1283e203995c8d84584e701b965efe086d1d666 (patch) | |
| tree | f2a6865af2a90bdca782c11fa9021576810cdf35 /gnu/build/activation.scm | |
| parent | 571c605f17481e8c606c876e04129d99632bc2ec (diff) | |
activation: Fix TOCTTOU in mkdir-p/perms.
Fixes <https://issues.guix.gnu.org/47584>.
I removed the 'Based upon mkdir-p from (guix build utils)'
comment because it's quite a bit different now.
* gnu/build/activation.scm (verify-not-symbolic): Delete.
(mkdir-p/perms): Rewrite in terms of 'openat'.
Signed-off-by: Ludovic Courtès <ludo@gnu.org>
Change-Id: Id2f5bcbb903283afd45f6109190210d02eb383c7
Diffstat (limited to 'gnu/build/activation.scm')
| -rw-r--r-- | gnu/build/activation.scm | 90 |
1 files changed, 57 insertions, 33 deletions
diff --git a/gnu/build/activation.scm b/gnu/build/activation.scm index a57ca78a86a..d1a2876a961 100644 --- a/gnu/build/activation.scm +++ b/gnu/build/activation.scm | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | ;;; Copyright © 2015, 2018 Mark H Weaver <mhw@netris.org> | 5 | ;;; Copyright © 2015, 2018 Mark H Weaver <mhw@netris.org> |
| 6 | ;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net> | 6 | ;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net> |
| 7 | ;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net> | 7 | ;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net> |
| 8 | ;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be> | 8 | ;;; Copyright © 2021, 2022 Maxime Devos <maximedevos@telenet.be> |
| 9 | ;;; Copyright © 2020 Christine Lemmer-Webber <cwebber@dustycloud.org> | 9 | ;;; Copyright © 2020 Christine Lemmer-Webber <cwebber@dustycloud.org> |
| 10 | ;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re> | 10 | ;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re> |
| 11 | ;;; Copyright © 2022 Tobias Geerinckx-Rice <me@tobias.gr> | 11 | ;;; Copyright © 2022 Tobias Geerinckx-Rice <me@tobias.gr> |
| @@ -66,46 +66,70 @@ | |||
| 66 | (define (dot-or-dot-dot? file) | 66 | (define (dot-or-dot-dot? file) |
| 67 | (member file '("." ".."))) | 67 | (member file '("." ".."))) |
| 68 | 68 | ||
| 69 | ;; Based upon mkdir-p from (guix build utils) | 69 | (define (mkdir-p/perms directory owner bits) |
| 70 | (define (verify-not-symbolic dir) | 70 | "Create directory DIRECTORY and all its ancestors. |
| 71 | "Verify DIR or its ancestors aren't symbolic links." | 71 | |
| 72 | Additionally, verify no component of DIRECTORY is a symbolic link, | ||
| 73 | without TOCTTOU races. However, if OWNER differs from the the current | ||
| 74 | (process) uid/gid, there is a small window in which DIRECTORY is set to the | ||
| 75 | current (process) uid/gid instead of OWNER. This is not expected to be | ||
| 76 | a problem in practice. | ||
| 77 | |||
| 78 | The permission bits and owner of DIRECTORY are set to BITS and OWNER. | ||
| 79 | Anything above DIRECTORY that already exists keeps | ||
| 80 | its old owner and bits. For components that do not exist yet, the owner | ||
| 81 | and bits are set according to the default behaviour of 'mkdir'." | ||
| 72 | (define absolute? | 82 | (define absolute? |
| 73 | (string-prefix? "/" dir)) | 83 | (string-prefix? "/" directory)) |
| 74 | 84 | ||
| 75 | (define not-slash | 85 | (define not-slash |
| 76 | (char-set-complement (char-set #\/))) | 86 | (char-set-complement (char-set #\/))) |
| 77 | 87 | ||
| 78 | (define (verify-component file) | 88 | ;; By combining O_NOFOLLOW and O_DIRECTORY, this procedure automatically |
| 79 | (unless (eq? 'directory (stat:type (lstat file))) | 89 | ;; verifies that no components are symlinks. |
| 80 | (error "file name component is not a directory" dir))) | 90 | (define open-flags (logior O_CLOEXEC ; don't pass the port on to subprocesses |
| 91 | O_NOFOLLOW ; don't follow symlinks | ||
| 92 | O_DIRECTORY)) ; reject anything not a directory | ||
| 81 | 93 | ||
| 82 | (let loop ((components (string-tokenize dir not-slash)) | 94 | (let loop ((components (string-tokenize directory not-slash)) |
| 83 | (root (if absolute? | 95 | (root (open (if absolute? "/" ".") open-flags))) |
| 84 | "" | ||
| 85 | "."))) | ||
| 86 | (match components | 96 | (match components |
| 87 | ((head tail ...) | 97 | ((head tail ...) |
| 88 | (let ((file (string-append root "/" head))) | 98 | (let retry () |
| 89 | (catch 'system-error | 99 | ;; In the usual case, we expect HEAD to already exist. |
| 90 | (lambda () | 100 | (match (catch 'system-error |
| 91 | (verify-component file) | 101 | (lambda () |
| 92 | (loop tail file)) | 102 | (openat root head open-flags)) |
| 93 | (lambda args | 103 | (lambda args |
| 94 | (if (= ENOENT (system-error-errno args)) | 104 | (if (= ENOENT (system-error-errno args)) |
| 95 | #t | 105 | #false |
| 96 | (apply throw args)))))) | 106 | (begin |
| 97 | (() #t)))) | 107 | (close-port root) |
| 98 | 108 | (apply throw args))))) | |
| 99 | ;; TODO: the TOCTTOU race can be addressed once guile has bindings | 109 | ((? port? new-root) |
| 100 | ;; for fstatat, openat and friends. | 110 | (close root) |
| 101 | (define (mkdir-p/perms directory owner bits) | 111 | (loop tail new-root)) |
| 102 | "Create the directory DIRECTORY and all its ancestors. | 112 | (#false |
| 103 | Verify no component of DIRECTORY is a symbolic link. | 113 | ;; If not, create it. |
| 104 | Warning: this is currently suspect to a TOCTTOU race!" | 114 | (catch 'system-error |
| 105 | (verify-not-symbolic directory) | 115 | (lambda _ |
| 106 | (mkdir-p directory) | 116 | (mkdirat root head)) |
| 107 | (chown directory (passwd:uid owner) (passwd:gid owner)) | 117 | (lambda args |
| 108 | (chmod directory bits)) | 118 | ;; Someone else created the directory. Unexpected but fine. |
| 119 | (unless (= EEXIST (system-error-errno args)) | ||
| 120 | (close-port root) | ||
| 121 | (apply throw args)))) | ||
| 122 | (retry))))) | ||
| 123 | (() | ||
| 124 | (catch 'system-error | ||
| 125 | (lambda () | ||
| 126 | (chown root (passwd:uid owner) (passwd:gid owner)) | ||
| 127 | (chmod root bits)) | ||
| 128 | (lambda args | ||
| 129 | (close-port root) | ||
| 130 | (apply throw args))) | ||
| 131 | (close-port root) | ||
| 132 | (values))))) | ||
| 109 | 133 | ||
| 110 | (define* (copy-account-skeletons home | 134 | (define* (copy-account-skeletons home |
| 111 | #:key | 135 | #:key |
