Merge pull request #2265 from ipuustin/ambient

capabilities: added support for ambient capabilities.
This commit is contained in:
Lennart Poettering 2016-01-12 15:16:24 +01:00
commit 1f52a79d4e
19 changed files with 372 additions and 80 deletions

View File

@ -806,6 +806,35 @@
settings.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>AmbientCapabilities=</varname></term>
<listitem><para>Controls which capabilities to include in the
ambient capability set for the executed process. Takes a
whitespace-separated list of capability names as read by
<citerefentry project='mankier'><refentrytitle>cap_from_name</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
e.g. <constant>CAP_SYS_ADMIN</constant>,
<constant>CAP_DAC_OVERRIDE</constant>,
<constant>CAP_SYS_PTRACE</constant>. This option may appear more than
once in which case the ambient capability sets are merged.
If the list of capabilities is prefixed with <literal>~</literal>, all
but the listed capabilities will be included, the effect of the
assignment inverted. If the empty string is
assigned to this option, the ambient capability set is reset to
the empty capability set, and all prior settings have no effect.
If set to <literal>~</literal> (without any further argument), the
ambient capability set is reset to the full set of available
capabilities, also undoing any previous settings. Note that adding
capabilities to ambient capability set adds them to the process's
inherited capability set.
</para><para>
Ambient capability sets are useful if you want to execute a process
as a non-privileged user but still want to give it some capabilities.
Note that in this case option <constant>keep-caps</constant> is
automatically added to <varname>SecureBits=</varname> to retain the
capabilities over the user change.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SecureBits=</varname></term>
<listitem><para>Controls the secure bits set for the executed

View File

