Merge pull request #1166 from brauner/2016-09-02/no_new_privileges

implement PR_SET_NO_NEW_PRIVS in liblxc
This commit is contained in:
Serge Hallyn 2016-09-15 20:35:21 -05:00 committed by GitHub
commit a307c27146
9 changed files with 249 additions and 13 deletions

View File

@ -598,6 +598,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 <sys/prctl.h>])
# 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 <sys/prctl.h>])
AC_CHECK_DECLS([PR_GET_NO_NEW_PRIVS], [], [], [#include <sys/prctl.h>])
# 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])

View File

@ -1310,6 +1310,34 @@ mknod errno 0
</variablelist>
</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-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.
</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>

View File

@ -39,10 +39,18 @@
#include <linux/unistd.h>
#include <pwd.h>
#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"
@ -657,8 +665,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,12 +674,9 @@ 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 */
/* Remove current setting. */
if (!c->set_config_item(c, "lxc.seccomp", "")) {
return false;
}
@ -695,6 +700,37 @@ static bool fetch_seccomp(const char *name, const char *lxcpath,
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;
}
@ -744,9 +780,16 @@ 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");
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
@ -1146,6 +1189,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;
@ -1161,7 +1217,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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -50,10 +50,18 @@
#include <sys/capability.h>
#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"
@ -850,6 +858,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

View File

@ -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 \

104
src/tests/lxc-test-no-new-privs Executable file
View File

@ -0,0 +1,104 @@
#!/bin/bash
# lxc: linux Container library
# Authors:
# Christian Brauner <christian.brauner@mailbox.org>
#
# 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 <<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