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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user