1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-03 05:18:09 +03:00

capability-util: generalize helper to acquire local caps (#35403)

This generalizes and modernizes the code to acquire set of local caps,
based on the code for this in the condition logic. Uses PidRef, and
acquires the full quintuplet of caps.

This can be considered preparation to one day maybe build without
libcap.
This commit is contained in:
Yu Watanabe 2024-12-20 11:52:24 +09:00 committed by GitHub
commit 8a135111ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 122 additions and 77 deletions

View File

@ -8,8 +8,9 @@
#include <unistd.h>
#include "alloc-util.h"
#include "capability-util.h"
#include "cap-list.h"
#include "capability-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "log.h"
#include "logarithm.h"
@ -17,6 +18,8 @@
#include "missing_prctl.h"
#include "missing_threads.h"
#include "parse-util.h"
#include "pidref.h"
#include "stat-util.h"
#include "user-util.h"
int have_effective_cap(int value) {
@ -607,3 +610,78 @@ int capability_get_ambient(uint64_t *ret) {
*ret = a;
return 1;
}
int pidref_get_capability(const PidRef *pidref, CapabilityQuintet *ret) {
int r;
if (!pidref_is_set(pidref))
return -ESRCH;
if (pidref_is_remote(pidref))
return -EREMOTE;
const char *path = procfs_file_alloca(pidref->pid, "status");
_cleanup_fclose_ FILE *f = fopen(path, "re");
if (!f) {
if (errno == ENOENT && proc_mounted() == 0)
return -ENOSYS;
return -errno;
}
CapabilityQuintet q = CAPABILITY_QUINTET_NULL;
for (;;) {
_cleanup_free_ char *line = NULL;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
static const struct {
const char *field;
size_t offset;
} fields[] = {
{ "CapBnd:", offsetof(CapabilityQuintet, bounding) },
{ "CapInh:", offsetof(CapabilityQuintet, inheritable) },
{ "CapPrm:", offsetof(CapabilityQuintet, permitted) },
{ "CapEff:", offsetof(CapabilityQuintet, effective) },
{ "CapAmb:", offsetof(CapabilityQuintet, ambient) },
};
FOREACH_ELEMENT(i, fields) {
const char *p = first_word(line, i->field);
if (!p)
continue;
uint64_t *v = (uint64_t*) ((uint8_t*) &q + i->offset);
if (*v != CAP_MASK_UNSET)
return -EBADMSG;
r = safe_atoux64(p, v);
if (r < 0)
return r;
if (*v == CAP_MASK_UNSET)
return -EBADMSG;
}
}
if (q.effective == CAP_MASK_UNSET ||
q.inheritable == CAP_MASK_UNSET ||
q.permitted == CAP_MASK_UNSET ||
q.effective == CAP_MASK_UNSET ||
q.ambient == CAP_MASK_UNSET)
return -EBADMSG;
r = pidref_verify(pidref);
if (r < 0)
return r;
if (ret)
*ret = q;
return 0;
}

View File

@ -8,6 +8,7 @@
#include "macro.h"
#include "missing_capability.h"
#include "pidref.h"
/* Special marker used when storing a capabilities mask as "unset" */
#define CAP_MASK_UNSET UINT64_MAX
@ -66,14 +67,18 @@ typedef struct CapabilityQuintet {
assert_cc(CAP_LAST_CAP < 64);
#define CAPABILITY_QUINTET_NULL { CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET }
#define CAPABILITY_QUINTET_NULL (CapabilityQuintet) { CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET, CAP_MASK_UNSET }
static inline bool capability_is_set(uint64_t v) {
return v != CAP_MASK_UNSET;
}
static inline bool capability_quintet_is_set(const CapabilityQuintet *q) {
return q->effective != CAP_MASK_UNSET ||
q->bounding != CAP_MASK_UNSET ||
q->inheritable != CAP_MASK_UNSET ||
q->permitted != CAP_MASK_UNSET ||
q->ambient != CAP_MASK_UNSET;
return capability_is_set(q->effective) ||
capability_is_set(q->bounding) ||
capability_is_set(q->inheritable) ||
capability_is_set(q->permitted) ||
capability_is_set(q->ambient);
}
/* Mangles the specified caps quintet taking the current bounding set into account:
@ -84,3 +89,5 @@ bool capability_quintet_mangle(CapabilityQuintet *q);
int capability_quintet_enforce(const CapabilityQuintet *q);
int capability_get_ambient(uint64_t *ret);
int pidref_get_capability(const PidRef *pidref, CapabilityQuintet *ret);

View File

@ -500,22 +500,6 @@ int pidref_is_kernel_thread(const PidRef *pid) {
return result;
}
int get_process_capeff(pid_t pid, char **ret) {
const char *p;
int r;
assert(pid >= 0);
assert(ret);
p = procfs_file_alloca(pid, "status");
r = get_proc_field(p, "CapEff", WHITESPACE, ret);
if (r == -ENOENT)
return -ESRCH;
return r;
}
static int get_process_link_contents(pid_t pid, const char *proc_file, char **ret) {
const char *p;
int r;

View File

@ -50,7 +50,6 @@ int get_process_exe(pid_t pid, char **ret);
int pid_get_uid(pid_t pid, uid_t *ret);
int pidref_get_uid(const PidRef *pid, uid_t *ret);
int get_process_gid(pid_t pid, gid_t *ret);
int get_process_capeff(pid_t pid, char **ret);
int get_process_cwd(pid_t pid, char **ret);
int get_process_root(pid_t pid, char **ret);
int get_process_environ(pid_t pid, char **ret);

View File

@ -132,6 +132,7 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
.log_level_max = -1,
.log_ratelimit_interval = s->ratelimit_interval,
.log_ratelimit_burst = s->ratelimit_burst,
.capability_quintet = CAPABILITY_QUINTET_NULL,
};
r = hashmap_ensure_put(&s->client_contexts, NULL, PID_TO_PTR(pid), c);
@ -154,7 +155,6 @@ static void client_context_reset(Server *s, ClientContext *c) {
c->comm = mfree(c->comm);
c->exe = mfree(c->exe);
c->cmdline = mfree(c->cmdline);
c->capeff = mfree(c->capeff);
c->auditid = AUDIT_SESSION_INVALID;
c->loginuid = UID_INVALID;
@ -184,6 +184,8 @@ static void client_context_reset(Server *s, ClientContext *c) {
c->log_filter_allowed_patterns = set_free_free(c->log_filter_allowed_patterns);
c->log_filter_denied_patterns = set_free_free(c->log_filter_denied_patterns);
c->capability_quintet = CAPABILITY_QUINTET_NULL;
}
static ClientContext* client_context_free(Server *s, ClientContext *c) {
@ -233,8 +235,7 @@ static void client_context_read_basic(ClientContext *c) {
if (pid_get_cmdline(c->pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &t) >= 0)
free_and_replace(c->cmdline, t);
if (get_process_capeff(c->pid, &t) >= 0)
free_and_replace(c->capeff, t);
(void) pidref_get_capability(&PIDREF_MAKE_FROM_PID(c->pid), &c->capability_quintet);
}
static int client_context_read_label(

View File

@ -7,6 +7,7 @@
#include "sd-id128.h"
#include "capability-util.h"
#include "set.h"
#include "time-util.h"
@ -27,7 +28,7 @@ struct ClientContext {
char *comm;
char *exe;
char *cmdline;
char *capeff;
CapabilityQuintet capability_quintet;
uint32_t auditid;
uid_t loginuid;

View File

@ -1109,7 +1109,7 @@ static void server_dispatch_message_real(
* Let's use a heap allocation for this one. */
cmdline1 = set_iovec_string_field(iovec, &n, "_CMDLINE=", c->cmdline);
IOVEC_ADD_STRING_FIELD(iovec, n, c->capeff, "_CAP_EFFECTIVE"); /* Read from /proc/.../status */
IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->capability_quintet.effective, uint64_t, capability_is_set, "%" PRIx64, "_CAP_EFFECTIVE");
IOVEC_ADD_SIZED_FIELD(iovec, n, c->label, c->label_size, "_SELINUX_CONTEXT");
IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->auditid, uint32_t, audit_session_is_valid, "%" PRIu32, "_AUDIT_SESSION");
IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->loginuid, uid_t, uid_is_valid, UID_FMT, "_AUDIT_LOGINUID");
@ -1144,7 +1144,7 @@ static void server_dispatch_message_real(
if (o->cmdline)
cmdline2 = set_iovec_string_field(iovec, &n, "OBJECT_CMDLINE=", o->cmdline);
IOVEC_ADD_STRING_FIELD(iovec, n, o->capeff, "OBJECT_CAP_EFFECTIVE");
IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->capability_quintet.effective, uint64_t, capability_is_set, "%" PRIx64, "OBJECT_CAP_EFFECTIVE");
IOVEC_ADD_SIZED_FIELD(iovec, n, o->label, o->label_size, "OBJECT_SELINUX_CONTEXT");
IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->auditid, uint32_t, audit_session_is_valid, "%" PRIu32, "OBJECT_AUDIT_SESSION");
IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->loginuid, uid_t, uid_is_valid, UID_FMT, "OBJECT_AUDIT_LOGINUID");

