[lxc-devel] [PATCH RFC] apparmor: improve behavior when kernel lacks mount restrictions

Stéphane Graber stgraber at ubuntu.com
Fri Sep 19 21:58:23 UTC 2014


On Sun, Sep 14, 2014 at 03:49:32AM +0000, Serge Hallyn wrote:
> (If we go this route we can also drop the whole lsm_label_get()
> method and the lsm_label field in the attach context info)

I'm not too familiar with the lsm code and I tend to get lost in there,
so just a few questions to confirm there's no hidden problems:
 - We can drop lsm_label_get() and lsb_label because we'll have lxc_conf
   everywhere instead?
 - What happens for attach, do we need a matching code change or is it
   going through the same lsm_* codepath and so will also parse lxc_conf
   and respect the new key if set?

> 
> Apparmor policies require mount restrictions to fullfill many of
> their promises - for instance if proc can be mounted anywhere,
> then 'deny /proc/sysrq-trigger w' prevents only accidents, not
> malice.
> 
> The mount restrictions are not available in the upstream kernel.
> We can detect their presence through /sys.  In the past, when
> we detected it missing, we would not enable apparmor.  But that
> prevents apparmor from helping to prevent accidents.
> 
> At the same time, if the user accidentaly boots a kernel which
> has regressed, we do not want them starting the container thinking
> they are more protected than they are.
> 
> This patch:
> 
> 1. adds a lxc.aa_allow_incomplete = 1 container config flag.  If
> not set, then any container which is not set to run unconfined
> will refuse to run.   If set, then the container will run with
> apparmor protection.
> 
> 2. to pass this flag to the apparmor driver, we pass the container
> configuration (lxc_conf) to the lsm_label_set hook, rather than
> just the label.
> 
> 3. add a testcase.  To test the case were a kernel does not
> provide mount restrictions, we mount an empty directory over
> the /sys/kernel/security/apparmor/features/mount directory.  In
> order to have that not be unmounted in a new namespace, we must
> test using unprivileged containers (who cannot remove bind mounts
> which hide existing mount contents).

Took me a while to figure out exactly what this covers, I guess that's
the case where:
 - policy contains mount entries
 - parser support the mount stanza
 - kernel doesn't support it

Because the other cases we usually run into are those where:
 - policy contains new fancy stanza
 - parser doesn't support it
 - kernel doesn't support it

In which case the whole profile fails to load and I believe we already
fail in that case with an appropriate error (rather than start
unconfined).

