all messages for Guix-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Maxime Devos <maximedevos@telenet.be>
To: guix-devel@gnu.org
Subject: Re: Potential security weakness in Guix services
Date: Fri, 29 Jan 2021 16:25:00 +0100	[thread overview]
Message-ID: <f07a3a59120d0f974b393aa9acbc9d89039ed57e.camel@telenet.be> (raw)
In-Reply-To: <d8c3724fa43ad9512fc930ee05a073b23b7df30e.camel@telenet.be>


[-- Attachment #1.1: Type: text/plain, Size: 2077 bytes --]

On Fri, 2021-01-29 at 14:33 +0100, Maxime Devos wrote:
> Hi Guix,
> [...]
> > Below is a summary of their messages, including a mitigation proposal.
> > Your feedback is requested!
> 
> I'm writing a patch right now.  It's a little more elaborate than my
> mkdir-p/own proposal.  In the patch, directories with owner, group
> and permission bits are created via extensions to a ‘fs-entry-service-type’,
> which will perform various basic consistency checks at build time
> (e.g., no directory can be owned by multiple users).
> 
> I'll post a draft when it's ready.

[First draft is attached, with many parts missing, it doesn't even
compile]
I think I've got a basic idea on how to handle this.
Some problems to address:

* Guile does not have ‘openat, mkdirat’ procedures.
  How to resolve: implement these upstream, write FFI bindings,
  or use 'chdir' carefully.

* Verify whether symlinks are handled correctly.
  (stat vs lstat vs fstatat ...)

* Perhaps O_NOCTTY, O_NOLINK, O_NOTRANS, O_NONBLOCK, O_DIRECTORY,
  O_NOFOLLOW ... need to be used at some places.

* Maybe fsync needs to be used in some places.

  The service definitions don't seem to do that anywhere when chmodding
  and chowning, so not implementing this shouldn't be a regression,
  but it does seem like something to verify.

* On some Linux versions and filesystems,
  the use of O_TMPFILE might simplify reasoning about security properties,
  race windows, etc., but idk if it's supported on the Hurd, and which
  (linux version, filesystem) combinations are supported.

* Mounting filesystems.
  Can all filesystems used by services when activating be assumed to be up?
  idk. 

* Support more security stuff (SELinux, SMACK, POSIX ACL, ...)
  Something for the far future, perhaps?

Perhaps I should just implement the basic mkdir-p/own proposal for now,
and in the future something more elaborate can be implemented?
All but the last two points probably still apply, though.

I'll take a look at how other systems handle this.

Maxime

[-- Attachment #1.2: directory-setup.scm --]
[-- Type: text/x-scheme, Size: 8414 bytes --]

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
(define-module (gnu services fs-entry)
  #:use-module stuff ...)
;;;
;;; Create directory structures for services with security context,
;;; without race conditions.  Symbolic links are not followed.
;;;

;; Values passed in extensions to @code{fs-entry-service-type}.
;; TODO maybe also allow defining SELinux, SMACK and POSIX ACL.
(define-record-type* <fs-entry>
  fs-entry make-fs-entry fs-entry?
  (where fs-entry-where) ; /name/of/file
  (bits  fs-entry-bits) ; permission bits
  (type  fs-entry-type) ; directory, regular or symlink
  (owner fs-entry-owner) ; owner, as a string
  (group fs-entry-group)) ; group, as a string

;; Likewise, but converted to a tree structure.
(define-record-type* <fs-entry/tree>
  fs-entry/tree make-fs-entry/tree fs-entry/tree?
  (name     fs-entry/tree-where) ; basename
  (bits     fs-entry/tree-bits) ; permission bits
  (type     fs-entry/tree-type) ; directory, regular or symlink
  (owner    fs-entry/tree-owner) ; owner, as a string
  (group    fs-entry/tree-group) ; group, as a string
  ;; boolean, for when <fs-entry> for /a/b is defined,
  ;; but not <fs-entry> for / and /a are defined, in which case
  ;; a ‘filler?’ <fs-entry/tree> for / and /a are created
  ;; in fs-entries->tree, which have as child /a and /a/b
  ;; respectively.
  ;;
  ;; (Note: the security context for / is currently ignored)
  (filler?  fs-entry/tree-filler?
            (default #f))
  ;; list of known children
  (children fs-entry-children))

(define %directory-separator #\/)
(define (fs-entry-name-components x)
  (string-split (fs-entry-where x) %directory-separator))

(define (fs-entries->tree list)
  "Translate @var{list}, a list of @code{fs-entry}, into a tree
structure (of <fs-entry/tree>)."
  ;; Sort list to prepare for a depth-first construction
  (define (list<? component<? x y)
    (cond ((and (null? x) (null? y)) #f)
	  ((null? x) #t)
	  ((null? y) #f)
	  ((component<? (car x) (car y)) #t)
	  ((component<? (car y) (car x)) #f)
	  (else (list<? component<? (cdr x) (cdr y)))))
  (define (entry<? x y)
    (list<? string<?
            (fs-entry-name-components x)
            (fs-entry-name-components y)))
  (define sorted (sort list entry<?))
  ;; Now construct the tree.
  ;; XXX insert filler for ???
  XXX
  ;; XXX make sure there are no inconsistencies
  ;; XXX prevent some screw-ups such as chowning or chmodding
  ;; entries from /gnu/store/.... Maybe that's prevented
  ;; by bind-mounting anyway.
  ;; (e.g. a symlink and directory with the same name).
  )

(define (tree->alist tree)
  `((name . ,(fs-entry/tree-name tree))
    (bits . ,(fs-entry/tree-bits tree))
    (type . ,(fs-entry/tree-type tree))
    (owner . ,(fs-entry/tree-owner tree))
    (group . ,(fs-entry/tree-group tree))
    (filler? . ,(fs-entry/tree-filler? tree))))

(define* (fs-entry-activation tree)
  ;; XXX for efficiency reasons, it might be useful to implement
  ;; some sort of caching mechanism to avoid looking up a uid/gid
  ;; multiple times from user name / user gid.
  #~(let* ((root (open "/" O_RDONLY))
           (ref  (lambda (sexp-tree obj))))
      (use-modules (srfi srfi-26))
      ;; XXX dynamic-wind stuff to close directories
      ;; and leaves.
      ;; XXX bindings to openat, or use chdir
      (define (activate-children! parent-fd parent-tree)
        (for-each (cute activate-child! parent-fd <>)
                  (assq-ref parent-tree 'children)))
      (define (activate-child! parent-fd child-tree)
        (let* ((name (assq-ref child-tree 'name))
               (child
                ;; XXX define
                (false-if-not-found
                 (openat parent-fd (fs-entry/tree-name child-tree)))))
          (if child ;; already exists
              (maybe-fixup-child! child child-tree)
              (create-child! parent-fd name child-tree))))
      (define (maybe-fixup-child! child child-tree)
        ;; First check if any changes need to be made.
        ;; If not, don't perform any write I/O.
        ;; XXX what happens if child is a symbolic link?
        ;; XXX handle (assq-ref child 'filler?)
        (let* ((stat (stat child))
               (child:bits (assq-ref child-tree 'bits))
               (child:uid (xxx (assq-ref child-tree 'uid)))
               (child:gid (xxx (assq-ref child-tree 'gid)))
               (bits-ok? (= (stat:perms child) child:bits))
               (owner-ok? (= (stat:uid child) child:uid))
               (group-ok? (= (stat:gid child) child:gid))
               (type-ok? (eq? (stat:type child)
                              (assq-ref child-tree 'type))))
          ;; XXX if programs hold open files to some files,
          ;; which aren't permitted by the new configuration,
          ;; then these programs ???
          ;; XXX log stuff perhaps
          (cond ((not type-ok?) (xxx-what-now))
                ;; Easy, no risk of accidentally creating
                ;; a setuid/setgid binary.
                ((and group-ok? owner-ok? (not bits-ok?))
                 (chmod child child:bits)
                 (activate-children! child child-tree))
                ;; XXX this relies on the Linux behaviour
                ;; of clearing setuid and setgid at chown
                ;; (in some cases), check the behaviour
                ;; on the Hurd and Linux
                ((not (and group-ok? owner-ok?))
                 ;; XXX check behaviour on symbolic links
                 (chown child child:uid child:gid)
                 (chmod child child:bits)
                 (activate-children! child child-tree))
                ;; Everything is OK!  Descend down the tree.
                ((and bits-ok? owner-ok? group-ok? type-ok?)
                 (activate-children! child child-tree))
                (else (XXX-I-missed-a-case)))))
      (define (create-child! parent-fd name child-tree)
        (case (assq-ref child-tree 'type)
          ((regular)
           ;; XXX default contents? Maybe allow including
           ;; a gexp #~(lambda (file-fd) do-stuff)
           ;; in the <fs-entry>?
           xxx-???-regular)
          ((directory)
           ;; XXX handle filler?
           ;; XXX check security implications of sticky-bit
           (mkdirat parent-fd name (assq-ref child-tree 'bits))
           (chown xxx-the-just-created-dir (assq-ref child-tree 'owner))
           (activate-chilren! xxx-the-just-created-dir child-tree))
          ;; XXX target?  Also, does any service actually require
          ;; this?
          ((symlink) xxx-???-symlink)
          (else ???)))
      (call-with-saved-umask
       (lambda ()
         ;; Prevent a race windows were newly-created directories
         ;; are temporarily world-executable where inappropriate.
         (umask #o777)
         (activate-children! root tree)))))

(define fs-entry-service-type
  (service-type (name 'fs-entries)
                (extensions
                 (list (service-extension activation-service-type
                                          fs-entry-activation)))
                (compose concatenate)
                (extend append)
                (description
                 "Create directory structures, with permission
bits, owner and groups (together called the security context),
without race conditions.  The value of this service is a list
of @code{fs-entry}.  The old security context is overwritten
at activation time, and some inconsistencies are detected at
build time.

If some parent directories of a @code{fs-entry} are not
explicitely specfied, it is required (at activation time)
they are root-owned (both user and group) and
world-unwritable.")))

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

  reply	other threads:[~2021-01-29 15:26 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-01-28 21:53 Potential security weakness in Guix services Leo Famulari
2021-01-29 13:33 ` Maxime Devos
2021-01-29 15:25   ` Maxime Devos [this message]
2021-02-01 15:35 ` Ludovic Courtès
2021-02-01 15:47   ` Julien Lepiller
2021-02-01 16:19     ` Maxime Devos
2021-02-02 13:07       ` Ludovic Courtès
2021-02-02 13:38         ` Maxime Devos
2021-02-02 15:30           ` Maxime Devos
2021-02-05  9:57           ` Ludovic Courtès
2021-02-05 12:20             ` Maxime Devos
2021-02-05 14:16               ` Maxime Devos
2021-02-06 21:28                 ` Ludovic Courtès
2021-02-06 22:01                   ` Maxime Devos
2021-02-10 20:45                     ` Ludovic Courtès
2021-02-06 21:26               ` Ludovic Courtès
2021-02-14 12:29                 ` TOCTTOU race (was: Potential security weakness in Guix services) Maxime Devos
2021-02-14 17:19                   ` Bengt Richter
2021-02-18 17:54                   ` TOCTTOU race Ludovic Courtès
2021-02-19 18:01                     ` Maxime Devos
2021-02-22  8:54                       ` Ludovic Courtès
2021-02-22 19:13                         ` Maxime Devos
2021-02-23 15:30                           ` Ludovic Courtès
2021-02-27  7:41                             ` Maxime Devos
2021-03-10 10:07                               ` Ludovic Courtès
2021-02-10 20:54             ` Potential security weakness in Guix services Christopher Lemmer Webber

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f07a3a59120d0f974b393aa9acbc9d89039ed57e.camel@telenet.be \
    --to=maximedevos@telenet.be \
    --cc=guix-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/guix.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.