[lxc-devel] [lxc/master] implement PR_SET_NO_NEW_PRIVS in liblxc

brauner on Github lxc-bot at linuxcontainers.org
Sat Sep 3 12:19:02 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 770 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160903/904ae97d/attachment.bin>
-------------- next part --------------
From 59c70350925998f0015df0244d9f3de50d7f6d8a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at mailbox.org>
Date: Fri, 2 Sep 2016 01:30:59 +0200
Subject: [PATCH 1/7] conf, confile: add option for PR_SET_NO_NEW_PRIVS

Signed-off-by: Christian Brauner <christian.brauner at mailbox.org>
---
 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 2593ce5..286879d 100644
--- a/src/lxc/conf.h
+++ b/src/lxc/conf.h
@@ -378,6 +378,9 @@ struct lxc_conf {
 
 	/* indicator if the container will be destroyed on shutdown */
 	int ephemeral;
+
+	/* 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 fac919d..9064d6e 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 {
@@ -2577,6 +2579,8 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv,
 		return lxc_get_conf_int(c, retv, inlen, c->init_gid);
 	else if (strcmp(key, "lxc.ephemeral") == 0)
 		return lxc_get_conf_int(c, retv, inlen, c->ephemeral);
+	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)
@@ -2973,3 +2977,17 @@ static int config_syslog(const char *key, const char *value,
 	ERROR("Wrong value for lxc.syslog");
 	return -1;
 }
+
+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 703c9b6ccc5342e93e26d1b20970386d6737b57f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at mailbox.org>
Date: Fri, 2 Sep 2016 01:40:39 +0200
Subject: [PATCH 2/7] start: set PR_SET_NO_NEW_PRIVS when requested

Signed-off-by: Christian Brauner <christian.brauner at mailbox.org>
---
 src/lxc/start.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/lxc/start.c b/src/lxc/start.c
index 2411626..4605d45 100644
--- a/src/lxc/start.c
+++ b/src/lxc/start.c
@@ -923,6 +923,14 @@ static int do_start(void *data)
 
 	setsid();
 
+	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.");
+	}
+
 	/* after this call, we are in error because this
 	 * ops should not return as it execs */
 	handler->ops->start(handler, handler->data);

From 538ad7ad69d3a2fc8f3e342999eeb8c854373724 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at mailbox.org>
Date: Fri, 2 Sep 2016 18:17:11 +0200
Subject: [PATCH 3/7] 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 <christian.brauner at mailbox.org>
---
 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 3c54e7c..1df6992 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 28406b9292163925faa13b04ada9191aae818245 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at mailbox.org>
Date: Fri, 2 Sep 2016 18:39:11 +0200
Subject: [PATCH 4/7] attach: call lxc_container_new() earlier

We will reuse the newly initialized container for PR_SET_NO_NEW_PRIVS.

Signed-off-by: Christian Brauner <christian.brauner at mailbox.org>
---
 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 0d9e3d0..813d049 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 ddfb8a09ace17a13a08dfcb7698dafffd10e9623 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at mailbox.org>
Date: Fri, 2 Sep 2016 18:56:48 +0200
Subject: [PATCH 5/7] 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.

Signed-off-by: Christian Brauner <christian.brauner at mailbox.org>
---
 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 813d049..171d5a9 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
@@ -1162,7 +1196,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
@@ -1206,6 +1239,19 @@ static int attach_child_main(void* data)
 	/* we don't need proc anymore */
 	close(procfd);
 
+	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.");
+	}
+
 	/* we're done, so we can now do whatever the user intended us to do */
 	rexit(payload->exec_function(payload->exec_payload));
 }

From 5abe8cd9f9478c96f540cae43a9638afe31abe85 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at mailbox.org>
Date: Sat, 3 Sep 2016 08:00:20 +0200
Subject: [PATCH 6/7] doc: add lxc.no_new_privs to lxc.container.conf

Signed-off-by: Christian Brauner <christian.brauner at mailbox.org>
---
 doc/lxc.container.conf.sgml.in | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/doc/lxc.container.conf.sgml.in b/doc/lxc.container.conf.sgml.in
index 1b740a5..e666ae1 100644
--- a/doc/lxc.container.conf.sgml.in
+++ b/doc/lxc.container.conf.sgml.in
@@ -1311,6 +1311,32 @@ mknod errno 0
     </refsect2>
 
     <refsect2>
+      <title>PR_SET_NO_NEW_PRIVS</title>
+      <para>
+              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-func‐
+              tional). Once set, this bit cannot be unset. The setting of this
+              bit is inher ited  by  children created by  fork()  and  clone(),
+              and  preserved  across execve().
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term>
+            <option>lxc.no_new_privs</option>
+          </term>
+          <listitem>
+            <para>
+              Specify whether the PR_SET_NO_NEW_PRIVS flag should be set for the
+              container. Set to 1 to activate.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2>
       <title>UID mappings</title>
       <para>
         A container can be started in a private user namespace with

From 66180d2062ad3b6c4a32a17acde9da18d6c80c09 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at mailbox.org>
Date: Sat, 3 Sep 2016 13:59:47 +0200
Subject: [PATCH 7/7] tests: add test for PR_SET_NO_NEW_PRIVS

Signed-off-by: Christian Brauner <christian.brauner at mailbox.org>
---
 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 cffc742..2e7dd56 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 0000000..251b19a
--- /dev/null
+++ b/src/tests/lxc-test-no-new-privs
@@ -0,0 +1,104 @@
+#!/bin/bash
+
+# lxc: linux Container library
+
+# Authors:
+# Christian Brauner <christian.brauner at mailbox.org>
+#
+# This is a test script for unprivileged containers
+
+# 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 <<EOF > /nnptest.c
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+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


More information about the lxc-devel mailing list