From 5a46f2831ee8444c6146345dd0e0ec2a83e4e761 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Sep 2016 01:30:59 +0200 Subject: [PATCH 1/8] conf, confile: add option for PR_SET_NO_NEW_PRIVS Signed-off-by: Christian Brauner --- src/lxc/conf.h | 3 +++ src/lxc/confile.c | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/lxc/conf.h b/src/lxc/conf.h index 69a72ea50..e48466730 100644 --- a/src/lxc/conf.h +++ b/src/lxc/conf.h @@ -382,6 +382,9 @@ struct lxc_conf { /* The facility to pass to syslog. Let's users establish as what type of * program liblxc is supposed to write to the syslog. */ char *syslog; + + /* Whether PR_SET_NO_NEW_PRIVS will be set for the container. */ + bool no_new_privs; }; #ifdef HAVE_TLS diff --git a/src/lxc/confile.c b/src/lxc/confile.c index 9ad05e588..8f370f6cf 100644 --- a/src/lxc/confile.c +++ b/src/lxc/confile.c @@ -114,6 +114,7 @@ static int config_init_cmd(const char *, const char *, struct lxc_conf *); static int config_init_uid(const char *, const char *, struct lxc_conf *); static int config_init_gid(const char *, const char *, struct lxc_conf *); static int config_ephemeral(const char *, const char *, struct lxc_conf *); +static int config_no_new_privs(const char *, const char *, struct lxc_conf *); static struct lxc_config_t config[] = { @@ -187,6 +188,7 @@ static struct lxc_config_t config[] = { { "lxc.init_gid", config_init_gid }, { "lxc.ephemeral", config_ephemeral }, { "lxc.syslog", config_syslog }, + { "lxc.no_new_privs", config_no_new_privs }, }; struct signame { @@ -2562,6 +2564,8 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv, return lxc_get_conf_int(c, retv, inlen, c->ephemeral); else if (strcmp(key, "lxc.syslog") == 0) v = c->syslog; + else if (strcmp(key, "lxc.no_new_privs") == 0) + return lxc_get_conf_int(c, retv, inlen, c->no_new_privs); else return -1; if (!v) @@ -2954,3 +2958,17 @@ static int config_syslog(const char *key, const char *value, lxc_log_syslog(facility); return config_string_item(&lxc_conf->syslog, value); } + +static int config_no_new_privs(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + int v = atoi(value); + + if (v != 0 && v != 1) { + ERROR("Wrong value for lxc.no_new_privs. Can only be set to 0 or 1"); + return -1; + } + lxc_conf->no_new_privs = v ? true : false; + + return 0; +} From 029cdff5822b155245df6355e1a774ceb4f415f7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Sep 2016 01:40:39 +0200 Subject: [PATCH 2/8] start: set PR_SET_NO_NEW_PRIVS when requested Set no_new_privs after setting the lsm label. If we do set it before we aren't allowed to change the label anymore. Signed-off-by: Christian Brauner --- src/lxc/start.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lxc/start.c b/src/lxc/start.c index 2411626de..bcc2e5ee9 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -850,6 +850,16 @@ static int do_start(void *data) if (lsm_process_label_set(NULL, handler->conf, 1, 1) < 0) goto out_warn_father; + /* Set PR_SET_NO_NEW_PRIVS after we changed the lsm label. If we do it + * before we aren't allowed anymore. */ + if (handler->conf->no_new_privs) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { + SYSERROR("Could not set PR_SET_NO_NEW_PRIVS to block execve() gainable privileges."); + goto out_warn_father; + } + DEBUG("Set PR_SET_NO_NEW_PRIVS to block execve() gainable privileges."); + } + /* Some init's such as busybox will set sane tty settings on stdin, * stdout, stderr which it thinks is the console. We already set them * the way we wanted on the real terminal, and we want init to do its From 1325da7eae056474fcb0e7362927d53e29e4ca2f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Sep 2016 18:17:11 +0200 Subject: [PATCH 3/8] attach_options: add LXC_ATTACH_NO_NEW_PRIVS Add a flag for PR_SET_NO_NEW_PRIVS. It is off by default. Signed-off-by: Christian Brauner --- src/lxc/attach_options.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lxc/attach_options.h b/src/lxc/attach_options.h index 3c54e7ca6..1df69924c 100644 --- a/src/lxc/attach_options.h +++ b/src/lxc/attach_options.h @@ -49,6 +49,8 @@ enum { /* the following are off by default */ LXC_ATTACH_REMOUNT_PROC_SYS = 0x00010000, //!< Remount /proc filesystem LXC_ATTACH_LSM_NOW = 0x00020000, //!< FIXME: unknown + /* Set PR_SET_NO_NEW_PRIVS to block execve() gainable privileges. */ + LXC_ATTACH_NO_NEW_PRIVS = 0x00040000, //!< PR_SET_NO_NEW_PRIVS /* we have 16 bits for things that are on by default * and 16 bits that are off by default, that should From ff07d7bb5a3e056eb51e5fe259c79d113435eca5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Sep 2016 18:39:11 +0200 Subject: [PATCH 4/8] attach: call lxc_container_new() earlier We will reuse the newly initialized container for PR_SET_NO_NEW_PRIVS. Signed-off-by: Christian Brauner --- src/lxc/attach.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lxc/attach.c b/src/lxc/attach.c index 0d9e3d047..813d04984 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -657,8 +657,8 @@ static int attach_child_main(void* data); /* define default options if no options are supplied by the user */ static lxc_attach_options_t attach_static_default_options = LXC_ATTACH_OPTIONS_DEFAULT; -static bool fetch_seccomp(const char *name, const char *lxcpath, - struct lxc_proc_context_info *i, lxc_attach_options_t *options) +static bool fetch_seccomp(struct lxc_proc_context_info *i, + lxc_attach_options_t *options) { struct lxc_container *c; char *path; @@ -666,10 +666,7 @@ static bool fetch_seccomp(const char *name, const char *lxcpath, if (!(options->namespaces & CLONE_NEWNS) || !(options->attach_flags & LXC_ATTACH_LSM)) return true; - c = lxc_container_new(name, lxcpath); - if (!c) - return false; - i->container = c; + c = i->container; /* Initialize an empty lxc_conf */ if (!c->set_config_item(c, "lxc.seccomp", "")) { @@ -744,7 +741,11 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun } init_ctx->personality = personality; - if (!fetch_seccomp(name, lxcpath, init_ctx, options)) + init_ctx->container = lxc_container_new(name, lxcpath); + if (!init_ctx->container) + return -1; + + if (!fetch_seccomp(init_ctx, options)) WARN("Failed to get seccomp policy"); cwd = getcwd(NULL, 0); From 2e812c16a502b03abe79ee00025de50d1928ad5e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 2 Sep 2016 18:56:48 +0200 Subject: [PATCH 5/8] attach: use PR_SET_NO_NEW_PRIVS - When we detect that the container, we want to attach to, has been stared with PR_SET_NO_NEW_PRIVS we attach with PR_SET_NO_NEW_PRIVS as well. (We might relax this restriction later but let's be strict for now.) - When LXC_ATTACH_NO_NEW_PRIVS is set in the flags passed to lxc_attach()/attach_child_main() then we set PR_SET_NO_NEW_PRIVS irrespective of whether the container was started with PR_SET_NO_NEW_PRIVS or not. - Set no_new_privs before lsm and seccomp. We probably don't want attach() to be able to change the lsm or seccomp policy if the container was started with PR_SET_NO_NEW_PRIVS enabled. Signed-off-by: Christian Brauner --- src/lxc/attach.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/lxc/attach.c b/src/lxc/attach.c index 813d04984..ac39fa223 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -668,7 +668,7 @@ static bool fetch_seccomp(struct lxc_proc_context_info *i, c = i->container; - /* Initialize an empty lxc_conf */ + /* Remove current setting. */ if (!c->set_config_item(c, "lxc.seccomp", "")) { return false; } @@ -692,6 +692,37 @@ static bool fetch_seccomp(struct lxc_proc_context_info *i, return false; } + INFO("Retrieved seccomp policy."); + return true; +} + +static bool no_new_privs(struct lxc_proc_context_info *ctx, + lxc_attach_options_t *options) +{ + struct lxc_container *c; + char *val; + + c = ctx->container; + + /* Remove current setting. */ + if (!c->set_config_item(c, "lxc.no_new_privs", "")) { + return false; + } + + /* Retrieve currently active setting. */ + val = c->get_running_config_item(c, "lxc.no_new_privs"); + if (!val) { + INFO("Failed to get running config item for lxc.no_new_privs."); + return false; + } + + /* Set currently active setting. */ + if (!c->set_config_item(c, "lxc.no_new_privs", val)) { + free(val); + return false; + } + free(val); + return true; } @@ -748,6 +779,9 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun if (!fetch_seccomp(init_ctx, options)) WARN("Failed to get seccomp policy"); + if (!no_new_privs(init_ctx, options)) + WARN("Could not determine whether PR_SET_NO_NEW_PRIVS is set."); + cwd = getcwd(NULL, 0); /* determine which namespaces the container was created with @@ -1147,6 +1181,19 @@ static int attach_child_main(void* data) shutdown(ipc_socket, SHUT_RDWR); close(ipc_socket); + if ((init_ctx->container && init_ctx->container->lxc_conf && + init_ctx->container->lxc_conf->no_new_privs) || + (options->attach_flags & LXC_ATTACH_NO_NEW_PRIVS)) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { + SYSERROR("PR_SET_NO_NEW_PRIVS could not be set. " + "Process can use execve() gainable " + "privileges."); + rexit(-1); + } + INFO("PR_SET_NO_NEW_PRIVS is set. Process cannot use execve() " + "gainable privileges."); + } + /* set new apparmor profile/selinux context */ if ((options->namespaces & CLONE_NEWNS) && (options->attach_flags & LXC_ATTACH_LSM) && init_ctx->lsm_label) { int on_exec; @@ -1162,7 +1209,6 @@ static int attach_child_main(void* data) ERROR("Loading seccomp policy"); rexit(-1); } - lxc_proc_put_context_info(init_ctx); /* The following is done after the communication socket is From 222ddc91a818cba50fe23c5166f7662d3da84622 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 3 Sep 2016 08:00:20 +0200 Subject: [PATCH 6/8] doc: add lxc.no_new_privs to lxc.container.conf Signed-off-by: Christian Brauner --- doc/lxc.container.conf.sgml.in | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/lxc.container.conf.sgml.in b/doc/lxc.container.conf.sgml.in index 1b740a57e..fcccd8ba9 100644 --- a/doc/lxc.container.conf.sgml.in +++ b/doc/lxc.container.conf.sgml.in @@ -1310,6 +1310,34 @@ mknod errno 0 + + PR_SET_NO_NEW_PRIVS + + With PR_SET_NO_NEW_PRIVS active execve() promises not to grant + privileges to do anything that could not have been done without + the execve() call (for example, rendering the set-user-ID and + set-group-ID mode bits, and file capabilities non-functional). + Once set, this bit cannot be unset. The setting of this bit is + inherited by children created by fork() and clone(), and preserved + across execve(). + Note that PR_SET_NO_NEW_PRIVS is applied after the container has + changed into its intended AppArmor profile or SElinux context. + + + + + + + + + Specify whether the PR_SET_NO_NEW_PRIVS flag should be set for the + container. Set to 1 to activate. + + + + + + UID mappings From 955e2a0237c7d914fc7561018ebff4970a8b12df Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 3 Sep 2016 15:19:27 +0200 Subject: [PATCH 7/8] attach, start: declare PR_{S,G}PR_GET_NO_NEW_PRIVS Signed-off-by: Christian Brauner --- configure.ac | 4 ++++ src/lxc/attach.c | 10 +++++++++- src/lxc/start.c | 10 +++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 39e313add..dd2ad681c 100644 --- a/configure.ac +++ b/configure.ac @@ -593,6 +593,10 @@ AM_CONDITIONAL([IS_BIONIC], [test "x$is_bionic" = "xyes"]) # Some systems lack PR_CAPBSET_DROP definition => HAVE_DECL_PR_CAPBSET_DROP AC_CHECK_DECLS([PR_CAPBSET_DROP], [], [], [#include ]) +# Some systems lack PR_{G,S}ET_NO_NEW_PRIVS definition => HAVE_DECL_PR_{G,S}ET_NO_NEW_PRIVS +AC_CHECK_DECLS([PR_SET_NO_NEW_PRIVS], [], [], [#include ]) +AC_CHECK_DECLS([PR_GET_NO_NEW_PRIVS], [], [], [#include ]) + # Check for some headers AC_CHECK_HEADERS([sys/signalfd.h pty.h ifaddrs.h sys/capability.h sys/personality.h utmpx.h sys/timerfd.h]) diff --git a/src/lxc/attach.c b/src/lxc/attach.c index ac39fa223..c74141050 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -39,10 +39,18 @@ #include #include -#if !HAVE_DECL_PR_CAPBSET_DROP +#ifndef HAVE_DECL_PR_CAPBSET_DROP #define PR_CAPBSET_DROP 24 #endif +#ifndef HAVE_DECL_PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#ifndef HAVE_DECL_PR_GET_NO_NEW_PRIVS +#define PR_GET_NO_NEW_PRIVS 39 +#endif + #include "namespace.h" #include "log.h" #include "attach.h" diff --git a/src/lxc/start.c b/src/lxc/start.c index bcc2e5ee9..ecc7b08f6 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -50,10 +50,18 @@ #include #endif -#if !HAVE_DECL_PR_CAPBSET_DROP +#ifndef HAVE_DECL_PR_CAPBSET_DROP #define PR_CAPBSET_DROP 24 #endif +#ifndef HAVE_DECL_PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#ifndef HAVE_DECL_PR_GET_NO_NEW_PRIVS +#define PR_GET_NO_NEW_PRIVS 39 +#endif + #include "af_unix.h" #include "bdev.h" #include "caps.h" From bca94305afabaa7c115d7732844230435b766169 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 3 Sep 2016 13:59:47 +0200 Subject: [PATCH 8/8] tests: add test for PR_SET_NO_NEW_PRIVS Signed-off-by: Christian Brauner --- src/tests/Makefile.am | 8 ++- src/tests/lxc-test-no-new-privs | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100755 src/tests/lxc-test-no-new-privs diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index cffc74204..2e7dd5602 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -53,8 +53,11 @@ bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \ lxc-test-reboot lxc-test-list lxc-test-attach lxc-test-device-add-remove \ lxc-test-apparmor lxc-test-utils -bin_SCRIPTS = lxc-test-automount lxc-test-autostart lxc-test-cloneconfig \ - lxc-test-createconfig +bin_SCRIPTS = lxc-test-automount \ + lxc-test-autostart \ + lxc-test-cloneconfig \ + lxc-test-createconfig \ + lxc-test-no-new-privs if DISTRO_UBUNTU bin_SCRIPTS += \ @@ -91,6 +94,7 @@ EXTRA_DIST = \ lxc-test-checkpoint-restore \ lxc-test-cloneconfig \ lxc-test-createconfig \ + lxc-test-no-new-privs \ lxc-test-snapdeps \ lxc-test-symlink \ lxc-test-ubuntu \ diff --git a/src/tests/lxc-test-no-new-privs b/src/tests/lxc-test-no-new-privs new file mode 100755 index 000000000..d10e0449a --- /dev/null +++ b/src/tests/lxc-test-no-new-privs @@ -0,0 +1,104 @@ +#!/bin/bash + +# lxc: linux Container library + +# Authors: +# Christian Brauner +# +# This is a test script for PR_SET_NO_NEW_PRIVS + +# This library 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 + +set -eux + +DONE=0 +cleanup() { + cd / + lxc-destroy -n c1 -f || true + if [ $DONE -eq 0 ]; then + echo "FAIL" + exit 1 + fi + echo "PASS" +} + +trap cleanup EXIT SIGHUP SIGINT SIGTERM + +mkdir -p /etc/lxc/ +cat > /etc/lxc/default.conf << EOF +lxc.network.type = veth +lxc.network.link = lxcbr0 +EOF + +ARCH=i386 +if type dpkg >/dev/null 2>&1; then + ARCH=$(dpkg --print-architecture) +fi + +lxc-create -t download -n c1 -- -d ubuntu -r xenial -a $ARCH +echo "lxc.no_new_privs = 1" >> /var/lib/lxc/c1/config + +lxc-start -n c1 +p1=$(lxc-info -n c1 -p -H) +[ "$p1" != "-1" ] || { echo "Failed to start container c1 (run $count)"; false; } +sleep 5s +lxc-attach -n c1 --clear-env -- apt update -y +lxc-attach -n c1 --clear-env -- apt install -y gcc make + +# Here documents don't seem to like sudo -i. +lxc-attach -n c1 --clear-env -- /bin/bash -c "cat < /nnptest.c +#include +#include +#include + +int main(int argc, char *argv[]) +{ + printf(\"%d\n\", geteuid()); +} +EOF" +lxc-attach -n c1 --clear-env -- cat /nnptest.c +lxc-attach -n c1 --clear-env -- make -C / nnptest +lxc-attach -n c1 --clear-env -- chmod u+s /nnptest + +# Check that lxc-attach obeys PR_SET_NO_NEW_PRIVS when it is set. +NNP_EUID=$(lxc-attach -n c1 --clear-env -- sudo -u ubuntu /nnptest) +if [ "$NNP_EUID" -ne 1000 ]; then + exit 1 +fi +lxc-stop -n c1 -k + +# Check that lxc-attach obeys PR_SET_NO_NEW_PRIVS when it is not set. +sed -i 's/lxc.no_new_privs = 1/lxc.no_new_privs = 0/' /var/lib/lxc/c1/config +lxc-start -n c1 +NNP_EUID=$(lxc-attach -n c1 --clear-env -- sudo -u ubuntu /nnptest) +if [ "$NNP_EUID" -ne 0 ]; then + exit 1 +fi +lxc-stop -n c1 -k + +# Check that lxc-execute and lxc-start obey PR_SET_NO_NEW_PRIVS when it is set. +NNP_EUID=$(lxc-execute -n c1 -- sudo -u ubuntu /nnptest) +if [ "$NNP_EUID" -ne 0 ]; then + exit 1 +fi + +# Check that lxc-execute and lxc-start obey PR_SET_NO_NEW_PRIVS when it is not set. +sed -i 's/lxc.no_new_privs = 0/lxc.no_new_privs = 1/' /var/lib/lxc/c1/config +NNP_EUID=$(lxc-execute -n c1 -- sudo -u ubuntu /nnptest) +if [ "$NNP_EUID" -ne 1000 ]; then + exit 1 +fi + +DONE=1