core: allow using seccomp without no_new_privs when unprivileged
Until now, using any form of seccomp while being unprivileged (User=) resulted in systemd enabling no_new_privs. There's no need for doing this because: * We trust the filters we apply * If User= is set and a process wants to apply a new seccomp filter, it will need to set no_new_privs itself An example of application that might want seccomp + !no_new_privs is a program that wants to run as an unprivileged user but uses file capabilities to start a web server on a privileged port while benefitting from a restrictive seccomp profile. We now keep the privileges needed to do seccomp before calling enforce_user() and drop them after the seccomp filters are applied. If the syscall filter doesn't allow the needed syscalls to drop the privileges, we keep the previous behavior by enabling no_new_privs.
This commit is contained in:
+135
-17
@@ -1378,15 +1378,7 @@ static bool context_has_syscall_logs(const ExecContext *c) {
|
||||
!hashmap_isempty(c->syscall_log);
|
||||
}
|
||||
|
||||
static bool context_has_no_new_privileges(const ExecContext *c) {
|
||||
assert(c);
|
||||
|
||||
if (c->no_new_privileges)
|
||||
return true;
|
||||
|
||||
if (have_effective_cap(CAP_SYS_ADMIN) > 0) /* if we are privileged, we don't need NNP */
|
||||
return false;
|
||||
|
||||
static bool context_has_seccomp(const ExecContext *c) {
|
||||
/* We need NNP if we have any form of seccomp and are unprivileged */
|
||||
return c->lock_personality ||
|
||||
c->memory_deny_write_execute ||
|
||||
@@ -1405,8 +1397,49 @@ static bool context_has_no_new_privileges(const ExecContext *c) {
|
||||
context_has_syscall_logs(c);
|
||||
}
|
||||
|
||||
static bool context_has_no_new_privileges(const ExecContext *c) {
|
||||
assert(c);
|
||||
|
||||
if (c->no_new_privileges)
|
||||
return true;
|
||||
|
||||
if (have_effective_cap(CAP_SYS_ADMIN) > 0) /* if we are privileged, we don't need NNP */
|
||||
return false;
|
||||
|
||||
return context_has_seccomp(c);
|
||||
}
|
||||
|
||||
#if HAVE_SECCOMP
|
||||
|
||||
static bool seccomp_allows_drop_privileges(const ExecContext *c) {
|
||||
void *id, *val;
|
||||
bool has_capget = false, has_capset = false, has_prctl = false;
|
||||
|
||||
assert(c);
|
||||
|
||||
/* No syscall filter, we are allowed to drop privileges */
|
||||
if (hashmap_isempty(c->syscall_filter))
|
||||
return true;
|
||||
|
||||
HASHMAP_FOREACH_KEY(val, id, c->syscall_filter) {
|
||||
_cleanup_free_ char *name = NULL;
|
||||
|
||||
name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1);
|
||||
|
||||
if (streq(name, "capget"))
|
||||
has_capget = true;
|
||||
else if (streq(name, "capset"))
|
||||
has_capset = true;
|
||||
else if (streq(name, "prctl"))
|
||||
has_prctl = true;
|
||||
}
|
||||
|
||||
if (c->syscall_allow_list)
|
||||
return has_capget && has_capset && has_prctl;
|
||||
else
|
||||
return !(has_capget || has_capset || has_prctl);
|
||||
}
|
||||
|
||||
static bool skip_seccomp_unavailable(const ExecContext *c, const ExecParameters *p, const char* msg) {
|
||||
|
||||
if (is_seccomp_available())
|
||||
@@ -3911,6 +3944,7 @@ int exec_invoke(
|
||||
needs_setuid, /* Do we need to do the actual setresuid()/setresgid() calls? */
|
||||
needs_mount_namespace, /* Do we need to set up a mount namespace for this kernel? */
|
||||
needs_ambient_hack; /* Do we need to apply the ambient capabilities hack? */
|
||||
bool keep_seccomp_privileges = false;
|
||||
#if HAVE_SELINUX
|
||||
_cleanup_free_ char *mac_selinux_context_net = NULL;
|
||||
bool use_selinux = false;
|
||||
@@ -3920,6 +3954,9 @@ int exec_invoke(
|
||||
#endif
|
||||
#if HAVE_APPARMOR
|
||||
bool use_apparmor = false;
|
||||
#endif
|
||||
#if HAVE_SECCOMP
|
||||
uint64_t saved_bset = 0;
|
||||
#endif
|
||||
uid_t saved_uid = getuid();
|
||||
gid_t saved_gid = getgid();
|
||||
@@ -4817,6 +4854,28 @@ int exec_invoke(
|
||||
(UINT64_C(1) << CAP_SETUID) |
|
||||
(UINT64_C(1) << CAP_SETGID);
|
||||
|
||||
#if HAVE_SECCOMP
|
||||
/* If the service has any form of a seccomp filter and it allows dropping privileges, we'll
|
||||
* keep the needed privileges to apply it even if we're not root. */
|
||||
if (needs_setuid &&
|
||||
uid_is_valid(uid) &&
|
||||
context_has_seccomp(context) &&
|
||||
seccomp_allows_drop_privileges(context)) {
|
||||
keep_seccomp_privileges = true;
|
||||
|
||||
if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return log_exec_error_errno(context, params, errno, "Failed to enable keep capabilities flag: %m");
|
||||
}
|
||||
|
||||
/* Save the current bounding set so we can restore it after applying the seccomp
|
||||
* filter */
|
||||
saved_bset = bset;
|
||||
bset |= (UINT64_C(1) << CAP_SYS_ADMIN) |
|
||||
(UINT64_C(1) << CAP_SETPCAP);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!cap_test_all(bset)) {
|
||||
r = capability_bounding_set_drop(bset, /* right_now= */ false);
|
||||
if (r < 0) {
|
||||
@@ -4858,6 +4917,26 @@ int exec_invoke(
|
||||
return log_exec_error_errno(context, params, r, "Failed to change UID to " UID_FMT ": %m", uid);
|
||||
}
|
||||
|
||||
if (keep_seccomp_privileges) {
|
||||
r = drop_capability(CAP_SETUID);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return log_exec_error_errno(context, params, r, "Failed to drop CAP_SETUID: %m");
|
||||
}
|
||||
|
||||
r = keep_capability(CAP_SYS_ADMIN);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return log_exec_error_errno(context, params, r, "Failed to keep CAP_SYS_ADMIN: %m");
|
||||
}
|
||||
|
||||
r = keep_capability(CAP_SETPCAP);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return log_exec_error_errno(context, params, r, "Failed to keep CAP_SETPCAP: %m");
|
||||
}
|
||||
}
|
||||
|
||||
if (!needs_ambient_hack && capability_ambient_set != 0) {
|
||||
|
||||
/* Raise the ambient capabilities after user change. */
|
||||
@@ -5027,14 +5106,6 @@ int exec_invoke(
|
||||
*exit_status = EXIT_SECCOMP;
|
||||
return log_exec_error_errno(context, params, r, "Failed to apply system call log filters: %m");
|
||||
}
|
||||
|
||||
/* This really should remain the last step before the execve(), to make sure our own code is unaffected
|
||||
* by the filter as little as possible. */
|
||||
r = apply_syscall_filter(context, params, needs_ambient_hack);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_SECCOMP;
|
||||
return log_exec_error_errno(context, params, r, "Failed to apply system call filters: %m");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBBPF
|
||||
@@ -5045,6 +5116,53 @@ int exec_invoke(
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_SECCOMP
|
||||
/* This really should remain as close to the execve() as possible, to make sure our own code is unaffected
|
||||
* by the filter as little as possible. */
|
||||
r = apply_syscall_filter(context, params, needs_ambient_hack);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_SECCOMP;
|
||||
return log_exec_error_errno(context, params, r, "Failed to apply system call filters: %m");
|
||||
}
|
||||
|
||||
if (keep_seccomp_privileges) {
|
||||
/* Restore the capability bounding set with what's expected from the service + the
|
||||
* ambient capabilities hack */
|
||||
if (!cap_test_all(saved_bset)) {
|
||||
r = capability_bounding_set_drop(saved_bset, /* right_now= */ false);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_CAPABILITIES;
|
||||
return log_exec_error_errno(context, params, r, "Failed to drop bset capabilities: %m");
|
||||
}
|
||||
}
|
||||
|
||||
/* Only drop CAP_SYS_ADMIN if it's not in the bounding set, otherwise we'll break
|
||||
* applications that use it. */
|
||||
if (!FLAGS_SET(saved_bset, (UINT64_C(1) << CAP_SYS_ADMIN))) {
|
||||
r = drop_capability(CAP_SYS_ADMIN);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return log_exec_error_errno(context, params, r, "Failed to drop CAP_SYS_ADMIN: %m");
|
||||
}
|
||||
}
|
||||
|
||||
/* Only drop CAP_SETPCAP if it's not in the bounding set, otherwise we'll break
|
||||
* applications that use it. */
|
||||
if (!FLAGS_SET(saved_bset, (UINT64_C(1) << CAP_SETPCAP))) {
|
||||
r = drop_capability(CAP_SETPCAP);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return log_exec_error_errno(context, params, r, "Failed to drop CAP_SETPCAP: %m");
|
||||
}
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_KEEPCAPS, 0) < 0) {
|
||||
*exit_status = EXIT_USER;
|
||||
return log_exec_error_errno(context, params, errno, "Failed to drop keep capabilities flag: %m");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
if (!strv_isempty(context->unset_environment)) {
|
||||
|
||||
Reference in New Issue
Block a user