mirror of
https://github.com/systemd/systemd.git
synced 2024-11-24 02:33:36 +08:00
Merge pull request #2265 from ipuustin/ambient
capabilities: added support for ambient capabilities.
This commit is contained in:
commit
1f52a79d4e
@ -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
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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 [...]]" },
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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
|
8
test/test-execute/exec-capabilityambientset.service
Normal file
8
test/test-execute/exec-capabilityambientset.service
Normal 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
|
Loading…
Reference in New Issue
Block a user