From ab7e3ef561e5c1ff63b58f4a329a9f90f0768eb0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 18:39:18 +0200 Subject: [PATCH 01/13] escape: fix systemd-escape description text The long man page paragraph got it right: the tool is for escaping systemd unit names, not just system unit names. Also fix the short man page paragraph and the --help text. Follow-up for 303608c1bcf9568371625fbbd9442946cadba422 --- man/systemd-escape.xml | 2 +- src/escape/escape.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/man/systemd-escape.xml b/man/systemd-escape.xml index bb4c7e48e5..fb20d2d94f 100644 --- a/man/systemd-escape.xml +++ b/man/systemd-escape.xml @@ -45,7 +45,7 @@ systemd-escape - Escape strings for usage in system unit names + Escape strings for usage in systemd unit names diff --git a/src/escape/escape.c b/src/escape/escape.c index 89e885d47c..5518c2a6fa 100644 --- a/src/escape/escape.c +++ b/src/escape/escape.c @@ -38,7 +38,7 @@ static bool arg_path = false; static void help(void) { printf("%s [OPTIONS...] [NAME...]\n\n" - "Escape strings for usage in system unit names.\n\n" + "Escape strings for usage in systemd unit names.\n\n" " -h --help Show this help\n" " --version Show package version\n" " --suffix=SUFFIX Unit suffix to append to escaped strings\n" From 3a87a86e33c20aab20d8b221adae2015d12bbb80 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 18:42:17 +0200 Subject: [PATCH 02/13] audit: introduce audit_session_is_valid() and make use of it everywhere Let's add a proper validation function, since validation isn't entirely trivial. Make use of it where applicable. Also make use of AUDIT_SESSION_INVALID where we need a marker for an invalid audit session. --- src/basic/audit-util.c | 4 ++-- src/basic/audit-util.h | 4 ++++ src/libsystemd/sd-bus/bus-creds.c | 2 +- src/login/logind-dbus.c | 6 +++--- src/login/logind-session.c | 8 ++++---- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/basic/audit-util.c b/src/basic/audit-util.c index d1c9695973..24a6c8a936 100644 --- a/src/basic/audit-util.c +++ b/src/basic/audit-util.c @@ -54,7 +54,7 @@ int audit_session_from_pid(pid_t pid, uint32_t *id) { if (r < 0) return r; - if (u == AUDIT_SESSION_INVALID || u <= 0) + if (!audit_session_is_valid(u)) return -ENODATA; *id = u; @@ -81,7 +81,7 @@ int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { if (r < 0) return r; - *uid = (uid_t) u; + *uid = u; return 0; } diff --git a/src/basic/audit-util.h b/src/basic/audit-util.h index e048503991..3088951326 100644 --- a/src/basic/audit-util.h +++ b/src/basic/audit-util.h @@ -29,3 +29,7 @@ int audit_session_from_pid(pid_t pid, uint32_t *id); int audit_loginuid_from_pid(pid_t pid, uid_t *uid); bool use_audit(void); + +static inline bool audit_session_is_valid(uint32_t id) { + return id > 0 && id != AUDIT_SESSION_INVALID; +} diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c index 649fcdba44..f10592acd6 100644 --- a/src/libsystemd/sd-bus/bus-creds.c +++ b/src/libsystemd/sd-bus/bus-creds.c @@ -570,7 +570,7 @@ _public_ int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessio if (!(c->mask & SD_BUS_CREDS_AUDIT_SESSION_ID)) return -ENODATA; - if (c->audit_session_id == AUDIT_SESSION_INVALID) + if (!audit_session_is_valid(c->audit_session_id)) return -ENXIO; *sessionid = c->audit_session_id; diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index c9b7d99818..e22956bda2 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -767,8 +767,8 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus if (hashmap_size(m->sessions) >= m->sessions_max) return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.", m->sessions_max); - audit_session_from_pid(leader, &audit_id); - if (audit_id > 0) { + (void) audit_session_from_pid(leader, &audit_id); + if (audit_session_is_valid(audit_id)) { /* Keep our session IDs and the audit session IDs in sync */ if (asprintf(&id, "%"PRIu32, audit_id) < 0) @@ -780,7 +780,7 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus * ID */ if (hashmap_get(m->sessions, id)) { log_warning("Existing logind session ID %s used by new audit session, ignoring", id); - audit_id = 0; + audit_id = AUDIT_SESSION_INVALID; id = mfree(id); } diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 42dfecaffb..11d9e8ff5e 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -82,6 +82,7 @@ Session* session_new(Manager *m, const char *id) { s->manager = m; s->fifo_fd = -1; s->vtfd = -1; + s->audit_id = AUDIT_SESSION_INVALID; return s; } @@ -283,7 +284,7 @@ int session_save(Session *s) { if (s->leader > 0) fprintf(f, "LEADER="PID_FMT"\n", s->leader); - if (s->audit_id > 0) + if (audit_session_is_valid(s->audit_id)) fprintf(f, "AUDIT=%"PRIu32"\n", s->audit_id); if (dual_timestamp_is_set(&s->timestamp)) @@ -459,9 +460,8 @@ int session_load(Session *s) { } if (leader) { - k = parse_pid(leader, &s->leader); - if (k >= 0) - audit_session_from_pid(s->leader, &s->audit_id); + if (parse_pid(leader, &s->leader) >= 0) + (void) audit_session_from_pid(s->leader, &s->audit_id); } if (type) { From cad93f2996f18c987b3b4b62a5ede762c11338c8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 18:57:04 +0200 Subject: [PATCH 03/13] core, sd-bus, logind: make use of uid_is_valid() in more places --- src/core/execute.c | 8 ++++---- src/libsystemd/sd-bus/bus-creds.c | 2 +- src/login/logind-dbus.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/execute.c b/src/core/execute.c index 48b84815ca..f9580a25ad 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -286,7 +286,7 @@ static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { gid_t oldgid = GID_INVALID; int r; - if (gid != GID_INVALID) { + if (gid_is_valid(gid)) { oldgid = getgid(); r = setegid(gid); @@ -294,7 +294,7 @@ static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { return -errno; } - if (uid != UID_INVALID) { + if (uid_is_valid(uid)) { olduid = getuid(); r = seteuid(uid); @@ -311,11 +311,11 @@ static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { /* If we fail to restore the uid or gid, things will likely fail later on. This should only happen if an LSM interferes. */ - if (uid != UID_INVALID) + if (uid_is_valid(uid)) (void) seteuid(olduid); restore_gid: - if (gid != GID_INVALID) + if (gid_is_valid(gid)) (void) setegid(oldgid); return r; diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c index f10592acd6..a05b4215fb 100644 --- a/src/libsystemd/sd-bus/bus-creds.c +++ b/src/libsystemd/sd-bus/bus-creds.c @@ -584,7 +584,7 @@ _public_ int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *uid) { if (!(c->mask & SD_BUS_CREDS_AUDIT_LOGIN_UID)) return -ENODATA; - if (c->audit_login_uid == UID_INVALID) + if (!uid_is_valid(c->audit_login_uid)) return -ENXIO; *uid = c->audit_login_uid; diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index e22956bda2..cd22ff4fca 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -88,7 +88,7 @@ int manager_get_user_from_creds(Manager *m, sd_bus_message *message, uid_t uid, assert(message); assert(ret); - if (uid == UID_INVALID) { + if (!uid_is_valid(uid)) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; /* Note that we get the owner UID of the session, not the actual client UID here! */ @@ -1132,7 +1132,7 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu if (r < 0) return r; - if (uid == UID_INVALID) { + if (!uid_is_valid(uid)) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; /* Note that we get the owner UID of the session, not the actual client UID here! */ From 54191eb3e74a8fa8bdd049471e630541c65e4f25 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 18:57:54 +0200 Subject: [PATCH 04/13] parse-util: introduce pid_is_valid() Checking for validity of a PID is relatively easy, but let's add a helper cal for this too, in order to make things more readable and more similar to uid_is_valid(), gid_is_valid() and friends. --- src/basic/parse-util.c | 2 +- src/basic/process-util.h | 4 ++++ src/login/logind-session.c | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 4532f222c8..89bb667c5f 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -59,7 +59,7 @@ int parse_pid(const char *s, pid_t* ret_pid) { if ((unsigned long) pid != ul) return -ERANGE; - if (pid <= 0) + if (!pid_is_valid(pid)) return -ERANGE; *ret_pid = pid; diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 17746b4ebf..b45d60dbd1 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -118,6 +118,10 @@ static inline bool ioprio_priority_is_valid(int i) { return i >= 0 && i < IOPRIO_BE_NR; } +static inline bool pid_is_valid(pid_t p) { + return p > 0; +} + int ioprio_parse_priority(const char *s, int *ret); pid_t getpid_cached(void); diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 11d9e8ff5e..3778bb7d70 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -45,6 +45,7 @@ #include "terminal-util.h" #include "user-util.h" #include "util.h" +#include "process-util.h" #define RELEASE_USEC (20*USEC_PER_SEC) @@ -281,7 +282,7 @@ int session_save(Session *s) { if (!s->vtnr) fprintf(f, "POSITION=%u\n", s->position); - if (s->leader > 0) + if (pid_is_valid(s->leader)) fprintf(f, "LEADER="PID_FMT"\n", s->leader); if (audit_session_is_valid(s->audit_id)) From 92a17af991c45b96e9fe2095028561f5baf6cab9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 18:58:57 +0200 Subject: [PATCH 05/13] execute: make some code shorter Let's simplify some lines to make it shorter. --- src/core/execute.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/core/execute.c b/src/core/execute.c index f9580a25ad..b76b9b9e6f 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -278,7 +278,7 @@ static int open_null_as(int flags, int nfd) { } static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { - union sockaddr_union sa = { + static const union sockaddr_union sa = { .un.sun_family = AF_UNIX, .un.sun_path = "/run/systemd/journal/stdout", }; @@ -289,24 +289,20 @@ static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { if (gid_is_valid(gid)) { oldgid = getgid(); - r = setegid(gid); - if (r < 0) + if (setegid(gid) < 0) return -errno; } if (uid_is_valid(uid)) { olduid = getuid(); - r = seteuid(uid); - if (r < 0) { + if (seteuid(uid) < 0) { r = -errno; goto restore_gid; } } - r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - r = -errno; + r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0 ? -errno : 0; /* If we fail to restore the uid or gid, things will likely fail later on. This should only happen if an LSM interferes. */ From c867611e0a123b81c890c7ee952b2944646d7f91 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 18:59:41 +0200 Subject: [PATCH 06/13] execute: don't pass unit ID in --user mode to journald for stream logging When we create a log stream connection to journald, we pass along the unit ID. With this change we do this only when we run as system instance, not as user instance, to remove the ambiguity whether a user or system unit is specified. The effect of this change is minor: journald ignores the field anyway from clients with UID != 0. This patch hence only fixes the unit attribution for the --user instance of the root user. --- src/core/execute.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/execute.c b/src/core/execute.c index b76b9b9e6f..7481588b08 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -356,8 +356,8 @@ static int connect_logger_as( "%i\n" "%i\n" "%i\n", - context->syslog_identifier ? context->syslog_identifier : ident, - unit->id, + context->syslog_identifier ?: ident, + MANAGER_IS_SYSTEM(unit->manager) ? unit->id : "", context->syslog_priority, !!context->syslog_level_prefix, output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE, From ec6fe7c86ab767e9fe6d9d5338e4716f9688f360 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 19:01:25 +0200 Subject: [PATCH 07/13] journald: add comment explaining journal rate limit return codes This is not obvious, hence let's add a comment. --- src/journal/journald-rate-limit.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c index f48639cf58..a3404222e0 100644 --- a/src/journal/journald-rate-limit.c +++ b/src/journal/journald-rate-limit.c @@ -216,6 +216,13 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u assert(id); + /* Returns: + * + * 0 → the log message shall be suppressed, + * 1 + n → the log message shall be permitted, and n messages were dropped from the peer before + * < 0 → error + */ + if (!r) return 1; From 7a1f1aaa789ae37b548bf55795e1733a21856c85 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Jul 2017 19:03:32 +0200 Subject: [PATCH 08/13] journald: only accept valid unit names for log streams Let's be a bit stricter in what we end up logging: ignore invalid unit name specifications. Let's validate all input! As we ignore unit names passed in from unprivileged clients anyway the effect of this additional check is minimal. (Also, no need to initialize the identifier/unit_id fields of stream objects to NULL if empty strings are passed, the default is NULL anyway...) --- src/journal/journald-stream.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 77551dc14b..ec10d7aedf 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -46,6 +46,7 @@ #include "stdio-util.h" #include "string-util.h" #include "syslog-util.h" +#include "unit-name.h" #define STDOUT_STREAMS_MAX 4096 @@ -295,9 +296,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) { switch (s->state) { case STDOUT_STREAM_IDENTIFIER: - if (isempty(p)) - s->identifier = NULL; - else { + if (!isempty(p)) { s->identifier = strdup(p); if (!s->identifier) return log_oom(); @@ -307,14 +306,12 @@ static int stdout_stream_line(StdoutStream *s, char *p) { return 0; case STDOUT_STREAM_UNIT_ID: - if (s->ucred.uid == 0) { - if (isempty(p)) - s->unit_id = NULL; - else { - s->unit_id = strdup(p); - if (!s->unit_id) - return log_oom(); - } + if (s->ucred.uid == 0 && + unit_name_is_valid(p, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { + + s->unit_id = strdup(p); + if (!s->unit_id) + return log_oom(); } s->state = STDOUT_STREAM_PRIORITY; From 6f8cbcdb27d772521ba71f92c25fd522efd56cf4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 17 Jul 2017 23:35:25 +0200 Subject: [PATCH 09/13] process-util: slightly optimize querying of our own process metadata When we are checking our own data, we can optimize things a bit. --- src/basic/process-util.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index ce6a59c8a4..f5bd6c9487 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -388,7 +388,7 @@ int is_kernel_thread(pid_t pid) { bool eof; FILE *f; - if (pid == 0 || pid == 1) /* pid 1, and we ourselves certainly aren't a kernel thread */ + if (pid == 0 || pid == 1 || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */ return 0; assert(pid > 1); @@ -471,6 +471,9 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) { assert(field); assert(uid); + if (pid < 0) + return -EINVAL; + p = procfs_file_alloca(pid, "status"); f = fopen(p, "re"); if (!f) { @@ -498,10 +501,22 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) { } int get_process_uid(pid_t pid, uid_t *uid) { + + if (pid == 0 || pid == getpid_cached()) { + *uid = getuid(); + return 0; + } + return get_process_id(pid, "Uid:", uid); } int get_process_gid(pid_t pid, gid_t *gid) { + + if (pid == 0 || pid == getpid_cached()) { + *gid = getgid(); + return 0; + } + assert_cc(sizeof(uid_t) == sizeof(gid_t)); return get_process_id(pid, "Gid:", gid); } @@ -577,7 +592,7 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) { assert(pid >= 0); assert(_ppid); - if (pid == 0) { + if (pid == 0 || pid == getpid_cached()) { *_ppid = getppid(); return 0; } @@ -775,6 +790,9 @@ bool pid_is_unwaited(pid_t pid) { if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ return true; + if (pid == getpid_cached()) + return true; + if (kill(pid, 0) >= 0) return true; @@ -792,6 +810,9 @@ bool pid_is_alive(pid_t pid) { if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ return true; + if (pid == getpid_cached()) + return true; + r = get_process_state(pid); if (r == -ESRCH || r == 'Z') return false; @@ -803,7 +824,10 @@ int pid_from_same_root_fs(pid_t pid) { const char *root; if (pid < 0) - return 0; + return false; + + if (pid == 0 || pid == getpid_cached()) + return true; root = procfs_file_alloca(pid, "root"); From 7bf7ce28b5a1f589f0f2382e54c03ab9b0794fab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 20 Jul 2017 11:38:15 +0200 Subject: [PATCH 10/13] string-util: add strlen_ptr() helper strlen_ptr() is to strlen() what streq_ptr() is to streq(): i.e. it handles NULL strings in a smart way. --- src/basic/bus-label.h | 4 +++- src/basic/escape.c | 2 +- src/basic/hexdecoct.c | 3 ++- src/basic/string-util.c | 4 ++-- src/basic/string-util.h | 7 +++++++ src/nss-myhostname/nss-myhostname.c | 2 +- src/systemctl/systemctl.c | 2 +- src/test/test-string-util.c | 7 +++++++ 8 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/basic/bus-label.h b/src/basic/bus-label.h index 62fb2c450c..600268b767 100644 --- a/src/basic/bus-label.h +++ b/src/basic/bus-label.h @@ -23,9 +23,11 @@ #include #include +#include "string-util.h" + char *bus_label_escape(const char *s); char *bus_label_unescape_n(const char *f, size_t l); static inline char *bus_label_unescape(const char *f) { - return bus_label_unescape_n(f, f ? strlen(f) : 0); + return bus_label_unescape_n(f, strlen_ptr(f)); } diff --git a/src/basic/escape.c b/src/basic/escape.c index 85e4b5282e..22b8b04156 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -314,7 +314,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi /* Undoes C style string escaping, and optionally prefixes it. */ - pl = prefix ? strlen(prefix) : 0; + pl = strlen_ptr(prefix); r = new(char, pl+length+1); if (!r) diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index 2d6e377f0a..766770389c 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "hexdecoct.h" #include "macro.h" +#include "string-util.h" #include "util.h" char octchar(int x) { @@ -569,7 +570,7 @@ static int base64_append_width(char **prefix, int plen, lines = (len + width - 1) / width; - slen = sep ? strlen(sep) : 0; + slen = strlen_ptr(sep); t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines); if (!t) return -ENOMEM; diff --git a/src/basic/string-util.c b/src/basic/string-util.c index cd58ef97ef..3287865863 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -215,7 +215,7 @@ char *strnappend(const char *s, const char *suffix, size_t b) { } char *strappend(const char *s, const char *suffix) { - return strnappend(s, suffix, suffix ? strlen(suffix) : 0); + return strnappend(s, suffix, strlen_ptr(suffix)); } char *strjoin_real(const char *x, ...) { @@ -707,7 +707,7 @@ char *strextend(char **x, ...) { assert(x); - l = f = *x ? strlen(*x) : 0; + l = f = strlen_ptr(*x); va_start(ap, x); for (;;) { diff --git a/src/basic/string-util.h b/src/basic/string-util.h index e8a0836538..34eb952ce9 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -200,3 +200,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase); #define _cleanup_string_free_erase_ _cleanup_(string_free_erasep) bool string_is_safe(const char *p) _pure_; + +static inline size_t strlen_ptr(const char *s) { + if (!s) + return 0; + + return strlen(s); +} diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index 0570fde592..869d233d49 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -211,7 +211,7 @@ static enum nss_status fill_in_hostent( c++; l_canonical = strlen(canonical); - l_additional = additional ? strlen(additional) : 0; + l_additional = strlen_ptr(additional); ms = ALIGN(l_canonical+1)+ (additional ? ALIGN(l_additional+1) : 0) + sizeof(char*) + diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 55fce62480..36675dc499 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -2010,7 +2010,7 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n) for (m = machine_infos; m < machine_infos + n; m++) { namelen = MAX(namelen, strlen(m->name) + (m->is_host ? sizeof(" (host)") - 1 : 0)); - statelen = MAX(statelen, m->state ? strlen(m->state) : 0); + statelen = MAX(statelen, strlen_ptr(m->state)); failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units)); jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs)); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 4b3e924cfb..604701ff7a 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -336,6 +336,12 @@ static void test_first_word(void) { assert_se(!first_word("Hellooo", "Hello")); } +static void test_strlen_ptr(void) { + assert_se(strlen_ptr("foo") == 3); + assert_se(strlen_ptr("") == 0); + assert_se(strlen_ptr(NULL) == 0); +} + int main(int argc, char *argv[]) { test_string_erase(); test_ascii_strcasecmp_n(); @@ -358,6 +364,7 @@ int main(int argc, char *argv[]) { test_in_charset(); test_split_pair(); test_first_word(); + test_strlen_ptr(); return 0; } From c165d97d16f24c9c28d9574ed82b3a6669408ca5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 20 Jul 2017 14:14:55 +0200 Subject: [PATCH 11/13] alloc-util: add new helpers memdup_suffix0() and newdup_suffix0() These are similar to memdup() and newdup(), but reserve one extra NUL byte at the end of the new allocation and initialize it. It's useful when copying out data from fixed size character arrays where NUL termination can't be assumed. --- src/basic/alloc-util.c | 27 +++++++++++++++++++++------ src/basic/alloc-util.h | 10 ++++++++++ src/shared/logs-show.c | 7 +++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c index b540dcddf5..948389f276 100644 --- a/src/basic/alloc-util.c +++ b/src/basic/alloc-util.c @@ -25,16 +25,31 @@ #include "util.h" void* memdup(const void *p, size_t l) { - void *r; + void *ret; - assert(p); + assert(l == 0 || p); - r = malloc(l); - if (!r) + ret = malloc(l); + if (!ret) return NULL; - memcpy(r, p, l); - return r; + memcpy(ret, p, l); + return ret; +} + +void* memdup_suffix0(const void*p, size_t l) { + void *ret; + + assert(l == 0 || p); + + /* The same as memdup() but place a safety NUL byte after the allocated memory */ + + ret = malloc(l + 1); + if (!ret) + return NULL; + + *((uint8_t*) mempcpy(ret, p, l)) = 0; + return ret; } void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h index a44dd473c1..0a89691bae 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -36,6 +36,8 @@ #define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) +#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n))) + #define malloc0(n) (calloc(1, (n))) static inline void *mfree(void *memory) { @@ -52,6 +54,7 @@ static inline void *mfree(void *memory) { }) void* memdup(const void *p, size_t l) _alloc_(2); +void* memdup_suffix0(const void*p, size_t l) _alloc_(2); static inline void freep(void *p) { free(*(void**) p); @@ -84,6 +87,13 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si return memdup(p, size * need); } +_alloc_(2, 3) static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) { + if (size_multiply_overflow(size, need)) + return NULL; + + return memdup_suffix0(p, size * need); +} + void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 02ae4265c6..54516cfb96 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -95,13 +95,12 @@ static int parse_field(const void *data, size_t length, const char *field, char return 0; nl = length - fl; - buf = new(char, nl+1); + + + buf = newdup_suffix0(char, (const char*) data + fl, nl); if (!buf) return log_oom(); - memcpy(buf, (const char*) data + fl, nl); - buf[nl] = 0; - free(*target); *target = buf; From 47b33c7d5284492e5679ccfabafe8c9738de8cb3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 20 Jul 2017 14:17:30 +0200 Subject: [PATCH 12/13] string-util: optimize strshorten() a bit There's no reason to determine the full length of the string, it's sufficient to know whether it is larger than the intended size... --- src/basic/string-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 3287865863..5808b1280e 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -558,7 +558,7 @@ bool nulstr_contains(const char *nulstr, const char *needle) { char* strshorten(char *s, size_t l) { assert(s); - if (l < strlen(s)) + if (strnlen(s, l+1) > l) s[l] = 0; return s; From 22e3a02b9d618bbebcf987bc1411acda367271ec Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 17 Jul 2017 23:36:35 +0200 Subject: [PATCH 13/13] journald: add minimal client metadata caching Cache client metadata, in order to be improve runtime behaviour under pressure. This is inspired by @vcaputo's work, specifically: https://github.com/systemd/systemd/pull/2280 That code implements related but different semantics. For a longer explanation what this change implements please have a look at the long source comment this patch adds to journald-context.c. After this commit: # time bash -c 'dd bs=$((1024*1024)) count=$((1*1024)) if=/dev/urandom | systemd-cat' 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 11.2783 s, 95.2 MB/s real 0m11.283s user 0m0.007s sys 0m6.216s Before this commit: # time bash -c 'dd bs=$((1024*1024)) count=$((1*1024)) if=/dev/urandom | systemd-cat' 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 52.0788 s, 20.6 MB/s real 0m52.099s user 0m0.014s sys 0m7.170s As side effect, this corrects the journal's rate limiter feature: we now always use the unit name as key for the ratelimiter. --- src/journal/journald-audit.c | 2 +- src/journal/journald-context.c | 588 +++++++++++++++++++++++++++++++++ src/journal/journald-context.h | 92 ++++++ src/journal/journald-kmsg.c | 2 +- src/journal/journald-native.c | 15 +- src/journal/journald-server.c | 448 ++++++------------------- src/journal/journald-server.h | 11 +- src/journal/journald-stream.c | 21 +- src/journal/journald-syslog.c | 15 +- src/journal/meson.build | 30 +- 10 files changed, 852 insertions(+), 372 deletions(-) create mode 100644 src/journal/journald-context.c create mode 100644 src/journal/journald-context.h diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c index a433c91c54..38ac3befdd 100644 --- a/src/journal/journald-audit.c +++ b/src/journal/journald-audit.c @@ -413,7 +413,7 @@ static void process_audit_string(Server *s, int type, const char *data, size_t s goto finish; } - server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, NULL, 0, NULL, LOG_NOTICE, 0); + server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, LOG_NOTICE, 0); finish: /* free() all entries that map_all_fields() added. All others diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c new file mode 100644 index 0000000000..10e9615f23 --- /dev/null +++ b/src/journal/journald-context.c @@ -0,0 +1,588 @@ +/*** + This file is part of systemd. + + Copyright 2017 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#ifdef HAVE_SELINUX +#include +#endif + +#include "alloc-util.h" +#include "audit-util.h" +#include "cgroup-util.h" +#include "journald-context.h" +#include "process-util.h" +#include "string-util.h" +#include "user-util.h" + +/* This implements a metadata cache for clients, which are identified by their PID. Requesting metadata through /proc + * is expensive, hence let's cache the data if we can. Note that this means the metadata might be out-of-date when we + * store it, but it might already be anyway, as we request the data asynchronously from /proc at a different time the + * log entry was originally created. We hence just increase the "window of inaccuracy" a bit. + * + * The cache is indexed by the PID. Entries may be "pinned" in the cache, in which case the entries are not removed + * until they are unpinned. Unpinned entries are kept around until cache pressure is seen. Cache entries older than 5s + * are never used (a sad attempt to deal with the UNIX weakness of PIDs reuse), cache entries older than 1s are + * refreshed in an incremental way (meaning: data is reread from /proc, but any old data we can't refresh is not + * flushed out). Data newer than 1s is used immediately without refresh. + * + * Log stream clients (i.e. all clients using the AF_UNIX/SOCK_STREAM stdout/stderr transport) will pin a cache entry + * as long as their socket is connected. Note that cache entries are shared between different transports. That means a + * cache entry pinned for the stream connection logic may be reused for the syslog or native protocols. + * + * Caching metadata like this has two major benefits: + * + * 1. Reading metadata is expensive, and we can thus substantially speed up log processing under flood. + * + * 2. Because metadata caching is shared between stream and datagram transports and stream connections pin a cache + * entry there's a good chance we can properly map a substantial set of datagram log messages to their originating + * service, as all services (unless explicitly configured otherwise) will have their stdout/stderr connected to a + * stream connection. This should improve cases where a service process logs immediately before exiting and we + * previously had trouble associating the log message with the service. + * + * NB: With and without the metadata cache: the implicitly added entry metadata in the journal (with the exception of + * UID/PID/GID and SELinux label) must be understood as possibly slightly out of sync (i.e. sometimes slighly older + * and sometimes slightly newer than what was current at the log event). + */ + +/* We refresh every 1s */ +#define REFRESH_USEC (1*USEC_PER_SEC) + +/* Data older than 5s we flush out */ +#define MAX_USEC (5*USEC_PER_SEC) + +/* Keep at most 16K entries in the cache. (Note though that this limit may be violated if enough streams pin entries in + * the cache, in which case we *do* permit this limit to be breached. That's safe however, as the number of stream + * clients itself is limited.) */ +#define CACHE_MAX (16*1024) + +static int client_context_compare(const void *a, const void *b) { + const ClientContext *x = a, *y = b; + + if (x->timestamp < y->timestamp) + return -1; + if (x->timestamp > y->timestamp) + return 1; + + if (x->pid < y->pid) + return -1; + if (x->pid > y->pid) + return 1; + + return 0; +} + +static int client_context_new(Server *s, pid_t pid, ClientContext **ret) { + ClientContext *c; + int r; + + assert(s); + assert(pid_is_valid(pid)); + assert(ret); + + r = hashmap_ensure_allocated(&s->client_contexts, NULL); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&s->client_contexts_lru, client_context_compare); + if (r < 0) + return r; + + c = new0(ClientContext, 1); + if (!c) + return -ENOMEM; + + c->pid = pid; + + c->uid = UID_INVALID; + c->gid = GID_INVALID; + c->auditid = AUDIT_SESSION_INVALID; + c->loginuid = UID_INVALID; + c->owner_uid = UID_INVALID; + c->lru_index = PRIOQ_IDX_NULL; + c->timestamp = USEC_INFINITY; + + r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c); + if (r < 0) { + free(c); + return r; + } + + *ret = c; + return 0; +} + +static void client_context_reset(ClientContext *c) { + assert(c); + + c->timestamp = USEC_INFINITY; + + c->uid = UID_INVALID; + c->gid = GID_INVALID; + + 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; + + c->cgroup = mfree(c->cgroup); + c->session = mfree(c->session); + c->owner_uid = UID_INVALID; + c->unit = mfree(c->unit); + c->user_unit = mfree(c->user_unit); + c->slice = mfree(c->slice); + c->user_slice = mfree(c->user_slice); + + c->invocation_id = SD_ID128_NULL; + + c->label = mfree(c->label); + c->label_size = 0; +} + +static ClientContext* client_context_free(Server *s, ClientContext *c) { + assert(s); + + if (!c) + return NULL; + + assert_se(hashmap_remove(s->client_contexts, PID_TO_PTR(c->pid)) == c); + + if (c->in_lru) + assert_se(prioq_remove(s->client_contexts_lru, c, &c->lru_index) >= 0); + + client_context_reset(c); + + return mfree(c); +} + +static void client_context_read_uid_gid(ClientContext *c, const struct ucred *ucred) { + assert(c); + assert(pid_is_valid(c->pid)); + + /* The ucred data passed in is always the most current and accurate, if we have any. Use it. */ + if (ucred && uid_is_valid(ucred->uid)) + c->uid = ucred->uid; + else + (void) get_process_uid(c->pid, &c->uid); + + if (ucred && gid_is_valid(ucred->gid)) + c->gid = ucred->gid; + else + (void) get_process_gid(c->pid, &c->gid); +} + +static void client_context_read_basic(ClientContext *c) { + char *t; + + assert(c); + assert(pid_is_valid(c->pid)); + + if (get_process_comm(c->pid, &t) >= 0) + free_and_replace(c->comm, t); + + if (get_process_exe(c->pid, &t) >= 0) + free_and_replace(c->exe, t); + + if (get_process_cmdline(c->pid, 0, false, &t) >= 0) + free_and_replace(c->cmdline, t); + + if (get_process_capeff(c->pid, &t) >= 0) + free_and_replace(c->capeff, t); +} + +static int client_context_read_label( + ClientContext *c, + const char *label, size_t label_size) { + + assert(c); + assert(pid_is_valid(c->pid)); + assert(label_size == 0 || label); + + if (label_size > 0) { + char *l; + + /* If we got an SELinux label passed in it counts. */ + + l = newdup_suffix0(char, label, label_size); + if (!l) + return -ENOMEM; + + free_and_replace(c->label, l); + c->label_size = label_size; + } +#ifdef HAVE_SELINUX + else { + char *con; + + /* If we got no SELinux label passed in, let's try to acquire one */ + + if (getpidcon(c->pid, &con) >= 0) { + free_and_replace(c->label, con); + c->label_size = strlen(c->label); + } + } +#endif + + return 0; +} + +static int client_context_read_cgroup(Server *s, ClientContext *c, const char *unit_id) { + char *t = NULL; + int r; + + assert(c); + + /* Try to acquire the current cgroup path */ + r = cg_pid_get_path_shifted(c->pid, s->cgroup_root, &t); + if (r < 0) { + + /* If that didn't work, we use the unit ID passed in as fallback, if we have nothing cached yet */ + if (unit_id && !c->unit) { + c->unit = strdup(unit_id); + if (c->unit) + return 0; + } + + return r; + } + + /* Let's shortcut this if the cgroup path didn't change */ + if (streq_ptr(c->cgroup, t)) { + free(t); + return 0; + } + + free_and_replace(c->cgroup, t); + + (void) cg_path_get_session(c->cgroup, &t); + free_and_replace(c->session, t); + + if (cg_path_get_owner_uid(c->cgroup, &c->owner_uid) < 0) + c->owner_uid = UID_INVALID; + + (void) cg_path_get_unit(c->cgroup, &t); + free_and_replace(c->unit, t); + + (void) cg_path_get_user_unit(c->cgroup, &t); + free_and_replace(c->user_unit, t); + + (void) cg_path_get_slice(c->cgroup, &t); + free_and_replace(c->slice, t); + + (void) cg_path_get_user_slice(c->cgroup, &t); + free_and_replace(c->user_slice, t); + + return 0; +} + +static int client_context_read_invocation_id( + Server *s, + ClientContext *c) { + + _cleanup_free_ char *escaped = NULL, *slice_path = NULL; + char ids[SD_ID128_STRING_MAX]; + const char *p; + int r; + + assert(s); + assert(c); + + /* Read the invocation ID of a unit off a unit. It's stored in the "trusted.invocation_id" extended attribute + * on the cgroup path. */ + + if (!c->unit || !c->slice) + return 0; + + r = cg_slice_to_path(c->slice, &slice_path); + if (r < 0) + return r; + + escaped = cg_escape(c->unit); + if (!escaped) + return -ENOMEM; + + p = strjoina(s->cgroup_root, "/", slice_path, "/", escaped); + if (!p) + return -ENOMEM; + + r = cg_get_xattr(SYSTEMD_CGROUP_CONTROLLER, p, "trusted.invocation_id", ids, 32); + if (r < 0) + return r; + if (r != 32) + return -EINVAL; + ids[32] = 0; + + return sd_id128_from_string(ids, &c->invocation_id); +} + +static void client_context_really_refresh( + Server *s, + ClientContext *c, + const struct ucred *ucred, + const char *label, size_t label_size, + const char *unit_id, + usec_t timestamp) { + + assert(s); + assert(c); + assert(pid_is_valid(c->pid)); + + if (timestamp == USEC_INFINITY) + timestamp = now(CLOCK_MONOTONIC); + + client_context_read_uid_gid(c, ucred); + client_context_read_basic(c); + (void) client_context_read_label(c, label, label_size); + + (void) audit_session_from_pid(c->pid, &c->auditid); + (void) audit_loginuid_from_pid(c->pid, &c->loginuid); + + (void) client_context_read_cgroup(s, c, unit_id); + (void) client_context_read_invocation_id(s, c); + + c->timestamp = timestamp; + + if (c->in_lru) { + assert(c->n_ref == 0); + assert_se(prioq_reshuffle(s->client_contexts_lru, c, &c->lru_index) >= 0); + } +} + +void client_context_maybe_refresh( + Server *s, + ClientContext *c, + const struct ucred *ucred, + const char *label, size_t label_size, + const char *unit_id, + usec_t timestamp) { + + assert(s); + assert(c); + + if (timestamp == USEC_INFINITY) + timestamp = now(CLOCK_MONOTONIC); + + /* No cached data so far? Let's fill it up */ + if (c->timestamp == USEC_INFINITY) + goto refresh; + + /* If the data isn't pinned and if the cashed data is older than the upper limit, we flush it out + * entirely. This follows the logic that as long as an entry is pinned the PID reuse is unlikely. */ + if (c->n_ref == 0 && c->timestamp + MAX_USEC < timestamp) { + client_context_reset(c); + goto refresh; + } + + /* If the data is older than the lower limit, we refresh, but keep the old data for all we can't update */ + if (c->timestamp + REFRESH_USEC < timestamp) + goto refresh; + + /* If the data passed along doesn't match the cached data we also do a refresh */ + if (ucred && uid_is_valid(ucred->uid) && c->uid != ucred->uid) + goto refresh; + + if (ucred && gid_is_valid(ucred->gid) && c->gid != ucred->gid) + goto refresh; + + if (label_size > 0 && (label_size != c->label_size || memcmp(label, c->label, label_size) != 0)) + goto refresh; + + return; + +refresh: + client_context_really_refresh(s, c, ucred, label, label_size, unit_id, timestamp); +} + +static void client_context_try_shrink_to(Server *s, size_t limit) { + assert(s); + + /* Bring the number of cache entries below the indicated limit, so that we can create a new entry without + * breaching the limit. Note that we only flush out entries that aren't pinned here. This means the number of + * cache entries may very well grow beyond the limit, if all entries stored remain pinned. */ + + while (hashmap_size(s->client_contexts) > limit) { + ClientContext *c; + + c = prioq_pop(s->client_contexts_lru); + if (!c) + break; /* All remaining entries are pinned, give up */ + + assert(c->in_lru); + assert(c->n_ref == 0); + + c->in_lru = false; + + client_context_free(s, c); + } +} + +void client_context_flush_all(Server *s) { + assert(s); + + /* Flush out all remaining entries. This assumes all references are already dropped. */ + + s->my_context = client_context_release(s, s->my_context); + s->pid1_context = client_context_release(s, s->pid1_context); + + client_context_try_shrink_to(s, 0); + + assert(prioq_size(s->client_contexts_lru) == 0); + assert(hashmap_size(s->client_contexts) == 0); + + s->client_contexts_lru = prioq_free(s->client_contexts_lru); + s->client_contexts = hashmap_free(s->client_contexts); +} + +static int client_context_get_internal( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + bool add_ref, + ClientContext **ret) { + + ClientContext *c; + int r; + + assert(s); + assert(ret); + + if (!pid_is_valid(pid)) + return -EINVAL; + + c = hashmap_get(s->client_contexts, PID_TO_PTR(pid)); + if (c) { + + if (add_ref) { + if (c->in_lru) { + /* The entry wasn't pinned so far, let's remove it from the LRU list then */ + assert(c->n_ref == 0); + assert_se(prioq_remove(s->client_contexts_lru, c, &c->lru_index) >= 0); + c->in_lru = false; + } + + c->n_ref++; + } + + client_context_maybe_refresh(s, c, ucred, label, label_len, unit_id, USEC_INFINITY); + + *ret = c; + return 0; + } + + client_context_try_shrink_to(s, CACHE_MAX-1); + + r = client_context_new(s, pid, &c); + if (r < 0) + return r; + + if (add_ref) + c->n_ref++; + else { + r = prioq_put(s->client_contexts_lru, c, &c->lru_index); + if (r < 0) { + client_context_free(s, c); + return r; + } + + c->in_lru = true; + } + + client_context_really_refresh(s, c, ucred, label, label_len, unit_id, USEC_INFINITY); + + *ret = c; + return 0; +} + +int client_context_get( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret) { + + return client_context_get_internal(s, pid, ucred, label, label_len, unit_id, false, ret); +} + +int client_context_acquire( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret) { + + return client_context_get_internal(s, pid, ucred, label, label_len, unit_id, true, ret); +}; + +ClientContext *client_context_release(Server *s, ClientContext *c) { + assert(s); + + if (!c) + return NULL; + + assert(c->n_ref > 0); + assert(!c->in_lru); + + c->n_ref--; + if (c->n_ref > 0) + return NULL; + + /* The entry is not pinned anymore, let's add it to the LRU prioq if we can. If we can't we'll drop it + * right-away */ + + if (prioq_put(s->client_contexts_lru, c, &c->lru_index) < 0) + client_context_free(s, c); + else + c->in_lru = true; + + return NULL; +} + +void client_context_acquire_default(Server *s) { + int r; + + assert(s); + + /* Ensure that our own and PID1's contexts are always pinned. Our own context is particularly useful to + * generate driver messages. */ + + if (!s->my_context) { + struct ucred ucred = { + .pid = getpid_cached(), + .uid = getuid(), + .gid = getgid(), + }; + + r = client_context_acquire(s, ucred.pid, &ucred, NULL, 0, NULL, &s->my_context); + if (r < 0) + log_warning_errno(r, "Failed to acquire our own context, ignoring: %m"); + } + + if (!s->pid1_context) { + + r = client_context_acquire(s, 1, NULL, NULL, 0, NULL, &s->pid1_context); + if (r < 0) + log_warning_errno(r, "Failed to acquire PID1's context, ignoring: %m"); + + } +} diff --git a/src/journal/journald-context.h b/src/journal/journald-context.h new file mode 100644 index 0000000000..eb1e21926a --- /dev/null +++ b/src/journal/journald-context.h @@ -0,0 +1,92 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2017 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "sd-id128.h" + +typedef struct ClientContext ClientContext; + +#include "journald-server.h" + +struct ClientContext { + unsigned n_ref; + unsigned lru_index; + usec_t timestamp; + bool in_lru; + + pid_t pid; + uid_t uid; + gid_t gid; + + char *comm; + char *exe; + char *cmdline; + char *capeff; + + uint32_t auditid; + uid_t loginuid; + + char *cgroup; + char *session; + uid_t owner_uid; + + char *unit; + char *user_unit; + + char *slice; + char *user_slice; + + sd_id128_t invocation_id; + + char *label; + size_t label_size; +}; + +int client_context_get( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret); + +int client_context_acquire( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret); + +ClientContext* client_context_release(Server *s, ClientContext *c); + +void client_context_maybe_refresh( + Server *s, + ClientContext *c, + const struct ucred *ucred, + const char *label, size_t label_size, + const char *unit_id, + usec_t tstamp); + +void client_context_acquire_default(Server *s); +void client_context_flush_all(Server *s); diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index 7fea85a5d8..2be82be5f6 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -310,7 +310,7 @@ static void dev_kmsg_record(Server *s, const char *p, size_t l) { if (cunescape_length_with_prefix(p, pl, "MESSAGE=", UNESCAPE_RELAX, &message) >= 0) IOVEC_SET_STRING(iovec[n++], message); - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority, 0); + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, priority, 0); finish: for (j = 0; j < z; j++) diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index abd06b1adc..23afe59bd5 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -37,6 +37,7 @@ #include "memfd-util.h" #include "parse-util.h" #include "path-util.h" +#include "process-util.h" #include "selinux-util.h" #include "socket-util.h" #include "string-util.h" @@ -142,6 +143,7 @@ static void server_process_entry_meta( static int server_process_entry( Server *s, const void *buffer, size_t *remaining, + ClientContext *context, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len) { @@ -303,7 +305,7 @@ static int server_process_entry( server_forward_wall(s, priority, identifier, message, ucred); } - server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); + server_dispatch_message(s, iovec, n, m, context, tv, priority, object_pid); finish: for (j = 0; j < n; j++) { @@ -329,16 +331,23 @@ void server_process_native_message( const struct timeval *tv, const char *label, size_t label_len) { - int r; size_t remaining = buffer_size; + ClientContext *context; + int r; assert(s); assert(buffer || buffer_size == 0); + if (ucred && pid_is_valid(ucred->pid)) { + r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context); + if (r < 0) + log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid); + } + do { r = server_process_entry(s, (const uint8_t*) buffer + (buffer_size - remaining), &remaining, - ucred, tv, label, label_len); + context, ucred, tv, label, label_len); } while (r == 0); } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index f391845ba9..feef10c2db 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -51,6 +51,7 @@ #include "journal-internal.h" #include "journal-vacuum.h" #include "journald-audit.h" +#include "journald-context.h" #include "journald-kmsg.h" #include "journald-native.h" #include "journald-rate-limit.h" @@ -70,8 +71,8 @@ #include "stdio-util.h" #include "string-table.h" #include "string-util.h" -#include "user-util.h" #include "syslog-util.h" +#include "user-util.h" #define USER_JOURNALS_MAX 1024 @@ -714,323 +715,109 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned server_schedule_sync(s, priority); } -static int get_invocation_id(const char *cgroup_root, const char *slice, const char *unit, char **ret) { - _cleanup_free_ char *escaped = NULL, *slice_path = NULL, *p = NULL; - char *copy, ids[SD_ID128_STRING_MAX]; - int r; +#define IOVEC_ADD_NUMERIC_FIELD(iovec, n, value, type, isset, format, field) \ + if (isset(value)) { \ + char *k; \ + k = newa(char, strlen(field "=") + DECIMAL_STR_MAX(type) + 1); \ + sprintf(k, field "=" format, value); \ + IOVEC_SET_STRING(iovec[n++], k); \ + } - /* Read the invocation ID of a unit off a unit. It's stored in the "trusted.invocation_id" extended attribute - * on the cgroup path. */ +#define IOVEC_ADD_STRING_FIELD(iovec, n, value, field) \ + if (!isempty(value)) { \ + char *k; \ + k = strjoina(field "=", value); \ + IOVEC_SET_STRING(iovec[n++], k); \ + } - r = cg_slice_to_path(slice, &slice_path); - if (r < 0) - return r; +#define IOVEC_ADD_ID128_FIELD(iovec, n, value, field) \ + if (!sd_id128_is_null(value)) { \ + char *k; \ + k = newa(char, strlen(field "=") + SD_ID128_STRING_MAX); \ + sd_id128_to_string(value, stpcpy(k, field "=")); \ + IOVEC_SET_STRING(iovec[n++], k); \ + } - escaped = cg_escape(unit); - if (!escaped) - return -ENOMEM; - - p = strjoin(cgroup_root, "/", slice_path, "/", escaped); - if (!p) - return -ENOMEM; - - r = cg_get_xattr(SYSTEMD_CGROUP_CONTROLLER, p, "trusted.invocation_id", ids, 32); - if (r < 0) - return r; - if (r != 32) - return -EINVAL; - ids[32] = 0; - - if (!id128_is_valid(ids)) - return -EINVAL; - - copy = strdup(ids); - if (!copy) - return -ENOMEM; - - *ret = copy; - return 0; -} +#define IOVEC_ADD_SIZED_FIELD(iovec, n, value, value_size, field) \ + if (value_size > 0) { \ + char *k; \ + k = newa(char, strlen(field "=") + value_size + 1); \ + *((char*) mempcpy(stpcpy(k, field "="), value, value_size)) = 0; \ + IOVEC_SET_STRING(iovec[n++], k); \ + } \ static void dispatch_message_real( Server *s, struct iovec *iovec, unsigned n, unsigned m, - const struct ucred *ucred, + const ClientContext *c, const struct timeval *tv, - const char *label, size_t label_len, - const char *unit_id, int priority, - pid_t object_pid, - char *cgroup) { + pid_t object_pid) { - char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)], - uid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)], - gid[sizeof("_GID=") + DECIMAL_STR_MAX(gid_t)], - owner_uid[sizeof("_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)], - source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)], - o_uid[sizeof("OBJECT_UID=") + DECIMAL_STR_MAX(uid_t)], - o_gid[sizeof("OBJECT_GID=") + DECIMAL_STR_MAX(gid_t)], - o_owner_uid[sizeof("OBJECT_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)]; - uid_t object_uid; - gid_t object_gid; - char *x; - int r; - char *t, *c; - uid_t realuid = 0, owner = 0, journal_uid; - bool owner_valid = false; -#ifdef HAVE_AUDIT - char audit_session[sizeof("_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], - audit_loginuid[sizeof("_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)], - o_audit_session[sizeof("OBJECT_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], - o_audit_loginuid[sizeof("OBJECT_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)]; - - uint32_t audit; - uid_t loginuid; -#endif + char source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)]; + uid_t journal_uid; + ClientContext *o; assert(s); assert(iovec); assert(n > 0); - assert(n + N_IOVEC_META_FIELDS + (object_pid > 0 ? N_IOVEC_OBJECT_FIELDS : 0) <= m); + assert(n + N_IOVEC_META_FIELDS + (pid_is_valid(object_pid) ? N_IOVEC_OBJECT_FIELDS : 0) <= m); - if (ucred) { - realuid = ucred->uid; + if (c) { + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->pid, pid_t, pid_is_valid, PID_FMT, "_PID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->uid, uid_t, uid_is_valid, UID_FMT, "_UID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->gid, gid_t, gid_is_valid, GID_FMT, "_GID"); - sprintf(pid, "_PID="PID_FMT, ucred->pid); - IOVEC_SET_STRING(iovec[n++], pid); + IOVEC_ADD_STRING_FIELD(iovec, n, c->comm, "_COMM"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->exe, "_EXE"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->cmdline, "_CMDLINE"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->capeff, "_CAP_EFFECTIVE"); - sprintf(uid, "_UID="UID_FMT, ucred->uid); - IOVEC_SET_STRING(iovec[n++], uid); + IOVEC_ADD_SIZED_FIELD(iovec, n, c->label, c->label_size, "_SELINUX_CONTEXT"); - sprintf(gid, "_GID="GID_FMT, ucred->gid); - IOVEC_SET_STRING(iovec[n++], gid); + 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"); - r = get_process_comm(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_COMM=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_STRING_FIELD(iovec, n, c->cgroup, "_SYSTEMD_CGROUP"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->session, "_SYSTEMD_SESSION"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->owner_uid, uid_t, uid_is_valid, UID_FMT, "_SYSTEMD_OWNER_UID"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->unit, "_SYSTEMD_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->user_unit, "_SYSTEMD_USER_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->slice, "_SYSTEMD_SLICE"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->user_slice, "_SYSTEMD_USER_SLICE"); - r = get_process_exe(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_EXE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_cmdline(ucred->pid, 0, false, &t); - if (r >= 0) { - x = strjoina("_CMDLINE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_capeff(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_CAP_EFFECTIVE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - -#ifdef HAVE_AUDIT - r = audit_session_from_pid(ucred->pid, &audit); - if (r >= 0) { - sprintf(audit_session, "_AUDIT_SESSION=%"PRIu32, audit); - IOVEC_SET_STRING(iovec[n++], audit_session); - } - - r = audit_loginuid_from_pid(ucred->pid, &loginuid); - if (r >= 0) { - sprintf(audit_loginuid, "_AUDIT_LOGINUID="UID_FMT, loginuid); - IOVEC_SET_STRING(iovec[n++], audit_loginuid); - } -#endif - - r = 0; - if (cgroup) - c = cgroup; - else - r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c); - - if (r >= 0) { - _cleanup_free_ char *raw_unit = NULL, *raw_slice = NULL; - char *session = NULL; - - x = strjoina("_SYSTEMD_CGROUP=", c); - IOVEC_SET_STRING(iovec[n++], x); - - r = cg_path_get_session(c, &t); - if (r >= 0) { - session = strjoina("_SYSTEMD_SESSION=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], session); - } - - if (cg_path_get_owner_uid(c, &owner) >= 0) { - owner_valid = true; - - sprintf(owner_uid, "_SYSTEMD_OWNER_UID="UID_FMT, owner); - IOVEC_SET_STRING(iovec[n++], owner_uid); - } - - if (cg_path_get_unit(c, &raw_unit) >= 0) { - x = strjoina("_SYSTEMD_UNIT=", raw_unit); - IOVEC_SET_STRING(iovec[n++], x); - } else if (unit_id && !session) { - x = strjoina("_SYSTEMD_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_user_unit(c, &t) >= 0) { - x = strjoina("_SYSTEMD_USER_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } else if (unit_id && session) { - x = strjoina("_SYSTEMD_USER_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_slice(c, &raw_slice) >= 0) { - x = strjoina("_SYSTEMD_SLICE=", raw_slice); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_user_slice(c, &t) >= 0) { - x = strjoina("_SYSTEMD_USER_SLICE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (raw_slice && raw_unit) { - if (get_invocation_id(s->cgroup_root, raw_slice, raw_unit, &t) >= 0) { - x = strjoina("_SYSTEMD_INVOCATION_ID=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - } - - if (!cgroup) - free(c); - } else if (unit_id) { - x = strjoina("_SYSTEMD_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } - -#ifdef HAVE_SELINUX - if (mac_selinux_use()) { - if (label) { - x = alloca(strlen("_SELINUX_CONTEXT=") + label_len + 1); - - *((char*) mempcpy(stpcpy(x, "_SELINUX_CONTEXT="), label, label_len)) = 0; - IOVEC_SET_STRING(iovec[n++], x); - } else { - char *con; - - if (getpidcon(ucred->pid, &con) >= 0) { - x = strjoina("_SELINUX_CONTEXT=", con); - - freecon(con); - IOVEC_SET_STRING(iovec[n++], x); - } - } - } -#endif + IOVEC_ADD_ID128_FIELD(iovec, n, c->invocation_id, "_SYSTEMD_INVOCATION_ID"); } + assert(n <= m); - if (object_pid) { - r = get_process_uid(object_pid, &object_uid); - if (r >= 0) { - sprintf(o_uid, "OBJECT_UID="UID_FMT, object_uid); - IOVEC_SET_STRING(iovec[n++], o_uid); - } + if (pid_is_valid(object_pid) && client_context_get(s, object_pid, NULL, NULL, 0, NULL, &o) >= 0) { - r = get_process_gid(object_pid, &object_gid); - if (r >= 0) { - sprintf(o_gid, "OBJECT_GID="GID_FMT, object_gid); - IOVEC_SET_STRING(iovec[n++], o_gid); - } + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->pid, pid_t, pid_is_valid, PID_FMT, "OBJECT_PID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->uid, uid_t, uid_is_valid, UID_FMT, "OBJECT_UID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->gid, gid_t, gid_is_valid, GID_FMT, "OBJECT_GID"); - r = get_process_comm(object_pid, &t); - if (r >= 0) { - x = strjoina("OBJECT_COMM=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_STRING_FIELD(iovec, n, o->comm, "OBJECT_COMM"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->exe, "OBJECT_EXE"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->cmdline, "OBJECT_CMDLINE"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->capeff, "OBJECT_CAP_EFFECTIVE"); - r = get_process_exe(object_pid, &t); - if (r >= 0) { - x = strjoina("OBJECT_EXE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_SIZED_FIELD(iovec, n, o->label, o->label_size, "OBJECT_SELINUX_CONTEXT"); - r = get_process_cmdline(object_pid, 0, false, &t); - if (r >= 0) { - x = strjoina("OBJECT_CMDLINE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + 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"); -#ifdef HAVE_AUDIT - r = audit_session_from_pid(object_pid, &audit); - if (r >= 0) { - sprintf(o_audit_session, "OBJECT_AUDIT_SESSION=%"PRIu32, audit); - IOVEC_SET_STRING(iovec[n++], o_audit_session); - } + IOVEC_ADD_STRING_FIELD(iovec, n, o->cgroup, "OBJECT_SYSTEMD_CGROUP"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->session, "OBJECT_SYSTEMD_SESSION"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->owner_uid, uid_t, uid_is_valid, UID_FMT, "OBJECT_SYSTEMD_OWNER_UID"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->unit, "OBJECT_SYSTEMD_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->user_unit, "OBJECT_SYSTEMD_USER_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->slice, "OBJECT_SYSTEMD_SLICE"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->user_slice, "OBJECT_SYSTEMD_USER_SLICE"); - r = audit_loginuid_from_pid(object_pid, &loginuid); - if (r >= 0) { - sprintf(o_audit_loginuid, "OBJECT_AUDIT_LOGINUID="UID_FMT, loginuid); - IOVEC_SET_STRING(iovec[n++], o_audit_loginuid); - } -#endif - - r = cg_pid_get_path_shifted(object_pid, s->cgroup_root, &c); - if (r >= 0) { - x = strjoina("OBJECT_SYSTEMD_CGROUP=", c); - IOVEC_SET_STRING(iovec[n++], x); - - r = cg_path_get_session(c, &t); - if (r >= 0) { - x = strjoina("OBJECT_SYSTEMD_SESSION=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_owner_uid(c, &owner) >= 0) { - sprintf(o_owner_uid, "OBJECT_SYSTEMD_OWNER_UID="UID_FMT, owner); - IOVEC_SET_STRING(iovec[n++], o_owner_uid); - } - - if (cg_path_get_unit(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_user_unit(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_USER_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_slice(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_SLICE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_user_slice(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_USER_SLICE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - free(c); - } + IOVEC_ADD_ID128_FIELD(iovec, n, o->invocation_id, "OBJECT_SYSTEMD_INVOCATION_ID="); } + assert(n <= m); if (tv) { @@ -1052,16 +839,16 @@ static void dispatch_message_real( assert(n <= m); - if (s->split_mode == SPLIT_UID && realuid > 0) - /* Split up strictly by any UID */ - journal_uid = realuid; - else if (s->split_mode == SPLIT_LOGIN && realuid > 0 && owner_valid && owner > 0) + if (s->split_mode == SPLIT_UID && c && uid_is_valid(c->uid)) + /* Split up strictly by (non-root) UID */ + journal_uid = c->uid; + else if (s->split_mode == SPLIT_LOGIN && c && c->uid > 0 && uid_is_valid(c->owner_uid)) /* Split up by login UIDs. We do this only if the * realuid is not root, in order not to accidentally * leak privileged information to the user that is * logged by a privileged process that is part of an * unprivileged session. */ - journal_uid = owner; + journal_uid = c->owner_uid; else journal_uid = 0; @@ -1069,11 +856,11 @@ static void dispatch_message_real( } void server_driver_message(Server *s, const char *message_id, const char *format, ...) { + struct iovec iovec[N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS]; unsigned n = 0, m; - int r; va_list ap; - struct ucred ucred = {}; + int r; assert(s); assert(format); @@ -1095,12 +882,8 @@ void server_driver_message(Server *s, const char *message_id, const char *format /* Error handling below */ va_end(ap); - ucred.pid = getpid_cached(); - ucred.uid = getuid(); - ucred.gid = getgid(); - if (r >= 0) - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0, NULL); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), s->my_context, NULL, LOG_INFO, 0); while (m < n) free(iovec[m++].iov_base); @@ -1114,24 +897,20 @@ void server_driver_message(Server *s, const char *message_id, const char *format n = 3; IOVEC_SET_STRING(iovec[n++], "PRIORITY=4"); IOVEC_SET_STRING(iovec[n++], buf); - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0, NULL); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), s->my_context, NULL, LOG_INFO, 0); } } void server_dispatch_message( Server *s, struct iovec *iovec, unsigned n, unsigned m, - const struct ucred *ucred, + ClientContext *c, const struct timeval *tv, - const char *label, size_t label_len, - const char *unit_id, int priority, pid_t object_pid) { - int rl, r; - _cleanup_free_ char *path = NULL; uint64_t available = 0; - char *c = NULL; + int rl; assert(s); assert(iovec || n == 0); @@ -1147,45 +926,21 @@ void server_dispatch_message( if (s->storage == STORAGE_NONE) return; - if (!ucred) - goto finish; + if (c && c->unit) { + (void) determine_space(s, &available, NULL); - r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &path); - if (r < 0) - goto finish; + rl = journal_rate_limit_test(s->rate_limit, c->unit, priority & LOG_PRIMASK, available); + if (rl == 0) + return; - /* example: /user/lennart/3/foobar - * /system/dbus.service/foobar - * - * So let's cut of everything past the third /, since that is - * where user directories start */ - - c = strchr(path, '/'); - if (c) { - c = strchr(c+1, '/'); - if (c) { - c = strchr(c+1, '/'); - if (c) - *c = 0; - } + /* Write a suppression message if we suppressed something */ + if (rl > 1) + server_driver_message(s, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_DROPPED_STR, + LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, c->unit), + NULL); } - (void) determine_space(s, &available, NULL); - rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available); - if (rl == 0) - return; - - /* Write a suppression message if we suppressed something */ - if (rl > 1) - server_driver_message(s, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_DROPPED_STR, - LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, path), - NULL); - -finish: - /* restore cgroup path for logging */ - if (c) - *c = '/'; - dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid, path); + dispatch_message_real(s, iovec, n, m, c, tv, priority, object_pid); } int server_flush_to_var(Server *s, bool require_flag_file) { @@ -1335,9 +1090,8 @@ int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void return -EIO; } - /* Try to get the right size, if we can. (Not all - * sockets support SIOCINQ, hence we just try, but - * don't rely on it. */ + /* Try to get the right size, if we can. (Not all sockets support SIOCINQ, hence we just try, but don't rely on + * it.) */ (void) ioctl(fd, SIOCINQ, &v); /* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful! */ @@ -2102,6 +1856,8 @@ int server_init(Server *s) { (void) server_connect_notify(s); + (void) client_context_acquire_default(s); + return system_journal_open(s, false); } @@ -2133,6 +1889,8 @@ void server_done(Server *s) { while (s->stdout_streams) stdout_stream_free(s->stdout_streams); + client_context_flush_all(s); + if (s->system_journal) (void) journal_file_close(s->system_journal); diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index 882bcead8d..6a4549b1ba 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -28,9 +28,11 @@ typedef struct Server Server; #include "hashmap.h" #include "journal-file.h" +#include "journald-context.h" #include "journald-rate-limit.h" #include "journald-stream.h" #include "list.h" +#include "prioq.h" typedef enum Storage { STORAGE_AUTO, @@ -166,6 +168,13 @@ struct Server { usec_t watchdog_usec; usec_t last_realtime_clock; + + /* Caching of client metadata */ + Hashmap *client_contexts; + Prioq *client_contexts_lru; + + ClientContext *my_context; /* the context of journald itself */ + ClientContext *pid1_context; /* the context of PID 1 */ }; #define SERVER_MACHINE_ID(s) ((s)->machine_id_field + strlen("_MACHINE_ID=")) @@ -176,7 +185,7 @@ struct Server { #define N_IOVEC_OBJECT_FIELDS 14 #define N_IOVEC_PAYLOAD_FIELDS 15 -void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid); +void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, ClientContext *c, const struct timeval *tv, int priority, pid_t object_pid); void server_driver_message(Server *s, const char *message_id, const char *format, ...) _printf_(3,0) _sentinel_; /* gperf lookup function */ diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index ec10d7aedf..f074f0476f 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -34,6 +34,7 @@ #include "fileio.h" #include "io-util.h" #include "journald-console.h" +#include "journald-context.h" #include "journald-kmsg.h" #include "journald-server.h" #include "journald-stream.h" @@ -41,6 +42,7 @@ #include "journald-wall.h" #include "mkdir.h" #include "parse-util.h" +#include "process-util.h" #include "selinux-util.h" #include "socket-util.h" #include "stdio-util.h" @@ -87,6 +89,8 @@ struct StdoutStream { char *state_file; + ClientContext *context; + LIST_FIELDS(StdoutStream, stdout_stream); LIST_FIELDS(StdoutStream, stdout_stream_notify_queue); }; @@ -96,6 +100,10 @@ void stdout_stream_free(StdoutStream *s) { return; if (s->server) { + + if (s->context) + client_context_release(s->server, s->context); + assert(s->server->n_stdout_streams > 0); s->server->n_stdout_streams--; LIST_REMOVE(stdout_stream, s->server->stdout_streams, s); @@ -233,7 +241,7 @@ static int stdout_stream_log(StdoutStream *s, const char *p) { char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1]; _cleanup_free_ char *message = NULL, *syslog_identifier = NULL; unsigned n = 0; - size_t label_len; + int r; assert(s); assert(p); @@ -278,8 +286,15 @@ static int stdout_stream_log(StdoutStream *s, const char *p) { if (message) IOVEC_SET_STRING(iovec[n++], message); - label_len = s->label ? strlen(s->label) : 0; - server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, s->label, label_len, s->unit_id, priority, 0); + if (s->context) + (void) client_context_maybe_refresh(s->server, s->context, NULL, NULL, 0, NULL, USEC_INFINITY); + else if (pid_is_valid(s->ucred.pid)) { + r = client_context_acquire(s->server, s->ucred.pid, &s->ucred, s->label, strlen_ptr(s->label), s->unit_id, &s->context); + if (r < 0) + log_warning_errno(r, "Failed to acquire client context, ignoring: %m"); + } + + server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), s->context, NULL, priority, 0); return 0; } diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index 17f855e967..a03c36df34 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -325,11 +325,12 @@ void server_process_syslog_message( char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)], syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; - struct iovec iovec[N_IOVEC_META_FIELDS + 6]; - unsigned n = 0; - int priority = LOG_USER | LOG_INFO; _cleanup_free_ char *identifier = NULL, *pid = NULL; + struct iovec iovec[N_IOVEC_META_FIELDS + 6]; + int priority = LOG_USER | LOG_INFO, r; + ClientContext *context = NULL; const char *orig; + unsigned n = 0; assert(s); assert(buf); @@ -376,7 +377,13 @@ void server_process_syslog_message( if (message) IOVEC_SET_STRING(iovec[n++], message); - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority, 0); + if (ucred && pid_is_valid(ucred->pid)) { + r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context); + if (r < 0) + log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid); + } + + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), context, tv, priority, 0); } int server_open_syslog_socket(Server *s) { diff --git a/src/journal/meson.build b/src/journal/meson.build index 582f83afb9..b95efc2041 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -59,24 +59,26 @@ journal_internal_sources += [audit_type_to_name] ############################################################ libjournal_core_sources = files(''' - journald-kmsg.c - journald-kmsg.h - journald-syslog.c - journald-syslog.h - journald-stream.c - journald-stream.h - journald-server.c - journald-server.h - journald-console.c - journald-console.h - journald-wall.c - journald-wall.h - journald-native.c - journald-native.h journald-audit.c journald-audit.h + journald-console.c + journald-console.h + journald-context.c + journald-context.h + journald-kmsg.c + journald-kmsg.h + journald-native.c + journald-native.h journald-rate-limit.c journald-rate-limit.h + journald-server.c + journald-server.h + journald-stream.c + journald-stream.h + journald-syslog.c + journald-syslog.h + journald-wall.c + journald-wall.h journal-internal.h '''.split())