> 
> Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
> ---
>  src/lxc/attach.c                  |   2 +-
>  src/lxc/conf.h                    |   1 +
>  src/lxc/confile.c                 |  14 +++
>  src/lxc/lsm/apparmor.c            |  32 +++++--
>  src/lxc/lsm/lsm.c                 |   4 +-
>  src/lxc/lsm/lsm.h                 |   8 +-
>  src/lxc/lsm/nop.c                 |   2 +-
>  src/lxc/lsm/selinux.c             |   6 +-
>  src/lxc/start.c                   |   7 +-
>  src/tests/Makefile.am             |   1 +
>  src/tests/lxc-test-apparmor-mount | 189 ++++++++++++++++++++++++++++++++++++++
>  11 files changed, 245 insertions(+), 21 deletions(-)
>  create mode 100755 src/tests/lxc-test-apparmor-mount
> 
> diff --git a/src/lxc/attach.c b/src/lxc/attach.c
> index 923c497..9d92240 100644
> --- a/src/lxc/attach.c
> +++ b/src/lxc/attach.c
> @@ -1043,7 +1043,7 @@ static int attach_child_main(void* data)
>  		int on_exec;
>  
>  		on_exec = options->attach_flags & LXC_ATTACH_LSM_EXEC ? 1 : 0;
> -		ret = lsm_process_label_set(init_ctx->lsm_label, 0, on_exec);
> +		ret = lsm_process_label_set(init_ctx->container->lxc_conf, 0, on_exec);
>  		if (ret < 0) {
>  			rexit(-1);
>  		}
> diff --git a/src/lxc/conf.h b/src/lxc/conf.h
> index df66f99..97d9f91 100644
> --- a/src/lxc/conf.h
> +++ b/src/lxc/conf.h
> @@ -311,6 +311,7 @@ struct lxc_conf {
>  	struct lxc_list hooks[NUM_LXC_HOOKS];
>  
>  	char *lsm_aa_profile;
> +	int lsm_aa_allow_incomplete;
>  	char *lsm_se_context;
>  	int tmp_umount_proc;
>  	char *seccomp;  // filename with the seccomp rules
> diff --git a/src/lxc/confile.c b/src/lxc/confile.c
> index 5de1241..af55feb 100644
> --- a/src/lxc/confile.c
> +++ b/src/lxc/confile.c
> @@ -58,6 +58,7 @@ static int config_tty(const char *, const char *, struct lxc_conf *);
>  static int config_ttydir(const char *, const char *, struct lxc_conf *);
>  static int config_kmsg(const char *, const char *, struct lxc_conf *);
>  static int config_lsm_aa_profile(const char *, const char *, struct lxc_conf *);
> +static int config_lsm_aa_incomplete(const char *, const char *, struct lxc_conf *);
>  static int config_lsm_se_context(const char *, const char *, struct lxc_conf *);
>  static int config_cgroup(const char *, const char *, struct lxc_conf *);
>  static int config_idmap(const char *, const char *, struct lxc_conf *);
> @@ -108,6 +109,7 @@ static struct lxc_config_t config[] = {
>  	{ "lxc.devttydir",            config_ttydir               },
>  	{ "lxc.kmsg",                 config_kmsg                 },
>  	{ "lxc.aa_profile",           config_lsm_aa_profile       },
> +	{ "lxc.aa_allow_incomplete",  config_lsm_aa_incomplete    },
>  	{ "lxc.se_context",           config_lsm_se_context       },
>  	{ "lxc.cgroup",               config_cgroup               },
>  	{ "lxc.id_map",               config_idmap                },
> @@ -1145,6 +1147,16 @@ static int config_lsm_aa_profile(const char *key, const char *value,
>  	return config_string_item(&lxc_conf->lsm_aa_profile, value);
>  }
>  
> +static int config_lsm_aa_incomplete(const char *key, const char *value,
> +				 struct lxc_conf *lxc_conf)
> +{
> +	int v = atoi(value);
> +
> +	lxc_conf->lsm_aa_allow_incomplete = v == 1 ? 1 : 0;
> +
> +	return 0;
> +}
> +
>  static int config_lsm_se_context(const char *key, const char *value,
>  				 struct lxc_conf *lxc_conf)
>  {
> @@ -2257,6 +2269,8 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv,
>  		return lxc_get_arch_entry(c, retv, inlen);
>  	else if (strcmp(key, "lxc.aa_profile") == 0)
>  		v = c->lsm_aa_profile;
> +	else if (strcmp(key, "lxc.aa_allow_incomplete") == 0)
> +		return lxc_get_conf_int(c, retv, inlen, c->lsm_aa_allow_incomplete);
>  	else if (strcmp(key, "lxc.se_context") == 0)
>  		v = c->lsm_se_context;
>  	else if (strcmp(key, "lxc.logfile") == 0)
> diff --git a/src/lxc/lsm/apparmor.c b/src/lxc/lsm/apparmor.c
> index f4c8d26..3e98a38 100644
> --- a/src/lxc/lsm/apparmor.c
> +++ b/src/lxc/lsm/apparmor.c
> @@ -29,6 +29,7 @@
>  
>  #include "log.h"
>  #include "lsm/lsm.h"
> +#include "conf.h"
>  
>  lxc_log_define(lxc_apparmor, lxc);
>  
> @@ -39,17 +40,23 @@ static int aa_enabled = 0;
>  #define AA_MOUNT_RESTR "/sys/kernel/security/apparmor/features/mount/mask"
>  #define AA_ENABLED_FILE "/sys/module/apparmor/parameters/enabled"
>  
> +static int mount_feature_enabled(void)
> +{
> +	struct stat statbuf;
> +	int ret;
> +	ret = stat(AA_MOUNT_RESTR, &statbuf);
> +	if (ret != 0)
> +		return 0;
> +	return 1;
> +}
> +
>  /* aa_getcon is not working right now.  Use our hand-rolled version below */
>  static int apparmor_enabled(void)
>  {
> -	struct stat statbuf;
>  	FILE *fin;
>  	char e;
>  	int ret;
>  
> -	ret = stat(AA_MOUNT_RESTR, &statbuf);
> -	if (ret != 0)
> -		return 0;
>  	fin = fopen(AA_ENABLED_FILE, "r");
>  	if (!fin)
>  		return 0;
> @@ -131,9 +138,11 @@ static int apparmor_am_unconfined(void)
>   *
>   * Notes: This relies on /proc being available.
>   */
> -static int apparmor_process_label_set(const char *label, int use_default,
> +static int apparmor_process_label_set(struct lxc_conf *conf, int use_default,
>  				      int on_exec)
>  {
> +	const char *label = conf->lsm_aa_profile;
> +
>  	if (!aa_enabled)
>  		return 0;
>  
> @@ -141,9 +150,20 @@ static int apparmor_process_label_set(const char *label, int use_default,
>  		if (use_default)
>  			label = AA_DEF_PROFILE;
>  		else
> -			return 0;
> +			label = "unconfined";
> +	}
> +
> +	if (!mount_feature_enabled() && strcmp(label, "unconfined") != 0) {
> +		WARN("Incomplete AppArmor support in your kernel");
> +		if (!conf->lsm_aa_allow_incomplete) {
> +			ERROR("If you really want to start this container, set");
> +			ERROR("lxc.aa_allow_incomplete = 1");
> +			ERROR("in your container configuration file");
> +			return -1;
> +		}
>  	}
>  
> +
>  	if (strcmp(label, "unconfined") == 0 && apparmor_am_unconfined()) {
>  		INFO("apparmor profile unchanged");
>  		return 0;
> diff --git a/src/lxc/lsm/lsm.c b/src/lxc/lsm/lsm.c
> index c994046..54b404c 100644
> --- a/src/lxc/lsm/lsm.c
> +++ b/src/lxc/lsm/lsm.c
> @@ -85,13 +85,13 @@ char *lsm_process_label_get(pid_t pid)
>  	return drv->process_label_get(pid);
>  }
>  
> -int lsm_process_label_set(const char *label, int use_default, int on_exec)
> +int lsm_process_label_set(struct lxc_conf *conf, int use_default, int on_exec)
>  {
>  	if (!drv) {
>  		ERROR("LSM driver not inited");
>  		return -1;
>  	}
> -	return drv->process_label_set(label, use_default, on_exec);
> +	return drv->process_label_set(conf, use_default, on_exec);
>  }
>  
>  #endif
> diff --git a/src/lxc/lsm/lsm.h b/src/lxc/lsm/lsm.h
> index 93f5b87..0d21b4c 100644
> --- a/src/lxc/lsm/lsm.h
> +++ b/src/lxc/lsm/lsm.h
> @@ -33,7 +33,7 @@ struct lsm_drv {
>  
>  	int   (*enabled)(void);
>  	char *(*process_label_get)(pid_t pid);
> -	int   (*process_label_set)(const char *label, int use_default,
> +	int   (*process_label_set)(struct lxc_conf *conf, int use_default,
>  				   int on_exec);
>  };
>  
> @@ -42,13 +42,15 @@ void        lsm_init(void);
>  int         lsm_enabled(void);
>  const char *lsm_name(void);
>  char       *lsm_process_label_get(pid_t pid);
> -int         lsm_process_label_set(const char *label, int use_default, int on_exec);
> +int         lsm_process_label_set(struct lxc_conf *conf,
> +		int use_default, int on_exec);
>  #else
>  static inline void        lsm_init(void) { }
>  static inline int         lsm_enabled(void) { return 0; }
>  static inline const char *lsm_name(void) { return "none"; }
>  static inline char       *lsm_process_label_get(pid_t pid) { return NULL; }
> -static inline int         lsm_process_label_set(const char *label, int use_default, int on_exec) { return 0; }
> +static inline int         lsm_process_label_set(struct lxc-conf *conf,
> +		int use_default, int on_exec) { return 0; }
>  #endif
>  
>  #endif
> diff --git a/src/lxc/lsm/nop.c b/src/lxc/lsm/nop.c
> index e5db124..5e10f4e 100644
> --- a/src/lxc/lsm/nop.c
> +++ b/src/lxc/lsm/nop.c
> @@ -29,7 +29,7 @@ static char *nop_process_label_get(pid_t pid)
>  	return NULL;
>  }
>  
> -static int nop_process_label_set(const char *label, int use_default,
> +static int nop_process_label_set(struct lxc_conf *conf, int use_default,
>  				 int on_exec)
>  {
>  	return 0;
> diff --git a/src/lxc/lsm/selinux.c b/src/lxc/lsm/selinux.c
> index f8dad95..9e33055 100644
> --- a/src/lxc/lsm/selinux.c
> +++ b/src/lxc/lsm/selinux.c
> @@ -29,6 +29,7 @@
>  
>  #include "log.h"
>  #include "lsm/lsm.h"
> +#include "conf.h"
>  
>  #define DEFAULT_LABEL "unconfined_t"
>  
> @@ -61,7 +62,7 @@ static char *selinux_process_label_get(pid_t pid)
>  /*
>   * selinux_process_label_set: Set SELinux context of a process
>   *
> - * @label   : the context to set
> + * @conf    : the container configuration
>   * @default : use the default context if label is NULL
>   * @on_exec : the new context will take effect on exec(2) not immediately
>   *
> @@ -69,9 +70,10 @@ static char *selinux_process_label_get(pid_t pid)
>   *
>   * Notes: This relies on /proc being available.
>   */
> -static int selinux_process_label_set(const char *label, int use_default,
> +static int selinux_process_label_set(struct lxc_conf *conf, int use_default,
>  				     int on_exec)
>  {
> +	const char *label = conf->lsm_se_context;
>  	if (!label) {
>  		if (use_default)
>  			label = DEFAULT_LABEL;
> diff --git a/src/lxc/start.c b/src/lxc/start.c
> index d9fbc8c..d4dcd56 100644
> --- a/src/lxc/start.c
> +++ b/src/lxc/start.c
> @@ -615,7 +615,6 @@ static int do_start(void *data)
>  {
>  	struct lxc_list *iterator;
>  	struct lxc_handler *handler = data;
> -	const char *lsm_label = NULL;
>  
>  	if (sigprocmask(SIG_SETMASK, &handler->oldmask, NULL)) {
>  		SYSERROR("failed to set sigprocmask");
> @@ -695,11 +694,7 @@ static int do_start(void *data)
>  		return -1;
>  
>  	/* Set the label to change to when we exec(2) the container's init */
> -	if (!strcmp(lsm_name(), "AppArmor"))
> -		lsm_label = handler->conf->lsm_aa_profile;
> -	else if (!strcmp(lsm_name(), "SELinux"))
> -		lsm_label = handler->conf->lsm_se_context;
> -	if (lsm_process_label_set(lsm_label, 1, 1) < 0)
> +	if (lsm_process_label_set(handler->conf, 1, 1) < 0)
>  		goto out_warn_father;
>  
>  	/* Some init's such as busybox will set sane tty settings on stdin,
> diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
> index 35bc1b3..e0969eb 100644
> --- a/src/tests/Makefile.am
> +++ b/src/tests/Makefile.am
> @@ -52,6 +52,7 @@ bin_SCRIPTS = lxc-test-autostart
>  
>  if DISTRO_UBUNTU
>  bin_SCRIPTS += \
> +	lxc-test-apparmor-mount \
>  	lxc-test-checkpoint-restore \
>  	lxc-test-ubuntu \
>  	lxc-test-unpriv \
> diff --git a/src/tests/lxc-test-apparmor-mount b/src/tests/lxc-test-apparmor-mount
> new file mode 100755
> index 0000000..503381e
> --- /dev/null
> +++ b/src/tests/lxc-test-apparmor-mount
> @@ -0,0 +1,189 @@
> +#!/bin/sh
> +
> +# apparmor_mount: test proper handling of apparmor in kernels
> +# without mount features
> +
> +# These require the ubuntu lxc package to be installed.
> +
> +# This program is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +
> +# This library 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
> +# Lesser General Public License for more details.
> +
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> +
> +# This test assumes an Ubuntu host
> +
> +set -e
> +
> +FAIL() {
> +	echo -n "Failed " >&2
> +	echo "$*" >&2
> +	exit 1
> +}
> +
> +run_cmd() {
> +	sudo -i -u $TUSER \
> +	    env http_proxy=${http_proxy:-} https_proxy=${https_proxy:-} \
> +	        XDG_RUNTIME_DIR=/run/user/$(id -u $TUSER) $*
> +}
> +
> +DONE=0
> +MOUNTSR=/sys/kernel/security/apparmor/features/mount
> +dnam=`mktemp -d`
> +cname=`basename $dnam`
> +cleanup() {
> +	run_cmd lxc-destroy -f -n $cname || true
> +	umount -l $MOUNTSR || true
> +	rmdir $dnam || true
> +	pkill -u $(id -u $TUSER) -9
> +	sed -i '/lxcunpriv/d' /run/lxc/nics /etc/lxc/lxc-usernet
> +	sed -i '/^lxcunpriv:/d' /etc/subuid /etc/subgid
> +	rm -Rf $HDIR /run/user/$(id -u $TUSER)
> +	deluser $TUSER
> +	if [ $DONE -eq 0 ]; then
> +		echo "FAIL"
> +		exit 1
> +	fi
> +	echo "PASS"
> +}
> +
> +trap cleanup exit
> +
> +# Only run on a normally configured ubuntu lxc system
> +if [ ! -d /sys/class/net/lxcbr0 ]; then
> +	echo "lxcbr0 is not configured."
> +	exit 1
> +fi
> +if [ "$(id -u)" != "0" ]; then
> +	echo "ERROR: Must run as root."
> +	exit 1
> +fi
> +
> +# This would be much simpler if we could run it as
> +# root.  However, in order to not have the bind mount
> +# of an empty directory over the securitfs 'mount' directory
> +# be removed, we need to do this as non-root.
> +
> +which newuidmap >/dev/null 2>&1 || { echo "'newuidmap' command is missing" >&2; exit 1; }
> +# create a test user
> +TUSER=lxcunpriv
> +HDIR=/home/$TUSER
> +
> +ARCH=i386
> +if type dpkg >/dev/null 2>&1; then
> +	ARCH=$(dpkg --print-architecture)
> +fi
> +
> +deluser $TUSER && rm -Rf $HDIR || true
> +useradd $TUSER
> +
> +mkdir -p $HDIR
> +echo "$TUSER veth lxcbr0 2" > /etc/lxc/lxc-usernet
> +sed -i '/^lxcunpriv:/d' /etc/subuid /etc/subgid
> +
> +usermod -v 910000-919999 -w 910000-919999 $TUSER
> +
> +mkdir -p $HDIR/.config/lxc/
> +cat > $HDIR/.config/lxc/default.conf << EOF
> +lxc.network.type = veth
> +lxc.network.link = lxcbr0
> +lxc.id_map = u 0 910000 9999
> +lxc.id_map = g 0 910000 9999
> +EOF
> +chown -R $TUSER: $HDIR
> +
> +mkdir -p /run/user/$(id -u $TUSER)
> +chown -R $TUSER: /run/user/$(id -u $TUSER)
> +
> +cd $HDIR
> +
> +cgm create all $TUSER
> +cgm chown all $TUSER $(id -u $TUSER) $(id -g $TUSER)
> +cgm movepid all $TUSER $$
> +
> +run_cmd mkdir -p $HDIR/.cache/lxc
> +[ -d /var/cache/lxc/download ] && \
> +    cp -R /var/cache/lxc/download $HDIR/.cache/lxc && \
> +    chown -R $TUSER: $HDIR/.cache/lxc
> +
> +run_cmd lxc-create -t download -n $cname -- -d ubuntu -r trusty -a $ARCH
> +
> +echo "test default confined container"
> +run_cmd lxc-start -n $cname -d
> +run_cmd lxc-wait -n $cname -s RUNNING
> +pid=`run_cmd lxc-info -p -H -n $cname`
> +profile=`cat /proc/$pid/attr/current`
> +if [ "x$profile" != "xlxc-container-default (enforce)" ]; then
> +	echo "FAIL: confined container was in profile $profile"
> +	exit 1
> +fi
> +run_cmd lxc-stop -n $cname
> +
> +echo "test regular unconfined container"
> +echo "lxc.aa_profile = unconfined" >> $HDIR/.local/share/lxc/$cname/config
> +run_cmd lxc-start -n $cname -d
> +run_cmd lxc-wait -n $cname -s RUNNING
> +pid=`run_cmd lxc-info -p -H -n $cname`
> +profile=`cat /proc/$pid/attr/current`
> +if [ "x$profile" != "xunconfined" ]; then
> +	echo "FAIL: unconfined container was in profile $profile"
> +	exit 1
> +fi
> +run_cmd lxc-stop -n $cname
> +
> +echo "masking $MOUNTSR"
> +mount --bind $dnam $MOUNTSR
> +
> +echo "test default confined container"
> +sed -i '/aa_profile/d' $HDIR/.local/share/lxc/$cname/config
> +run_cmd lxc-start -n $cname -d || true
> +sleep 3
> +pid=`run_cmd lxc-info -p -H -n $cname` || true
> +if [ -n "$pid" -a "$pid" != "-1" ]; then
> +	echo "FAIL: confined container started without mount restrictions"
> +	echo "pid was $pid"
> +	exit 1
> +fi
> +
> +echo "test regular unconfined container"
> +echo "lxc.aa_profile = unconfined" >> $HDIR/.local/share/lxc/$cname/config
> +run_cmd lxc-start -n $cname -d
> +run_cmd lxc-wait -n $cname -s RUNNING
> +pid=`run_cmd lxc-info -p -H -n $cname`
> +if [ "$pid" = "-1" ]; then
> +	echo "FAIL: unconfined container failed to start without mount restrictions"
> +	exit 1
> +fi
> +profile=`cat /proc/$pid/attr/current`
> +if [ "x$profile" != "xunconfined" ]; then
> +	echo "FAIL: confined container was in profile $profile"
> +	exit 1
> +fi
> +run_cmd lxc-stop -n $cname
> +
> +echo "testing override"
> +sed -i '/aa_profile/d' $HDIR/.local/share/lxc/$cname/config
> +echo "lxc.aa_allow_incomplete = 1" >> $HDIR/.local/share/lxc/$cname/config
> +run_cmd lxc-start -n $cname -d
> +run_cmd lxc-wait -n $cname -s RUNNING
> +pid=`run_cmd lxc-info -p -H -n $cname`
> +if [ "$pid" = "-1" ]; then
> +	echo "FAIL: excepted container failed to start without mount restrictions"
> +	exit 1
> +fi
> +profile=`cat /proc/$pid/attr/current`
> +if [ "x$profile" != "xlxc-container-default (enforce)" ]; then
> +	echo "FAIL: confined container was in profile $profile"
> +	exit 1
> +fi
> +run_cmd lxc-stop -n $cname
> +
> +DONE=1
> -- 
> 2.1.0
> 
> _______________________________________________
> lxc-devel mailing list
> lxc-devel at lists.linuxcontainers.org
> http://lists.linuxcontainers.org/listinfo/lxc-devel

-- 
Stéphane Graber
Ubuntu developer
http://www.ubuntu.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20140919/f53d6356/attachment.sig>


More information about the lxc-devel mailing list