View File

@ -21,6 +21,7 @@
#include "battery-util.h"
#include "blockdev-util.h"
#include "cap-list.h"
#include "capability-util.h"
#include "cgroup-util.h"
#include "compare-operator.h"
#include "condition.h"
@ -701,45 +702,23 @@ static int condition_test_security(Condition *c, char **env) {
}
static int condition_test_capability(Condition *c, char **env) {
unsigned long long capabilities = (unsigned long long) -1;
_cleanup_fclose_ FILE *f = NULL;
int value, r;
int r;
assert(c);
assert(c->parameter);
assert(c->type == CONDITION_CAPABILITY);
/* If it's an invalid capability, we don't have it */
value = capability_from_name(c->parameter);
int value = capability_from_name(c->parameter);
if (value < 0)
return -EINVAL;
/* If it's a valid capability we default to assume
* that we have it */
CapabilityQuintet q;
r = pidref_get_capability(&PIDREF_MAKE_FROM_PID(getpid_cached()), &q);
if (r < 0)
return r;
f = fopen("/proc/self/status", "re");
if (!f)
return -errno;
for (;;) {
_cleanup_free_ char *line = NULL;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0)
break;
const char *p = startswith(line, "CapBnd:");
if (p) {
if (sscanf(p, "%llx", &capabilities) != 1)
return -EIO;
break;
}
}
return !!(capabilities & (1ULL << value));
return !!(q.bounding & ((UINT64_C(1) << value)));
}
static int condition_test_needs_update(Condition *c, char **env) {

View File

@ -305,6 +305,18 @@ static void test_capability_get_ambient(void) {
}
}
static void test_pidref_get_capability(void) {
CapabilityQuintet q = CAPABILITY_QUINTET_NULL;
assert_se(pidref_get_capability(&PIDREF_MAKE_FROM_PID(getpid_cached()), &q) >= 0);
assert_se(q.effective != CAP_MASK_UNSET);
assert_se(q.inheritable != CAP_MASK_UNSET);
assert_se(q.permitted != CAP_MASK_UNSET);
assert_se(q.effective != CAP_MASK_UNSET);
assert_se(q.ambient != CAP_MASK_UNSET);
}
int main(int argc, char *argv[]) {
bool run_ambient;
@ -336,5 +348,7 @@ int main(int argc, char *argv[]) {
test_capability_get_ambient();
test_pidref_get_capability();
return 0;
}

View File

@ -363,24 +363,6 @@ TEST(status_field) {
}
}
TEST(capeff) {
for (int pid = 0; pid < 2; pid++) {
_cleanup_free_ char *capeff = NULL;
int r, p;
r = get_process_capeff(0, &capeff);
log_info("capeff: '%s' (r=%d)", capeff, r);
if (IN_SET(r, -ENOENT, -EPERM))
return;
assert_se(r == 0);
assert_se(*capeff);
p = capeff[strspn(capeff, HEXDIGITS)];
assert_se(!p || isspace(p));
}
}
TEST(read_one_line_file) {
_cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-fileio-1lf-XXXXXX";
int fd;