mirror of
https://git.savannah.gnu.org/git/guix.git
synced 2026-05-28 12:01:49 +02:00
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
This commit is contained in:
committed by
Ludovic Courtès
parent
571c605f17
commit
c1283e2039
+57
-33
@@ -5,7 +5,7 @@
|
|||||||
;;; Copyright © 2015, 2018 Mark H Weaver <mhw@netris.org>
|
;;; Copyright © 2015, 2018 Mark H Weaver <mhw@netris.org>
|
||||||
;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net>
|
;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net>
|
||||||
;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net>
|
;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net>
|
||||||
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
|
;;; Copyright © 2021, 2022 Maxime Devos <maximedevos@telenet.be>
|
||||||
;;; Copyright © 2020 Christine Lemmer-Webber <cwebber@dustycloud.org>
|
;;; Copyright © 2020 Christine Lemmer-Webber <cwebber@dustycloud.org>
|
||||||
;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re>
|
;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re>
|
||||||
;;; Copyright © 2022 Tobias Geerinckx-Rice <me@tobias.gr>
|
;;; Copyright © 2022 Tobias Geerinckx-Rice <me@tobias.gr>
|
||||||
@@ -66,46 +66,70 @@
|
|||||||
(define (dot-or-dot-dot? file)
|
(define (dot-or-dot-dot? file)
|
||||||
(member file '("." "..")))
|
(member file '("." "..")))
|
||||||
|
|
||||||
;; Based upon mkdir-p from (guix build utils)
|
(define (mkdir-p/perms directory owner bits)
|
||||||
(define (verify-not-symbolic dir)
|
"Create directory DIRECTORY and all its ancestors.
|
||||||
"Verify DIR or its ancestors aren't symbolic links."
|
|
||||||
|
Additionally, verify no component of DIRECTORY is a symbolic link,
|
||||||
|
without TOCTTOU races. However, if OWNER differs from the the current
|
||||||
|
(process) uid/gid, there is a small window in which DIRECTORY is set to the
|
||||||
|
current (process) uid/gid instead of OWNER. This is not expected to be
|
||||||
|
a problem in practice.
|
||||||
|
|
||||||
|
The permission bits and owner of DIRECTORY are set to BITS and OWNER.
|
||||||
|
Anything above DIRECTORY that already exists keeps
|
||||||
|
its old owner and bits. For components that do not exist yet, the owner
|
||||||
|
and bits are set according to the default behaviour of 'mkdir'."
|
||||||
(define absolute?
|
(define absolute?
|
||||||
(string-prefix? "/" dir))
|
(string-prefix? "/" directory))
|
||||||
|
|
||||||
(define not-slash
|
(define not-slash
|
||||||
(char-set-complement (char-set #\/)))
|
(char-set-complement (char-set #\/)))
|
||||||
|
|
||||||
(define (verify-component file)
|
;; By combining O_NOFOLLOW and O_DIRECTORY, this procedure automatically
|
||||||
(unless (eq? 'directory (stat:type (lstat file)))
|
;; verifies that no components are symlinks.
|
||||||
(error "file name component is not a directory" dir)))
|
(define open-flags (logior O_CLOEXEC ; don't pass the port on to subprocesses
|
||||||
|
O_NOFOLLOW ; don't follow symlinks
|
||||||
|
O_DIRECTORY)) ; reject anything not a directory
|
||||||
|
|
||||||
(let loop ((components (string-tokenize dir not-slash))
|
(let loop ((components (string-tokenize directory not-slash))
|
||||||
(root (if absolute?
|
(root (open (if absolute? "/" ".") open-flags)))
|
||||||
""
|
|
||||||
".")))
|
|
||||||
(match components
|
(match components
|
||||||
((head tail ...)
|
((head tail ...)
|
||||||
(let ((file (string-append root "/" head)))
|
(let retry ()
|
||||||
(catch 'system-error
|
;; In the usual case, we expect HEAD to already exist.
|
||||||
(lambda ()
|
(match (catch 'system-error
|
||||||
(verify-component file)
|
(lambda ()
|
||||||
(loop tail file))
|
(openat root head open-flags))
|
||||||
(lambda args
|
(lambda args
|
||||||
(if (= ENOENT (system-error-errno args))
|
(if (= ENOENT (system-error-errno args))
|
||||||
#t
|
#false
|
||||||
(apply throw args))))))
|
(begin
|
||||||
(() #t))))
|
(close-port root)
|
||||||
|
(apply throw args)))))
|
||||||
;; TODO: the TOCTTOU race can be addressed once guile has bindings
|
((? port? new-root)
|
||||||
;; for fstatat, openat and friends.
|
(close root)
|
||||||
(define (mkdir-p/perms directory owner bits)
|
(loop tail new-root))
|
||||||
"Create the directory DIRECTORY and all its ancestors.
|
(#false
|
||||||
Verify no component of DIRECTORY is a symbolic link.
|
;; If not, create it.
|
||||||
Warning: this is currently suspect to a TOCTTOU race!"
|
(catch 'system-error
|
||||||
(verify-not-symbolic directory)
|
(lambda _
|
||||||
(mkdir-p directory)
|
(mkdirat root head))
|
||||||
(chown directory (passwd:uid owner) (passwd:gid owner))
|
(lambda args
|
||||||
(chmod directory bits))
|
;; Someone else created the directory. Unexpected but fine.
|
||||||
|
(unless (= EEXIST (system-error-errno args))
|
||||||
|
(close-port root)
|
||||||
|
(apply throw args))))
|
||||||
|
(retry)))))
|
||||||
|
(()
|
||||||
|
(catch 'system-error
|
||||||
|
(lambda ()
|
||||||
|
(chown root (passwd:uid owner) (passwd:gid owner))
|
||||||
|
(chmod root bits))
|
||||||
|
(lambda args
|
||||||
|
(close-port root)
|
||||||
|
(apply throw args)))
|
||||||
|
(close-port root)
|
||||||
|
(values)))))
|
||||||
|
|
||||||
(define* (copy-account-skeletons home
|
(define* (copy-account-skeletons home
|
||||||
#:key
|
#:key
|
||||||
|
|||||||
Reference in New Issue
Block a user