diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index 16d65d2b29..7f2b755eab 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -733,6 +733,17 @@
above).
+
+
+
+ Takes a boolean argument. Specifies the value of the PR_SET_NO_NEW_PRIVS
+ flag for the container payload. Defaults to off. When turned on the payload code of the container cannot
+ acquire new privileges, i.e. the "setuid" file bit as well as file system capabilities will not have an effect
+ anymore. See prctl2 for details
+ about this flag.
+
+
diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml
index 3f8c325fd9..436f8cd0e9 100644
--- a/man/systemd.nspawn.xml
+++ b/man/systemd.nspawn.xml
@@ -222,6 +222,17 @@
all cases.
+
+ NoNewPrivileges=
+
+ Takes a boolean argument that controls the PR_SET_NO_NEW_PRIVS flag for
+ the container payload. This is equivalent to the
+ command line switch. See
+ systemd-nspawn1 for
+ details.
+
+
+
KillSignal=
diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf
index f7d2e1a515..a3759674ef 100644
--- a/src/nspawn/nspawn-gperf.gperf
+++ b/src/nspawn/nspawn-gperf.gperf
@@ -50,6 +50,7 @@ Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, of
Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit)
Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit)
Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname)
+Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges)
Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only)
Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode)
Files.Bind, config_parse_bind, 0, 0
diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c
index 3757380e28..11a2f41b96 100644
--- a/src/nspawn/nspawn-settings.c
+++ b/src/nspawn/nspawn-settings.c
@@ -36,6 +36,7 @@ int settings_load(FILE *f, const char *path, Settings **ret) {
s->userns_mode = _USER_NAMESPACE_MODE_INVALID;
s->uid_shift = UID_INVALID;
s->uid_range = UID_INVALID;
+ s->no_new_privileges = -1;
s->read_only = -1;
s->volatile_mode = _VOLATILE_MODE_INVALID;
diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h
index 3d3ee4c28c..130331ee18 100644
--- a/src/nspawn/nspawn-settings.h
+++ b/src/nspawn/nspawn-settings.h
@@ -50,9 +50,10 @@ typedef enum SettingsMask {
SETTING_PIVOT_ROOT = UINT64_C(1) << 15,
SETTING_SYSCALL_FILTER = UINT64_C(1) << 16,
SETTING_HOSTNAME = UINT64_C(1) << 17,
- SETTING_RLIMIT_FIRST = UINT64_C(1) << 18, /* we define one bit per resource limit here */
- SETTING_RLIMIT_LAST = UINT64_C(1) << (18 + _RLIMIT_MAX - 1),
- _SETTINGS_MASK_ALL = (UINT64_C(1) << (18 + _RLIMIT_MAX)) - 1
+ SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18,
+ SETTING_RLIMIT_FIRST = UINT64_C(1) << 19, /* we define one bit per resource limit here */
+ SETTING_RLIMIT_LAST = UINT64_C(1) << (19 + _RLIMIT_MAX - 1),
+ _SETTINGS_MASK_ALL = (UINT64_C(1) << (19 + _RLIMIT_MAX)) - 1
} SettingsMask;
typedef struct Settings {
@@ -76,6 +77,7 @@ typedef struct Settings {
char **syscall_blacklist;
struct rlimit *rlimit[_RLIMIT_MAX];
char *hostname;
+ int no_new_privileges;
/* [Image] */
int read_only;
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 7357576a2a..52c6ba1c0c 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -203,6 +203,7 @@ static size_t arg_root_hash_size = 0;
static char **arg_syscall_whitelist = NULL;
static char **arg_syscall_blacklist = NULL;
static struct rlimit *arg_rlimit[_RLIMIT_MAX] = {};
+static bool arg_no_new_privileges = false;
static void help(void) {
printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
@@ -446,6 +447,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SYSTEM_CALL_FILTER,
ARG_RLIMIT,
ARG_HOSTNAME,
+ ARG_NO_NEW_PRIVILEGES,
};
static const struct option options[] = {
@@ -462,6 +464,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "read-only", no_argument, NULL, ARG_READ_ONLY },
{ "capability", required_argument, NULL, ARG_CAPABILITY },
{ "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY },
+ { "no-new-privileges", required_argument, NULL, ARG_NO_NEW_PRIVILEGES },
{ "link-journal", required_argument, NULL, ARG_LINK_JOURNAL },
{ "bind", required_argument, NULL, ARG_BIND },
{ "bind-ro", required_argument, NULL, ARG_BIND_RO },
@@ -773,6 +776,15 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
+ case ARG_NO_NEW_PRIVILEGES:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --no-new-privileges= argument: %s", optarg);
+
+ arg_no_new_privileges = r;
+ arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES;
+ break;
+
case 'j':
arg_link_journal = LINK_GUEST;
arg_link_journal_try = true;
@@ -2463,6 +2475,10 @@ static int inner_child(
if (r < 0)
return r;
+ if (arg_no_new_privileges)
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
+ return log_error_errno(errno, "Failed to disable new privileges: %m");
+
/* LXC sets container=lxc, so follow the scheme here */
envp[n_env++] = strjoina("container=", arg_container_service_name);
@@ -3339,6 +3355,10 @@ static int load_settings(void) {
settings->hostname)
free_and_replace(arg_hostname, settings->hostname);
+ if ((arg_settings_mask & SETTING_NO_NEW_PRIVILEGES) == 0 &&
+ settings->no_new_privileges >= 0)
+ arg_no_new_privileges = settings->no_new_privileges;
+
return 0;
}
@@ -4229,7 +4249,7 @@ int main(int argc, char *argv[]) {
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1) >= 0);
- if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) {
+ if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) < 0) {
r = log_error_errno(errno, "Failed to become subreaper: %m");
goto finish;
}