[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