summaryrefslogtreecommitdiff
path: root/gnu/build/activation.scm
diff options
context:
space:
mode:
authorMaxime Devos <maximedevos@telenet.be>2022-10-28 18:04:09 +0200
committerLudovic Courtès <ludo@gnu.org>2024-09-06 11:46:26 +0200
commitc1283e203995c8d84584e701b965efe086d1d666 (patch)
treef2a6865af2a90bdca782c11fa9021576810cdf35 /gnu/build/activation.scm
parent571c605f17481e8c606c876e04129d99632bc2ec (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.scm90
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
72Additionally, verify no component of DIRECTORY is a symbolic link,
73without 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
75current (process) uid/gid instead of OWNER. This is not expected to be
76a problem in practice.
77
78The permission bits and owner of DIRECTORY are set to BITS and OWNER.
79Anything above DIRECTORY that already exists keeps
80its old owner and bits. For components that do not exist yet, the owner
81and 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
103Verify no component of DIRECTORY is a symbolic link. 113 ;; If not, create it.
104Warning: 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