From 1184626a269e38dcdfcd1042975a31d4eb30cd5e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 28 Nov 2024 12:59:21 +0100 Subject: [PATCH 1/3] capability-util: generalize helper to acquire local caps 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. --- src/basic/capability-util.c | 80 ++++++++++++++++++++++++++++++++++++- src/basic/capability-util.h | 3 ++ src/shared/condition.c | 37 ++++------------- src/test/test-capability.c | 14 +++++++ 4 files changed, 104 insertions(+), 30 deletions(-) diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index 8040eb399a..f4fab22e73 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -8,8 +8,9 @@ #include #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; +} diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index bb781940c4..7f9160e006 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -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 @@ -84,3 +85,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); diff --git a/src/shared/condition.c b/src/shared/condition.c index ac23681a11..e4aa50c8f7 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -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) { diff --git a/src/test/test-capability.c b/src/test/test-capability.c index e497cccead..6c23c1bb28 100644 --- a/src/test/test-capability.c +++ b/src/test/test-capability.c @@ -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; } From a5370d35d6ea88a99d84e8549f67b271b91ade25 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Dec 2024 19:01:13 +0100 Subject: [PATCH 2/3] capability-util: introduce capability_is_set() helper --- src/basic/capability-util.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 7f9160e006..0195518d84 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -69,12 +69,16 @@ 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 } +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: From 25b1a73f71d9a8103e812f802e9134b217b60dc7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Dec 2024 19:02:24 +0100 Subject: [PATCH 3/3] journald: get rid of get_process_capeff(), use pidref_get_capability() instead This does pretty much the same, but is nicer, since it parses things properly. --- src/basic/capability-util.h | 2 +- src/basic/process-util.c | 16 ---------------- src/basic/process-util.h | 1 - src/journal/journald-context.c | 7 ++++--- src/journal/journald-context.h | 3 ++- src/journal/journald-server.c | 4 ++-- src/test/test-fileio.c | 18 ------------------ 7 files changed, 9 insertions(+), 42 deletions(-) diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 0195518d84..9caae1ac06 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -67,7 +67,7 @@ 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; diff --git a/src/basic/process-util.c b/src/basic/process-util.c index c74a7e7ea4..d02a122a35 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -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; diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 0763b64cff..f1088afc1d 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -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); diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index c48ad81f37..50dc8867e1 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -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( diff --git a/src/journal/journald-context.h b/src/journal/journald-context.h index 6e0d9f6f75..34637f85e4 100644 --- a/src/journal/journald-context.h +++ b/src/journal/journald-context.h @@ -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; diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index bebc1e584c..8fcfa0c57b 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -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"); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 76f5598325..60b568b2d2 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -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;