From mboxrd@z Thu Jan 1 00:00:00 1970 From: Ricardo Wurmus Subject: [PATCH] Add SELinux policy for guix-daemon. Date: Thu, 25 Jan 2018 17:17:14 +0100 Message-ID: Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:37569) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eekDY-0004iM-FF for guix-devel@gnu.org; Thu, 25 Jan 2018 11:17:40 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1eekDR-0007zQ-7M for guix-devel@gnu.org; Thu, 25 Jan 2018 11:17:36 -0500 Received: from venus.bbbm.mdc-berlin.de ([141.80.25.30]:52288) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1eekDQ-0007yJ-L7 for guix-devel@gnu.org; Thu, 25 Jan 2018 11:17:29 -0500 Received: from localhost (localhost [127.0.0.1]) by venus.bbbm.mdc-berlin.de (Postfix) with ESMTP id B8D53380407 for ; Thu, 25 Jan 2018 17:17:26 +0100 (CET) Received: from venus.bbbm.mdc-berlin.de ([127.0.0.1]) by localhost (venus.bbbm.mdc-berlin.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id lBiab6qCoqvy for ; Thu, 25 Jan 2018 17:17:25 +0100 (CET) Received: from HTCAONE.mdc-berlin.net (puck.citx.mdc-berlin.de [141.80.36.101]) (using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by venus.bbbm.mdc-berlin.de (Postfix) with ESMTPS for ; Thu, 25 Jan 2018 17:17:25 +0100 (CET) List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org Sender: "Guix-devel" To: guix-devel@gnu.org --=-=-= Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Hi Guix, attached is a patch that adds an SELinux policy for the guix-daemon. The policy defines the guix_daemon_t domain and specifies what labels may be accessed and how by processes running in that domain. These file labels are defined: * guix_daemon_conf_t for Guix configuration files (in localstatedir and sysconfdir) * guix_daemon_exec_t for executables spawned by the daemon (which are allowed to run in the guix_daemon_t domain) * guix_daemon_socket_t for the daemon socket file * guix_profiles_t for the contents of the profiles directory The =E2=80=9Cfilecon=E2=80=9D statements near the bottom of the file spec= ify which labels are to be used for what file names. I tested this with =E2=80=9Cguix build --no-grafts --check hello=E2=80=9D= , =E2=80=9Cguix build samtools=E2=80=9D, =E2=80=9Cguix gc -C 1k=E2=80=9D, and =E2=80=9Cguix pac= kage -p ~/foo -i hello=E2=80=9D; no operations were blocked by SELinux. If you want to test this on Fedora, set SELinux to permissive, and make sure to configure Guix properly (i.e. set localstatedir, prefix, and sysconfdir). Then install the policy with =E2=80=9Csudo semodule -i etc/guix-daemon.cil=E2=80=9D. Then relabel the filesystem (at least /gnu= , $localstatedir, $sysconfdir, and $prefix) with something like this: sudo restorecon -R /gnu $localstatedir $sysconfdir $prefix This will take a very long time (a couple of hours). Restart the daemon. Check that it now runs in the guix_daemon_t context: ps -Zax | grep /bin/guix-daemon This should return something like this system_u:system_r:guix_daemon.guix_daemon_t:s0 14886 ? Ss 0:00 /root= /.guix-profile/bin/guix-daemon --build-users-group=3Dguix-builder Check the audit log for violations: sudo tail -f /var/log/audit/audit.log | grep x-daemon And then use Guix: guix build --no-grafts --check hello The audit log shouldn=E2=80=99t show you any complaints. At this point y= ou could probably switch to enforcing mode, but I haven=E2=80=99t tested thi= s myself for no particular reason. Open issues: * guix_daemon_socket_t isn=E2=80=99t actually used. All of the socket operations that I observed involve contexts that don=E2=80=99t have any= thing to do with guix_daemon_socket_t. It doesn=E2=80=99t hurt to have this = unused label, but I would have preferred to define socket rules for only this label. Oh well. * =E2=80=9Cguix gc=E2=80=9D cannot access arbitrary links to profiles. B= y 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=E2=80=99s home directory = this will be =E2=80=9Cuser_home_t=E2=80=9D (for which I=E2=80=99ve added a r= ule). But for links from root=E2=80=99s home directory, or /tmp, or the HTTP server=E2=80=99= s working directory =E2=80=A6 this won=E2=80=99t work. =E2=80=9Cguix gc=E2=80=9D= would be prevented from reading and following these links. * I don=E2=80=99t know if the daemon=E2=80=99s TCP listen feature still w= orks. I didn=E2=80=99t test it and assume that it would require extra rules, because SELinux treats network sockets differently from files. * Is this all correct? I don=E2=80=99t know! I only just learned about = the SELinux Common Intermediate Language (CIL), and the documentation is very sparse, so I have no idea if I did something stupid. It seems fine to me, but I must admit that I find it a bit uncomfortable to see so many access types in the rules. * I allowed type transitions from init_t to guix_daemon_t via guix_daemon_exec_t, but also from guix_store_content_t to guix_daemon_t via guix_daemon_exec_t. Type transitions are necessary to get from an allowed entry point to a domain. On Fedora =E2=80=9Cini= t_t=E2=80=9D is the domain in which processes are that are spawned by the init system. With the first type transition I permit these processes to transition to the guix_daemon_t domain when the executables are labeled as guix_daemon_exec_t (such as the daemon executable itself, and all the helpers it spawns). This much is obvious. But the second type transition is less obvious. It is needed to make sure that we can enter the guix_daemon_t domain even when running the daemon from an executable in the store (which will be running in the =E2=80=9Cguix_store_content_t=E2=80=9D domain). = Thinking of this, I wonder if maybe that=E2=80=99s actually a mistake and shouldn=E2= =80=99t be permitted. * A possible problem is that I assign all files with a name matching =E2=80=9C/gnu/store/.+-(guix-.+|profile)/bin/guix-daemon=E2=80=9D the l= abel =E2=80=9Cguix_daemon_exec_t=E2=80=9D; this means that *any* file with t= hat name in any profile would be permitted to run in the 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 guix_daemon_t domain. At that point SELinux could not prevent it from accessing files that are allowed for processes in that domain (such as the actual daemon). This makes me wonder if we could do better by generating a much more restrictive policy at installation time, so that only the *exact* file name of the currently installed guix-daemon executable would be labelled with guix_daemon_exec_t, instead of using a regular expression like that. This means that root would have to install/upgrade the policy at installation time whenever the Guix package that provides the effectively running guix-daemon executable is upgraded. Food for thought. Without further ado, here=E2=80=99s the patch: --=-=-= Content-Type: text/x-patch; charset="utf-8" Content-Disposition: inline; filename="0001-etc-Add-SELinux-policy-for-the-daemon.patch" Content-Transfer-Encoding: quoted-printable >From d20bae0953d5d0a6bf1c06ab44505af6dea4df4d Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Thu, 25 Jan 2018 15:21:07 +0100 Subject: [PATCH] etc: Add SELinux policy for the daemon. * etc/guix-daemon.cil.in: New file. * Makefile.am: Add dist_selinux_policy_DATA. * configure.ac: Handle --with-selinux-policy-dir. --- Makefile.am | 3 + configure.ac | 10 +- etc/guix-daemon.cil.in | 281 +++++++++++++++++++++++++++++++++++++++++++= ++++++ 3 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 etc/guix-daemon.cil.in diff --git a/Makefile.am b/Makefile.am index aebd3b1eb..8f8ca0059 100644 --- a/Makefile.am +++ b/Makefile.am @@ -431,6 +431,9 @@ dist_zshcompletion_DATA =3D etc/completion/zsh/_guix # Fish completion file. dist_fishcompletion_DATA =3D etc/completion/fish/guix.fish =20 +# SELinux policy +dist_selinux_policy_DATA =3D etc/guix-daemon.cil + EXTRA_DIST =3D \ HACKING \ ROADMAP \ diff --git a/configure.ac b/configure.ac index 1e3912248..de86bfdd3 100644 --- a/configure.ac +++ b/configure.ac @@ -54,6 +54,13 @@ AC_ARG_WITH([fish-completion-dir], [fishcompletiondir=3D'${datadir}/fish/vendor_completions.d']) AC_SUBST([fishcompletiondir]) =20 +AC_ARG_WITH([selinux-policy-dir], + AC_HELP_STRING([--with-selinux-policy-dir=3DDIR], + [name of the SELinux policy directory]), + [selinux_policydir=3D"$withval"], + [selinux_policydir=3D'${datadir}/selinux/']) +AC_SUBST([selinux_policydir]) + dnl Better be verbose. AC_MSG_CHECKING([for the store directory]) AC_MSG_RESULT([$storedir]) @@ -270,7 +277,8 @@ esac AC_CONFIG_FILES([Makefile po/guix/Makefile.in po/packages/Makefile.in - guix/config.scm]) + etc/guix-daemon.cil + guix/config.scm]) =20 AC_CONFIG_FILES([scripts/guix], [chmod +x scripts/guix]) AC_CONFIG_FILES([test-env:build-aux/test-env.in], [chmod +x test-env]) diff --git a/etc/guix-daemon.cil.in b/etc/guix-daemon.cil.in new file mode 100644 index 000000000..825c12712 --- /dev/null +++ b/etc/guix-daemon.cil.in @@ -0,0 +1,281 @@ +; -*- lisp -*- +;;; GNU Guix --- Functional package management for GNU +;;; Copyright =C2=A9 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 (a= t +;;; 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 . + +(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)))) --=20 2.15.1 --=-=-= Content-Type: text/plain -- Ricardo --=-=-=--