services: Prevent following symlinks during activation.

This addresses a potential security issue, where a compromised
service could trick the activation code in changing the permissions,
owner and group of arbitrary files.  However, this patch is
currently only a partial fix, due to a TOCTTOU (time-of-check to
time-of-use) race, which can be fixed once guile has bindings
to openat and friends.

Fixes: <https://lists.gnu.org/archive/html/guix-devel/2021-01/msg00388.html>

* gnu/build/activation.scm: new procedure 'mkdir-p/perms'.
* gnu/services/authentication.scm
  (%nslcd-activation, nslcd-service-type): use new procedure.
* gnu/services/cups.scm (%cups-activation): likewise.
* gnu/services/dbus.scm (dbus-activation): likewise.
* gnu/services/dns.scm (knot-activation): likewise.

Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
Maxime Devos 2021-02-14 12:57:32 +01:00 committed by Ludovic Courtès
parent 1a1d0fe505
commit 520bac7ed0
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
5 changed files with 96 additions and 45 deletions

View file

@ -1,6 +1,11 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2015 Mark H Weaver <mhw@netris.org> ;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
;;; Copyright © 2015, 2018 Mark H Weaver <mhw@netris.org>
;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net>
;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -37,7 +42,8 @@ (define-module (gnu build activation)
activate-modprobe activate-modprobe
activate-firmware activate-firmware
activate-ptrace-attach activate-ptrace-attach
activate-current-system)) activate-current-system
mkdir-p/perms))
;;; Commentary: ;;; Commentary:
;;; ;;;
@ -55,6 +61,47 @@ (define %skeleton-directory
(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 (verify-not-symbolic dir)
"Verify DIR or its ancestors aren't symbolic links."
(define absolute?
(string-prefix? "/" dir))
(define not-slash
(char-set-complement (char-set #\/)))
(define (verify-component file)
(unless (eq? 'directory (stat:type (lstat file)))
(error "file name component is not a directory" dir)))
(let loop ((components (string-tokenize dir not-slash))
(root (if absolute?
""
".")))
(match components
((head tail ...)
(let ((file (string-append root "/" head)))
(catch 'system-error
(lambda ()
(verify-component file)
(loop tail file))
(lambda args
(if (= ENOENT (system-error-errno args))
#t
(apply throw args))))))
(() #t))))
;; TODO: the TOCTTOU race can be addressed once guile has bindings
;; for fstatat, openat and friends.
(define (mkdir-p/perms directory owner bits)
"Create the directory DIRECTORY and all its ancestors.
Verify no component of DIRECTORY is a symbolic link.
Warning: this is currently suspect to a TOCTTOU race!"
(verify-not-symbolic directory)
(mkdir-p directory)
(chown directory (passwd:uid owner) (passwd:gid owner))
(chmod directory bits))
(define* (copy-account-skeletons home (define* (copy-account-skeletons home
#:key #:key
(directory %skeleton-directory) (directory %skeleton-directory)

View file

@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018 Danny Milosavljevic <dannym@scratchpost.org> ;;; Copyright © 2018 Danny Milosavljevic <dannym@scratchpost.org>
;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net> ;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -31,6 +32,7 @@ (define-module (gnu services authentication)
#:use-module (guix gexp) #:use-module (guix gexp)
#:use-module (guix records) #:use-module (guix records)
#:use-module (guix packages) #:use-module (guix packages)
#:use-module (guix modules)
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (srfi srfi-26) #:use-module (srfi srfi-26)
@ -521,6 +523,16 @@ (module pam-ldap-module))))
(define (pam-ldap-pam-services config) (define (pam-ldap-pam-services config)
(list (pam-ldap-pam-service config))) (list (pam-ldap-pam-service config)))
(define %nslcd-activation
(with-imported-modules (source-module-closure '((gnu build activation)))
#~(begin
(use-modules (gnu build activation))
(let ((rundir "/var/run/nslcd")
(user (getpwnam "nslcd")))
(mkdir-p/perms rundir user #o755)
(when (file-exists? "/etc/nslcd.conf")
(chmod "/etc/nslcd.conf" #o400))))))
(define nslcd-service-type (define nslcd-service-type
(service-type (service-type
(name 'nslcd) (name 'nslcd)
@ -531,15 +543,7 @@ (define nslcd-service-type
(service-extension etc-service-type (service-extension etc-service-type
nslcd-etc-service) nslcd-etc-service)
(service-extension activation-service-type (service-extension activation-service-type
(const #~(begin (const %nslcd-activation))
(use-modules (guix build utils))
(let ((rundir "/var/run/nslcd")
(user (getpwnam "nslcd")))
(mkdir-p rundir)
(chown rundir (passwd:uid user) (passwd:gid user))
(chmod rundir #o755)
(when (file-exists? "/etc/nslcd.conf")
(chmod "/etc/nslcd.conf" #o400))))))
(service-extension pam-root-service-type (service-extension pam-root-service-type
pam-ldap-pam-services) pam-ldap-pam-services)
(service-extension nscd-service-type (service-extension nscd-service-type

View file

@ -4,6 +4,7 @@
;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net> ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2019 Alex Griffin <a@ajgrf.com> ;;; Copyright © 2019 Alex Griffin <a@ajgrf.com>
;;; Copyright © 2019 Tobias Geerinckx-Rice <me@tobias.gr> ;;; Copyright © 2019 Tobias Geerinckx-Rice <me@tobias.gr>
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -31,6 +32,7 @@ (define-module (gnu services cups)
#:use-module (guix packages) #:use-module (guix packages)
#:use-module (guix records) #:use-module (guix records)
#:use-module (guix gexp) #:use-module (guix gexp)
#:use-module (guix modules)
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:use-module ((srfi srfi-1) #:select (append-map find)) #:use-module ((srfi srfi-1) #:select (append-map find))
#:export (cups-service-type #:export (cups-service-type
@ -871,13 +873,11 @@ (define-configuration opaque-cups-configuration
(define %cups-activation (define %cups-activation
;; Activation gexp. ;; Activation gexp.
(with-imported-modules '((guix build utils)) (with-imported-modules (source-module-closure '((gnu build activation)
(guix build utils)))
#~(begin #~(begin
(use-modules (guix build utils)) (use-modules (gnu build activation)
(define (mkdir-p/perms directory owner perms) (guix build utils))
(mkdir-p directory)
(chown directory (passwd:uid owner) (passwd:gid owner))
(chmod directory perms))
(define (build-subject parameters) (define (build-subject parameters)
(string-concatenate (string-concatenate
(map (lambda (pair) (map (lambda (pair)

View file

@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2019, 2020 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com> ;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com>
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -28,6 +29,7 @@ (define-module (gnu services dbus)
#:use-module (guix gexp) #:use-module (guix gexp)
#:use-module ((guix packages) #:select (package-name)) #:use-module ((guix packages) #:select (package-name))
#:use-module (guix records) #:use-module (guix records)
#:use-module (guix modules)
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:export (dbus-configuration #:export (dbus-configuration
@ -161,24 +163,23 @@ (define dbus-setuid-programs
(define (dbus-activation config) (define (dbus-activation config)
"Return an activation gexp for D-Bus using @var{config}." "Return an activation gexp for D-Bus using @var{config}."
(with-imported-modules (source-module-closure
'((gnu build activation)
(guix build utils)))
#~(begin #~(begin
(use-modules (guix build utils)) (use-modules (gnu build activation)
(guix build utils))
(mkdir-p "/var/run/dbus")
(let ((user (getpwnam "messagebus"))) (let ((user (getpwnam "messagebus")))
(chown "/var/run/dbus"
(passwd:uid user) (passwd:gid user))
;; This directory contains the daemon's socket so it must be ;; This directory contains the daemon's socket so it must be
;; world-readable. ;; world-readable.
(chmod "/var/run/dbus" #o755)) (mkdir-p/perms "/var/run/dbus" user #o755))
(unless (file-exists? "/etc/machine-id") (unless (file-exists? "/etc/machine-id")
(format #t "creating /etc/machine-id...~%") (format #t "creating /etc/machine-id...~%")
(invoke (string-append #$(dbus-configuration-dbus config) (invoke (string-append #$(dbus-configuration-dbus config)
"/bin/dbus-uuidgen") "/bin/dbus-uuidgen")
"--ensure=/etc/machine-id")))) "--ensure=/etc/machine-id")))))
(define dbus-shepherd-service (define dbus-shepherd-service
(match-lambda (match-lambda

View file

@ -2,6 +2,7 @@
;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu> ;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com> ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
;;; Copyright © 2020 Pierre Langlois <pierre.langlois@gmx.com> ;;; Copyright © 2020 Pierre Langlois <pierre.langlois@gmx.com>
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -28,6 +29,7 @@ (define-module (gnu services dns)
#:use-module (guix packages) #:use-module (guix packages)
#:use-module (guix records) #:use-module (guix records)
#:use-module (guix gexp) #:use-module (guix gexp)
#:use-module (guix modules)
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (srfi srfi-26) #:use-module (srfi srfi-26)
#:use-module (srfi srfi-34) #:use-module (srfi srfi-34)
@ -607,17 +609,14 @@ (define %knot-accounts
(shell (file-append shadow "/sbin/nologin"))))) (shell (file-append shadow "/sbin/nologin")))))
(define (knot-activation config) (define (knot-activation config)
(with-imported-modules (source-module-closure '((gnu build activation)))
#~(begin #~(begin
(use-modules (guix build utils)) (use-modules (gnu build activation))
(define (mkdir-p/perms directory owner perms)
(mkdir-p directory)
(chown directory (passwd:uid owner) (passwd:gid owner))
(chmod directory perms))
(mkdir-p/perms #$(knot-configuration-run-directory config) (mkdir-p/perms #$(knot-configuration-run-directory config)
(getpwnam "knot") #o755) (getpwnam "knot") #o755)
(mkdir-p/perms "/var/lib/knot" (getpwnam "knot") #o755) (mkdir-p/perms "/var/lib/knot" (getpwnam "knot") #o755)
(mkdir-p/perms "/var/lib/knot/keys" (getpwnam "knot") #o755) (mkdir-p/perms "/var/lib/knot/keys" (getpwnam "knot") #o755)
(mkdir-p/perms "/var/lib/knot/keys/keys" (getpwnam "knot") #o755))) (mkdir-p/perms "/var/lib/knot/keys/keys" (getpwnam "knot") #o755))))
(define (knot-shepherd-service config) (define (knot-shepherd-service config)
(let* ((config-file (knot-config-file config)) (let* ((config-file (knot-config-file config))