diff --git a/Makefile.am b/Makefile.am index 1e4fefe3fe..eb5d38231b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -446,6 +446,9 @@ dist_zshcompletion_DATA = etc/completion/zsh/_guix # Fish completion file. dist_fishcompletion_DATA = etc/completion/fish/guix.fish +# SELinux policy +dist_selinux_policy_DATA = etc/guix-daemon.cil + EXTRA_DIST = \ HACKING \ ROADMAP \ diff --git a/configure.ac b/configure.ac index f69f796484..398846f64b 100644 --- a/configure.ac +++ b/configure.ac @@ -54,6 +54,13 @@ AC_ARG_WITH([fish-completion-dir], [fishcompletiondir='${datadir}/fish/vendor_completions.d']) AC_SUBST([fishcompletiondir]) +AC_ARG_WITH([selinux-policy-dir], + AC_HELP_STRING([--with-selinux-policy-dir=DIR], + [name of the SELinux policy directory]), + [selinux_policydir="$withval"], + [selinux_policydir='${datadir}/selinux/']) +AC_SUBST([selinux_policydir]) + dnl Better be verbose. AC_MSG_CHECKING([for the store directory]) AC_MSG_RESULT([$storedir]) @@ -272,7 +279,8 @@ esac AC_CONFIG_FILES([Makefile po/guix/Makefile.in po/packages/Makefile.in - guix/config.scm]) + etc/guix-daemon.cil + guix/config.scm]) AC_CONFIG_FILES([test-env:build-aux/test-env.in], [chmod +x test-env]) AC_CONFIG_FILES([pre-inst-env:build-aux/pre-inst-env.in], diff --git a/doc/guix.texi b/doc/guix.texi index c3b7d07d84..68f6c12294 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -21,7 +21,7 @@ Copyright @copyright{} 2015, 2016 Mathieu Lirzin@* Copyright @copyright{} 2014 Pierre-Antoine Rault@* Copyright @copyright{} 2015 Taylan Ulrich Bayırlı/Kammer@* Copyright @copyright{} 2015, 2016, 2017 Leo Famulari@* -Copyright @copyright{} 2015, 2016, 2017 Ricardo Wurmus@* +Copyright @copyright{} 2015, 2016, 2017, 2018 Ricardo Wurmus@* Copyright @copyright{} 2016 Ben Woodcroft@* Copyright @copyright{} 2016, 2017 Chris Marusich@* Copyright @copyright{} 2016, 2017 Efraim Flashner@* @@ -123,6 +123,7 @@ Setting Up the Daemon * Build Environment Setup:: Preparing the isolated build environment. * Daemon Offload Setup:: Offloading builds to remote machines. +* SELinux Support:: Using an SELinux policy for the daemon. Package Management @@ -754,6 +755,7 @@ the daemon to download pre-built binaries. @menu * Build Environment Setup:: Preparing the isolated build environment. * Daemon Offload Setup:: Offloading builds to remote machines. +* SELinux Support:: Using an SELinux policy for the daemon. @end menu @node Build Environment Setup @@ -1081,6 +1083,92 @@ main node: @end example +@node SELinux Support +@subsection SELinux Support + +@cindex SELinux, daemon policy +@cindex mandatory access control, SELinux +@cindex security, guix-daemon +Guix includes an SELinux policy file at @file{etc/guix-daemon.cil} that +can be installed on a system where SELinux is enabled, in order to label +Guix files and to specify the expected behavior of the daemon. Since +GuixSD does not provide an SELinux base policy, the daemon policy cannot +be used on GuixSD. + +@subsubsection Installing the SELinux policy +@cindex SELinux, policy installation +To install the policy run this command as root: + +@example +semodule -i etc/guix-daemon.cil +@end example + +Then relabel the file system with @code{restorecon} or by a different +mechanism provided by your system. + +Once the policy is installed, the file system has been relabeled, and +the daemon has been restarted, it should be running in the +@code{guix_daemon_t} context. You can confirm this with the following +command: + +@example +ps -Zax | grep guix-daemon +@end example + +Monitor the SELinux log files as you run a command like @code{guix build +hello} to convince yourself that SELinux permits all necessary +operations. + +@subsubsection Limitations +@cindex SELinux, limitations + +This policy is not perfect. Here is a list of limitations or quirks +that should be considered when deploying the provided SELinux policy for +the Guix daemon. + +@enumerate +@item +@code{guix_daemon_socket_t} isn’t actually used. None of the socket +operations involve contexts that have anything to do with +@code{guix_daemon_socket_t}. It doesn’t hurt to have this unused label, +but it would be preferrable to define socket rules for only this label. + +@item +@code{guix gc} cannot access arbitrary links to profiles. By design, +the file label of the destination of a symlink is independent of the +file label of the link itself. Although all profiles under +$localstatedir are labelled, the links to these profiles inherit the +label of the directory they are in. For links in the user’s home +directory this will be @code{user_home_t}. But for links from the root +user’s home directory, or @file{/tmp}, or the HTTP server’s working +directory, etc, this won’t work. @code{guix gc} would be prevented from +reading and following these links. + +@item +The daemon’s feature to listen for TCP connections might no longer work. +This might require extra rules, because SELinux treats network sockets +differently from files. + +@item +Currently all files with a name matching the regular expression +@code{/gnu/store/.+-(guix-.+|profile)/bin/guix-daemon} are assigned the +label @code{guix_daemon_exec_t}; this means that @emph{any} file with +that name in any profile would be permitted to run in the +@code{guix_daemon_t} domain. This is not ideal. An attacker could +build a package that provides this executable and convince a user to +install and run it, which lifts it into the @code{guix_daemon_t} domain. +At that point SELinux could not prevent it from accessing files that are +allowed for processes in that domain. + +We could generate a much more restrictive policy at installation time, +so that only the @emph{exact} file name of the currently installed +@code{guix-daemon} executable would be labelled with +@code{guix_daemon_exec_t}, instead of using a broad regular expression. +The downside is that root would have to install or upgrade the policy at +installation time whenever the Guix package that provides the +effectively running @code{guix-daemon} executable is upgraded. +@end enumerate + @node Invoking guix-daemon @section Invoking @command{guix-daemon} diff --git a/etc/guix-daemon.cil.in b/etc/guix-daemon.cil.in new file mode 100644 index 0000000000..c0c82d8fbb --- /dev/null +++ b/etc/guix-daemon.cil.in @@ -0,0 +1,285 @@ +; -*- lisp -*- +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2018 Ricardo Wurmus +;;; +;;; 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 . + +;; This is a specification for SELinux 2.7 written in the SELinux Common +;; Intermediate Language (CIL). It refers to types that must be defined in +;; the system's base policy. + +(block guix_daemon + ;; Require existing types + (typeattributeset cil_gen_require init_t) + (typeattributeset cil_gen_require tmp_t) + (typeattributeset cil_gen_require nscd_var_run_t) + (typeattributeset cil_gen_require var_log_t) + (typeattributeset cil_gen_require domain) + + ;; Declare own types + (type guix_daemon_t) + (roletype object_r guix_daemon_t) + (type guix_daemon_conf_t) + (roletype object_r guix_daemon_conf_t) + (type guix_daemon_exec_t) + (roletype object_r guix_daemon_exec_t) + (type guix_daemon_socket_t) + (roletype object_r guix_daemon_socket_t) + (type guix_store_content_t) + (roletype object_r guix_store_content_t) + (type guix_profiles_t) + (roletype object_r guix_profiles_t) + + ;; These types are domains, thereby allowing process rules + (typeattributeset domain (guix_daemon_t guix_daemon_exec_t)) + + (level low (s0)) + + ;; When a process in init_t or guix_store_content_t spawns a + ;; guix_daemon_exec_t process, let it run in the guix_daemon_t context + (typetransition init_t guix_daemon_exec_t + process guix_daemon_t) + (typetransition guix_store_content_t guix_daemon_exec_t + process guix_daemon_t) + + ;; Permit communication with NSCD + (allow guix_daemon_t + nscd_var_run_t + (file (map read))) + (allow guix_daemon_t + nscd_var_run_t + (dir (search))) + (allow guix_daemon_t + nscd_var_run_t + (sock_file (write))) + (allow guix_daemon_t + nscd_t + (fd (use))) + (allow guix_daemon_t + nscd_t + (unix_stream_socket (connectto))) + + ;; Permit logging and temp file access + (allow guix_daemon_t + tmp_t + (lnk_file (setattr unlink))) + (allow guix_daemon_t + tmp_t + (dir (create + rmdir + add_name remove_name + open read write + getattr setattr + search))) + (allow guix_daemon_t + var_log_t + (file (create getattr open write))) + (allow guix_daemon_t + var_log_t + (dir (getattr write add_name))) + (allow guix_daemon_t + var_run_t + (lnk_file (read))) + (allow guix_daemon_t + var_run_t + (dir (search))) + + ;; Spawning processes, execute helpers + (allow guix_daemon_t + self + (process (fork))) + (allow guix_daemon_t + guix_daemon_exec_t + (file (execute execute_no_trans read open))) + + ;; TODO: unknown + (allow guix_daemon_t + root_t + (dir (mounton))) + (allow guix_daemon_t + fs_t + (filesystem (getattr))) + (allow guix_daemon_conf_t + fs_t + (filesystem (associate))) + + ;; Build isolation + (allow guix_daemon_t + guix_store_content_t + (file (mounton))) + (allow guix_store_content_t + fs_t + (filesystem (associate))) + (allow guix_daemon_t + guix_store_content_t + (dir (mounton))) + (allow guix_daemon_t + guix_daemon_t + (capability (net_admin + fsetid fowner + chown setuid setgid + dac_override dac_read_search + sys_chroot))) + (allow guix_daemon_t + fs_t + (filesystem (unmount))) + (allow guix_daemon_t + devpts_t + (filesystem (mount))) + (allow guix_daemon_t + devpts_t + (chr_file (setattr getattr))) + (allow guix_daemon_t + tmpfs_t + (filesystem (mount))) + (allow guix_daemon_t + tmpfs_t + (dir (getattr))) + (allow guix_daemon_t + proc_t + (filesystem (mount))) + (allow guix_daemon_t + null_device_t + (chr_file (getattr open read write))) + (allow guix_daemon_t + kvm_device_t + (chr_file (getattr))) + (allow guix_daemon_t + zero_device_t + (chr_file (getattr))) + (allow guix_daemon_t + urandom_device_t + (chr_file (getattr))) + (allow guix_daemon_t + random_device_t + (chr_file (getattr))) + (allow guix_daemon_t + devtty_t + (chr_file (getattr))) + + ;; Access to store items + (allow guix_daemon_t + guix_store_content_t + (dir (reparent + create + getattr setattr + search rename + add_name remove_name + open write + rmdir))) + (allow guix_daemon_t + guix_store_content_t + (file (create + lock + setattr getattr + execute execute_no_trans + link unlink + map + rename + open read write))) + (allow guix_daemon_t + guix_store_content_t + (lnk_file (create + getattr setattr + link unlink + read + rename))) + + ;; Access to configuration files and directories + (allow guix_daemon_t + guix_daemon_conf_t + (dir (search + setattr getattr + add_name remove_name + open read write))) + (allow guix_daemon_t + guix_daemon_conf_t + (file (create + lock + map + getattr setattr + unlink + open read write))) + (allow guix_daemon_t + guix_daemon_conf_t + (lnk_file (create getattr rename unlink))) + + ;; Access to profiles + (allow guix_daemon_t + guix_profiles_t + (dir (getattr setattr read open))) + (allow guix_daemon_t + guix_profiles_t + (lnk_file (read getattr))) + + ;; Access to profile links in the home directory + ;; TODO: allow access to profile links *anywhere* on the filesystem + (allow guix_daemon_t + user_home_t + (lnk_file (read getattr))) + (allow guix_daemon_t + user_home_t + (dir (search))) + + ;; Socket operations + (allow guix_daemon_t + init_t + (fd (use))) + (allow guix_daemon_t + init_t + (unix_stream_socket (write))) + (allow guix_daemon_t + guix_daemon_conf_t + (unix_stream_socket (listen))) + (allow guix_daemon_t + guix_daemon_conf_t + (sock_file (create unlink))) + (allow guix_daemon_t + self + (unix_stream_socket (create + read write + connect bind accept + getopt setopt))) + (allow guix_daemon_t + self + (fifo_file (write read))) + (allow guix_daemon_t + self + (udp_socket (ioctl create))) + + ;; Label file system + (filecon "@guix_sysconfdir@/guix(/.*)?" + any (system_u object_r guix_daemon_conf_t (low low))) + (filecon "@guix_localstatedir@/guix(/.*)?" + any (system_u object_r guix_daemon_conf_t (low low))) + (filecon "@guix_localstatedir@/guix/profiles(/.*)?" + any (system_u object_r guix_profiles_t (low low))) + (filecon "/gnu" + dir (unconfined_u object_r guix_store_content_t (low low))) + (filecon "@storedir@(/.+)?" + any (unconfined_u object_r guix_store_content_t (low low))) + (filecon "@storedir@/[^/]+/.+" + any (unconfined_u object_r guix_store_content_t (low low))) + (filecon "@prefix@/bin/guix-daemon" + file (system_u object_r guix_daemon_exec_t (low low))) + (filecon "@storedir@/.+-(guix-.+|profile)/bin/guix-daemon" + file (system_u object_r guix_daemon_exec_t (low low))) + (filecon "@storedir@/.+-(guix-.+|profile)/libexec/guix-authenticate" + file (system_u object_r guix_daemon_exec_t (low low))) + (filecon "@storedir@/.+-(guix-.+|profile)/libexec/guix/(.*)?" + any (system_u object_r guix_daemon_exec_t (low low))) + (filecon "@guix_localstatedir@/guix/daemon-socket/socket" + any (system_u object_r guix_daemon_socket_t (low low))))