@ -96,7 +96,62 @@ unsigned long cap_last_cap(void) {
return p;
}
int capability_bounding_set_drop(uint64_t drop, bool right_now) {
int capability_update_inherited_set(cap_t caps, uint64_t set) {
unsigned long i;
/* Add capabilities in the set to the inherited caps. Do not apply
* them yet. */
for (i = 0; i < cap_last_cap(); i++) {
if (set & (UINT64_C(1) << i)) {
cap_value_t v;
v = (cap_value_t) i;
/* Make the capability inheritable. */
if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0)
return -errno;
}
}
return 0;
}
int capability_ambient_set_apply(uint64_t set, bool also_inherit) {
unsigned long i;
_cleanup_cap_free_ cap_t caps = NULL;
/* Add the capabilities to the ambient set. */
if (also_inherit) {
int r;
caps = cap_get_proc();
if (!caps)
return -errno;
r = capability_update_inherited_set(caps, set);
if (r < 0)
return -errno;
if (cap_set_proc(caps) < 0)
return -errno;
}
for (i = 0; i < cap_last_cap(); i++) {
if (set & (UINT64_C(1) << i)) {
/* Add the capability to the ambient set. */
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0)
return -errno;
}
}
return 0;
}
int capability_bounding_set_drop(uint64_t keep, bool right_now) {
_cleanup_cap_free_ cap_t after_cap = NULL;
cap_flag_value_t fv;
unsigned long i;
@ -137,7 +192,7 @@ int capability_bounding_set_drop(uint64_t drop, bool right_now) {
for (i = 0; i <= cap_last_cap(); i++) {
if (drop & ((uint64_t) 1ULL << (uint64_t) i)) {
if (!(keep & (UINT64_C(1) << i))) {
cap_value_t v;
/* Drop it from the bounding set */
@ -176,7 +231,7 @@ finish:
return r;
}
static int drop_from_file(const char *fn, uint64_t drop) {
static int drop_from_file(const char *fn, uint64_t keep) {
int r, k;
uint32_t hi, lo;
uint64_t current, after;
@ -196,7 +251,7 @@ static int drop_from_file(const char *fn, uint64_t drop) {
return -EIO;
current = (uint64_t) lo | ((uint64_t) hi << 32ULL);
after = current & ~drop;
after = current & keep;
if (current == after)
return 0;
@ -213,14 +268,14 @@ static int drop_from_file(const char *fn, uint64_t drop) {
return r;
}
int capability_bounding_set_drop_usermode(uint64_t drop) {
int capability_bounding_set_drop_usermode(uint64_t keep) {
int r;
r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop);
r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep);
if (r < 0)
return r;
r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop);
r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep);
if (r < 0)
return r;
@ -257,7 +312,7 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) {
return log_error_errno(errno, "Failed to disable keep capabilities flag: %m");
/* Drop all caps from the bounding set, except the ones we want */
r = capability_bounding_set_drop(~keep_capabilities, true);
r = capability_bounding_set_drop(keep_capabilities, true);
if (r < 0)
return log_error_errno(r, "Failed to drop capabilities: %m");

View File

@ -29,10 +29,15 @@
#include "macro.h"
#include "util.h"
#define CAP_ALL (uint64_t) -1
unsigned long cap_last_cap(void);
int have_effective_cap(int value);
int capability_bounding_set_drop(uint64_t drop, bool right_now);
int capability_bounding_set_drop_usermode(uint64_t drop);
int capability_bounding_set_drop(uint64_t keep, bool right_now);
int capability_bounding_set_drop_usermode(uint64_t keep);
int capability_ambient_set_apply(uint64_t set, bool also_inherit);
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);
@ -46,3 +51,9 @@ static inline void cap_free_charpp(char **p) {
cap_free(*p);
}
#define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp)
static inline bool cap_test_all(uint64_t caps) {
uint64_t m;
m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1;
return (caps & m) == m;
}

View File

@ -1129,3 +1129,19 @@ static inline key_serial_t request_key(const char *type, const char *description
#ifndef KEY_SPEC_USER_KEYRING
#define KEY_SPEC_USER_KEYRING -4
#endif
#ifndef PR_CAP_AMBIENT
#define PR_CAP_AMBIENT 47
#endif
#ifndef PR_CAP_AMBIENT_IS_SET
#define PR_CAP_AMBIENT_IS_SET 1
#endif
#ifndef PR_CAP_AMBIENT_RAISE
#define PR_CAP_AMBIENT_RAISE 2
#endif
#ifndef PR_CAP_AMBIENT_CLEAR_ALL
#define PR_CAP_AMBIENT_CLEAR_ALL 4
#endif

View File

@ -293,9 +293,25 @@ static int property_get_capability_bounding_set(
assert(reply);
assert(c);
/* We store this negated internally, to match the kernel, but
* we expose it normalized. */
return sd_bus_message_append(reply, "t", ~c->capability_bounding_set_drop);
return sd_bus_message_append(reply, "t", c->capability_bounding_set);
}
static int property_get_ambient_capabilities(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ExecContext *c = userdata;
assert(bus);
assert(reply);
assert(c);
return sd_bus_message_append(reply, "t", c->capability_ambient_set);
}
static int property_get_capabilities(
@ -689,6 +705,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),

View File

@ -737,12 +737,7 @@ static int enforce_user(const ExecContext *context, uid_t uid) {
/* Sets (but doesn't lookup) the uid and make sure we keep the
* capabilities while doing so. */
if (context->capabilities) {
_cleanup_cap_free_ cap_t d = NULL;
static const cap_value_t bits[] = {
CAP_SETUID, /* Necessary so that we can run setresuid() below */
CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */
};
if (context->capabilities || context->capability_ambient_set != 0) {
/* First step: If we need to keep capabilities but
* drop privileges we need to make sure we keep our
@ -758,16 +753,24 @@ static int enforce_user(const ExecContext *context, uid_t uid) {
/* Second step: set the capabilities. This will reduce
* the capabilities to the minimum we need. */
d = cap_dup(context->capabilities);
if (!d)
return -errno;
if (context->capabilities) {
_cleanup_cap_free_ cap_t d = NULL;
static const cap_value_t bits[] = {
CAP_SETUID, /* Necessary so that we can run setresuid() below */
CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */
};
if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0)
return -errno;
d = cap_dup(context->capabilities);
if (!d)
return -errno;
if (cap_set_proc(d) < 0)
return -errno;
if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0)
return -errno;
if (cap_set_proc(d) < 0)
return -errno;
}
}
/* Third step: actually set the uids */
@ -1856,6 +1859,8 @@ static int exec_child(
if (params->apply_permissions) {
int secure_bits = context->secure_bits;
for (i = 0; i < _RLIMIT_MAX; i++) {
if (!context->rlimit[i])
continue;
@ -1866,28 +1871,71 @@ static int exec_child(
}
}
if (context->capability_bounding_set_drop) {
r = capability_bounding_set_drop(context->capability_bounding_set_drop, false);
if (!cap_test_all(context->capability_bounding_set)) {
r = capability_bounding_set_drop(context->capability_bounding_set, false);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
return r;
}
}
/* This is done before enforce_user, but ambient set
* does not survive over setresuid() if keep_caps is not set. */
if (context->capability_ambient_set != 0) {
r = capability_ambient_set_apply(context->capability_ambient_set, true);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
return r;
}
if (context->capabilities) {
/* The capabilities in ambient set need to be also in the inherited
* set. If they aren't, trying to get them will fail. Add the ambient
* set inherited capabilities to the capability set in the context.
* This is needed because if capabilities are set (using "Capabilities="
* keyword), they will override whatever we set now. */
r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
return r;
}
}
}
if (context->user) {
r = enforce_user(context, uid);
if (r < 0) {
*exit_status = EXIT_USER;
return r;
}
if (context->capability_ambient_set != 0) {
/* Fix the ambient capabilities after user change. */
r = capability_ambient_set_apply(context->capability_ambient_set, false);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
return r;
}
/* If we were asked to change user and ambient capabilities
* were requested, we had to add keep-caps to the securebits
* so that we would maintain the inherited capability set
* through the setresuid(). Make sure that the bit is added
* also to the context secure_bits so that we don't try to
* drop the bit away next. */
secure_bits |= 1<<SECURE_KEEP_CAPS;
}
}
/* PR_GET_SECUREBITS is not privileged, while
* PR_SET_SECUREBITS is. So to suppress
* potential EPERMs we'll try not to call
* PR_SET_SECUREBITS unless necessary. */
if (prctl(PR_GET_SECUREBITS) != context->secure_bits)
if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) {
if (prctl(PR_GET_SECUREBITS) != secure_bits)
if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) {
*exit_status = EXIT_SECUREBITS;
return -errno;
}
@ -2114,6 +2162,7 @@ void exec_context_init(ExecContext *c) {
c->timer_slack_nsec = NSEC_INFINITY;
c->personality = PERSONALITY_INVALID;
c->runtime_directory_mode = 0755;
c->capability_bounding_set = CAP_ALL;
}
void exec_context_done(ExecContext *c) {
@ -2517,12 +2566,23 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
(c->secure_bits & 1<<SECURE_NOROOT) ? " noroot" : "",
(c->secure_bits & 1<<SECURE_NOROOT_LOCKED) ? "noroot-locked" : "");
if (c->capability_bounding_set_drop) {
if (c->capability_bounding_set != CAP_ALL) {
unsigned long l;
fprintf(f, "%sCapabilityBoundingSet:", prefix);
for (l = 0; l <= cap_last_cap(); l++)
if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l)))
if (c->capability_bounding_set & (UINT64_C(1) << l))
fprintf(f, " %s", strna(capability_to_name(l)));
fputs("\n", f);
}
if (c->capability_ambient_set != 0) {
unsigned long l;
fprintf(f, "%sAmbientCapabilities:", prefix);
for (l = 0; l <= cap_last_cap(); l++)
if (c->capability_ambient_set & (UINT64_C(1) << l))
fprintf(f, " %s", strna(capability_to_name(l)));
fputs("\n", f);

View File

@ -155,7 +155,9 @@ struct ExecContext {
char **read_write_dirs, **read_only_dirs, **inaccessible_dirs;
unsigned long mount_flags;
uint64_t capability_bounding_set_drop;
uint64_t capability_bounding_set;
uint64_t capability_ambient_set;
cap_t capabilities;
int secure_bits;

View File

@ -47,7 +47,8 @@ $1.SyslogLevel, config_parse_log_level, 0,
$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix)
$1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context)
$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context)
$1.CapabilityBoundingSet, config_parse_bounding_set, 0, offsetof($1, exec_context.capability_bounding_set_drop)
$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set)
$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set)
$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec)
$1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context)
m4_ifdef(`HAVE_SECCOMP',

View File

@ -38,6 +38,7 @@
#include "bus-internal.h"
#include "bus-util.h"
#include "cap-list.h"
#include "capability-util.h"
#include "cgroup.h"
#include "conf-parser.h"
#include "cpu-set-util.h"
@ -1024,7 +1025,7 @@ int config_parse_exec_secure_bits(const char *unit,
return 0;
}
int config_parse_bounding_set(
int config_parse_capability_set(
const char *unit,
const char *filename,
unsigned line,
@ -1036,8 +1037,8 @@ int config_parse_bounding_set(
void *data,
void *userdata) {
uint64_t *capability_bounding_set_drop = data;
uint64_t capability_bounding_set, sum = 0;
uint64_t *capability_set = data;
uint64_t sum = 0, initial = 0;
bool invert = false;
const char *p;
@ -1051,10 +1052,9 @@ int config_parse_bounding_set(
rvalue++;
}
/* Note that we store this inverted internally, since the
* kernel wants it like this. But we actually expose it
* non-inverted everywhere to have a fully normalized
* interface. */
if (strcmp(lvalue, "CapabilityBoundingSet") == 0)
initial = CAP_ALL; /* initialized to all bits on */
/* else "AmbientCapabilities" initialized to all bits off */
p = rvalue;
for (;;) {
@ -1073,18 +1073,21 @@ int config_parse_bounding_set(
cap = capability_from_name(word);
if (cap < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding set, ignoring: %s", word);
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding/ambient set, ignoring: %s", word);
continue;
}
sum |= ((uint64_t) UINT64_C(1)) << (uint64_t) cap;
}
capability_bounding_set = invert ? ~sum : sum;
if (*capability_bounding_set_drop != 0 && capability_bounding_set != 0)
*capability_bounding_set_drop = ~(~*capability_bounding_set_drop | capability_bounding_set);
sum = invert ? ~sum : sum;
if (sum == 0 || *capability_set == initial)
/* "" or uninitialized data -> replace */
*capability_set = sum;
else
*capability_bounding_set_drop = ~capability_bounding_set;
/* previous data -> merge */
*capability_set |= sum;
return 0;
}
@ -4002,7 +4005,7 @@ void unit_dump_config_items(FILE *f) {
{ config_parse_log_level, "LEVEL" },
{ config_parse_exec_capabilities, "CAPABILITIES" },
{ config_parse_exec_secure_bits, "SECUREBITS" },
{ config_parse_bounding_set, "BOUNDINGSET" },
{ config_parse_capability_set, "BOUNDINGSET" },
{ config_parse_limit, "LIMIT" },
{ config_parse_unit_deps, "UNIT [...]" },
{ config_parse_exec, "PATH [ARGUMENT [...]]" },

View File

@ -56,7 +56,7 @@ int config_parse_exec_cpu_sched_prio(const char *unit, const char *filename, uns
int config_parse_exec_cpu_affinity(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_capabilities(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_exec_secure_bits(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_bounding_set(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_capability_set(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_bytes_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_sec_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);

View File

@ -117,7 +117,7 @@ static usec_t arg_runtime_watchdog = 0;
static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE;
static char **arg_default_environment = NULL;
static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {};
static uint64_t arg_capability_bounding_set_drop = 0;
static uint64_t arg_capability_bounding_set = CAP_ALL;
static nsec_t arg_timer_slack_nsec = NSEC_INFINITY;
static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE;
static Set* arg_syscall_archs = NULL;
@ -644,7 +644,7 @@ static int parse_config_file(void) {
{ "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers },
{ "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog },
{ "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog },
{ "Manager", "CapabilityBoundingSet", config_parse_bounding_set, 0, &arg_capability_bounding_set_drop },
{ "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set },
#ifdef HAVE_SECCOMP
{ "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs },
#endif
@ -1631,14 +1631,14 @@ int main(int argc, char *argv[]) {
if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0)
log_error_errno(errno, "Failed to adjust timer slack: %m");
if (arg_capability_bounding_set_drop) {
r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop);
if (!cap_test_all(arg_capability_bounding_set)) {
r = capability_bounding_set_drop_usermode(arg_capability_bounding_set);
if (r < 0) {
log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m");
error_message = "Failed to drop capability bounding set of usermode helpers";
goto finish;
}
r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true);
r = capability_bounding_set_drop(arg_capability_bounding_set, true);
if (r < 0) {
log_emergency_errno(r, "Failed to drop capability bounding set: %m");
error_message = "Failed to drop capability bounding set";

View File

@ -3231,7 +3231,7 @@ int unit_patch_contexts(Unit *u) {
ec->no_new_privileges = true;
if (ec->private_devices)
ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD;
ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD);
}
cc = unit_get_cgroup_context(u);

View File

@ -134,7 +134,7 @@ int import_fork_tar_x(const char *path, pid_t *ret) {
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
r = capability_bounding_set_drop(~retain, true);
r = capability_bounding_set_drop(retain, true);
if (r < 0)
log_error_errno(r, "Failed to drop capabilities, ignoring: %m");
@ -208,7 +208,7 @@ int import_fork_tar_c(const char *path, pid_t *ret) {
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
r = capability_bounding_set_drop(~retain, true);
r = capability_bounding_set_drop(retain, true);
if (r < 0)
log_error_errno(r, "Failed to drop capabilities, ignoring: %m");

View File

@ -1482,7 +1482,7 @@ static int setup_journal(const char *directory) {
}
static int drop_capabilities(void) {
return capability_bounding_set_drop(~arg_retain, false);
return capability_bounding_set_drop(arg_retain, false);
}
static int reset_audit_loginuid(void) {

View File

@ -20,6 +20,7 @@
#include <netinet/in.h>
#include <pwd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
@ -66,8 +67,9 @@ static void show_capabilities(void) {
cap_free(text);
}
static int setup_tests(void) {
static int setup_tests(bool *run_ambient) {
struct passwd *nobody;
int r;
nobody = getpwnam("nobody");
if (!nobody) {
@ -77,6 +79,18 @@ static int setup_tests(void) {
test_uid = nobody->pw_uid;
test_gid = nobody->pw_gid;
*run_ambient = false;
r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
/* There's support for PR_CAP_AMBIENT if the prctl() call
* succeeded or error code was something else than EINVAL. The
* EINVAL check should be good enough to rule out false
* positives. */
if (r >= 0 || errno != EINVAL)
*run_ambient = true;
return 0;
}
@ -140,8 +154,53 @@ static void test_have_effective_cap(void) {
assert_se(!have_effective_cap(CAP_CHOWN));
}
static void test_update_inherited_set(void) {
cap_t caps;
uint64_t set = 0;
cap_flag_value_t fv;
caps = cap_get_proc();
assert_se(caps);
assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
assert(fv == CAP_CLEAR);
set = (UINT64_C(1) << CAP_CHOWN);
assert_se(!capability_update_inherited_set(caps, set));
assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
assert(fv == CAP_SET);
cap_free(caps);
}
static void test_set_ambient_caps(void) {
cap_t caps;
uint64_t set = 0;
cap_flag_value_t fv;
caps = cap_get_proc();
assert_se(caps);
assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
assert(fv == CAP_CLEAR);
cap_free(caps);
assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0);
set = (UINT64_C(1) << CAP_CHOWN);
assert_se(!capability_ambient_set_apply(set, true));
caps = cap_get_proc();
assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
assert(fv == CAP_SET);
cap_free(caps);
assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 1);
}
int main(int argc, char *argv[]) {
int r;
bool run_ambient;
log_parse_environment();
log_open();
@ -149,14 +208,19 @@ int main(int argc, char *argv[]) {
if (getuid() != 0)
return EXIT_TEST_SKIP;
r = setup_tests();
r = setup_tests(&run_ambient);
if (r < 0)
return -r;
show_capabilities();
test_drop_privileges();
test_update_inherited_set();
fork_test(test_have_effective_cap);
if (run_ambient)
fork_test(test_set_ambient_caps);
return 0;
}

View File

@ -20,6 +20,7 @@
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include "fileio.h"
@ -224,6 +225,20 @@ static void test_exec_capabilityboundingset(Manager *m) {
test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED);
}
static void test_exec_capabilityambientset(Manager *m) {
int r;
/* Check if the kernel has support for ambient capabilities. Run
* the tests only if that's the case. Clearing all ambient
* capabilities is fine, since we are expecting them to be unset
* in the first place for the tests. */
r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
if (r >= 0 || errno != EINVAL) {
test(m, "exec-capabilityambientset.service", 0, CLD_EXITED);
test(m, "exec-capabilityambientset-merge.service", 0, CLD_EXITED);
}
}
static void test_exec_privatenetwork(Manager *m) {
int r;
@ -266,6 +281,7 @@ int main(int argc, char *argv[]) {
test_exec_umask,
test_exec_runtimedirectory,
test_exec_capabilityboundingset,
test_exec_capabilityambientset,
test_exec_oomscoreadjust,
test_exec_ioschedulingclass,
NULL,

View File

@ -28,6 +28,7 @@
#include <unistd.h>
#include "alloc-util.h"
#include "capability-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hashmap.h"
@ -625,8 +626,8 @@ static uint64_t make_cap(int cap) {
return ((uint64_t) 1ULL << (uint64_t) cap);
}
static void test_config_parse_bounding_set(void) {
/* int config_parse_bounding_set(
static void test_config_parse_capability_set(void) {
/* int config_parse_capability_set(
const char *unit,
const char *filename,
unsigned line,
@ -638,38 +639,38 @@ static void test_config_parse_bounding_set(void) {
void *data,
void *userdata) */
int r;
uint64_t capability_bounding_set_drop = 0;
uint64_t capability_bounding_set = 0;
r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "CAP_NET_RAW",
&capability_bounding_set_drop, NULL);
&capability_bounding_set, NULL);
assert_se(r >= 0);
assert_se(capability_bounding_set_drop == ~make_cap(CAP_NET_RAW));
assert_se(capability_bounding_set == make_cap(CAP_NET_RAW));
r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "CAP_NET_ADMIN",
&capability_bounding_set_drop, NULL);
&capability_bounding_set, NULL);
assert_se(r >= 0);
assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "",
&capability_bounding_set_drop, NULL);
&capability_bounding_set, NULL);
assert_se(r >= 0);
assert_se(capability_bounding_set_drop == ~((uint64_t) 0ULL));
assert_se(capability_bounding_set == UINT64_C(0));
r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "~",
&capability_bounding_set_drop, NULL);
&capability_bounding_set, NULL);
assert_se(r >= 0);
assert_se(capability_bounding_set_drop == (uint64_t) 0ULL);
assert_se(cap_test_all(capability_bounding_set));
capability_bounding_set_drop = 0;
r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
capability_bounding_set = 0;
r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, " 'CAP_NET_RAW' WAT_CAP??? CAP_NET_ADMIN CAP'_trailing_garbage",
&capability_bounding_set_drop, NULL);
&capability_bounding_set, NULL);
assert_se(r >= 0);
assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
}
static void test_config_parse_rlimit(void) {
@ -829,7 +830,7 @@ int main(int argc, char *argv[]) {
r = test_unit_file_get_set();
test_config_parse_exec();
test_config_parse_bounding_set();
test_config_parse_capability_set();
test_config_parse_rlimit();
test_config_parse_pass_environ();
test_load_env_file_1();

View File

@ -0,0 +1,9 @@
[Unit]
Description=Test for AmbientCapabilities
[Service]
ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"'
Type=oneshot
User=nobody
AmbientCapabilities=CAP_NET_ADMIN
AmbientCapabilities=CAP_NET_RAW

View File

@ -0,0 +1,8 @@
[Unit]
Description=Test for AmbientCapabilities
[Service]
ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"'
Type=oneshot
User=nobody
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW