Merge pull request #6763 from kinvolk/iaguis/no-new-privs

core: allow using seccomp without no_new_privs when unprivileged
This commit is contained in:
Luca Boccassi
2023-11-07 21:34:49 +00:00
committed by GitHub
9 changed files with 231 additions and 101 deletions
+12 -4
View File
@@ -367,16 +367,16 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) {
return 0;
}
int drop_capability(cap_value_t cv) {
static int change_capability(cap_value_t cv, cap_flag_value_t flag) {
_cleanup_cap_free_ cap_t tmp_cap = NULL;
tmp_cap = cap_get_proc();
if (!tmp_cap)
return -errno;
if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, CAP_CLEAR) < 0) ||
(cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, CAP_CLEAR) < 0) ||
(cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, CAP_CLEAR) < 0))
if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, flag) < 0) ||
(cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, flag) < 0) ||
(cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, flag) < 0))
return -errno;
if (cap_set_proc(tmp_cap) < 0)
@@ -385,6 +385,14 @@ int drop_capability(cap_value_t cv) {
return 0;
}
int drop_capability(cap_value_t cv) {
return change_capability(cv, CAP_CLEAR);
}
int keep_capability(cap_value_t cv) {
return change_capability(cv, CAP_SET);
}
bool ambient_capabilities_supported(void) {
static int cache = -1;
+1
View File
@@ -31,6 +31,7 @@ int capability_update_inherited_set(cap_t caps, uint64_t ambient_set);
int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities);
int drop_capability(cap_value_t cv);
int keep_capability(cap_value_t cv);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(cap_t, cap_free, NULL);
#define _cleanup_cap_free_ _cleanup_(cap_freep)
+135 -17
View File
@@ -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)) {
+12
View File
@@ -754,6 +754,18 @@ static void test_exec_systemcallfilter(Manager *m) {
test(m, "exec-systemcallfilter-with-errno-in-allow-list.service", errno_from_name("EILSEQ"), CLD_EXITED);
test(m, "exec-systemcallfilter-override-error-action.service", SIGSYS, CLD_KILLED);
test(m, "exec-systemcallfilter-override-error-action2.service", errno_from_name("EILSEQ"), CLD_EXITED);
test(m, "exec-systemcallfilter-nonewprivileges.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED);
test(m, "exec-systemcallfilter-nonewprivileges-protectclock.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED);
r = find_executable("capsh", NULL);
if (r < 0) {
log_notice_errno(r, "Skipping %s, could not find capsh binary: %m", __func__);
return;
}
test(m, "exec-systemcallfilter-nonewprivileges-bounding1.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED);
test(m, "exec-systemcallfilter-nonewprivileges-bounding2.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED);
#endif
}