diff --git a/src/basic/audit-util.h b/src/basic/audit-util.h index bc08388d41..1ea5610e6d 100644 --- a/src/basic/audit-util.h +++ b/src/basic/audit-util.h @@ -1,10 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#if HAVE_AUDIT +# include +#endif + #include #include #include +#include "errno-util.h" +#include "log.h" #include "pidref.h" #define AUDIT_SESSION_INVALID UINT32_MAX @@ -17,3 +23,30 @@ bool use_audit(void); static inline bool audit_session_is_valid(uint32_t id) { return id > 0 && id != AUDIT_SESSION_INVALID; } + +/* The wrappers for audit_open() and audit_close() are inline functions so that we don't get a spurious + * linkage to libaudit in libbasic, but we also don't need to create a separate source file for two very + * short functions. */ + +static inline int close_audit_fd(int fd) { +#if HAVE_AUDIT + if (fd >= 0) + audit_close(fd); +#else + assert(fd < 0); +#endif + return -EBADF; +} + +static inline int open_audit_fd_or_warn(void) { + int fd = -EBADF; + +#if HAVE_AUDIT + /* If the kernel lacks netlink or audit support, don't worry about it. */ + fd = audit_open(); + if (fd < 0) + return log_full_errno(ERRNO_IS_NOT_SUPPORTED(errno) ? LOG_DEBUG : LOG_WARNING, + errno, "Failed to connect to audit log, ignoring: %m"); +#endif + return fd; +} diff --git a/src/core/audit-fd.c b/src/core/audit-fd.c index 6674fa8379..87b085c1bf 100644 --- a/src/core/audit-fd.c +++ b/src/core/audit-fd.c @@ -5,58 +5,36 @@ #include "audit-fd.h" #if HAVE_AUDIT +# include -#include -#include - -#include "capability-util.h" -#include "fd-util.h" -#include "log.h" +# include "audit-util.h" +# include "capability-util.h" static bool initialized = false; -static int audit_fd; - -int get_audit_fd(void) { +static int audit_fd = -EBADF; +#endif +int get_core_audit_fd(void) { +#if HAVE_AUDIT if (!initialized) { - if (have_effective_cap(CAP_AUDIT_WRITE) <= 0) { + if (have_effective_cap(CAP_AUDIT_WRITE) <= 0) audit_fd = -EPERM; - initialized = true; - - return audit_fd; - } - - audit_fd = audit_open(); - - if (audit_fd < 0) { - if (!IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT)) - log_error_errno(errno, "Failed to connect to audit log: %m"); - - audit_fd = errno ? -errno : -EINVAL; - } + else + audit_fd = open_audit_fd_or_warn(); initialized = true; } return audit_fd; +#else + return -EAFNOSUPPORT; +#endif } -void close_audit_fd(void) { - - if (initialized && audit_fd >= 0) - safe_close(audit_fd); - +void close_core_audit_fd(void) { +#if HAVE_AUDIT + close_audit_fd(audit_fd); initialized = true; audit_fd = -ECONNRESET; -} - -#else - -int get_audit_fd(void) { - return -EAFNOSUPPORT; -} - -void close_audit_fd(void) { -} - #endif +} diff --git a/src/core/audit-fd.h b/src/core/audit-fd.h index 5cdf61e52d..2fceb433a7 100644 --- a/src/core/audit-fd.h +++ b/src/core/audit-fd.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int get_audit_fd(void); -void close_audit_fd(void); +int get_core_audit_fd(void); +void close_core_audit_fd(void); diff --git a/src/core/manager.c b/src/core/manager.c index 8f1dc626c2..1b629b1378 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -3560,7 +3560,7 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { if (MANAGER_IS_RELOADING(m)) return; - audit_fd = get_audit_fd(); + audit_fd = get_core_audit_fd(); if (audit_fd < 0) return; @@ -3575,7 +3575,7 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { if (ERRNO_IS_PRIVILEGE(errno)) { /* We aren't allowed to send audit messages? Then let's not retry again. */ log_debug_errno(errno, "Failed to send audit message, closing audit socket: %m"); - close_audit_fd(); + close_core_audit_fd(); } else log_warning_errno(errno, "Failed to send audit message, ignoring: %m"); } diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c index a67a520a3b..8ab488390c 100644 --- a/src/core/selinux-access.c +++ b/src/core/selinux-access.c @@ -98,9 +98,7 @@ _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) { const char *fmt2; #if HAVE_AUDIT - int fd; - - fd = get_audit_fd(); + int fd = get_core_audit_fd(); if (fd >= 0) { _cleanup_free_ char *buf = NULL; @@ -112,9 +110,9 @@ _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) { if (r >= 0) { if (type == SELINUX_AVC) - audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_AVC, buf, NULL, NULL, NULL, getuid()); + audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, getuid()); else if (type == SELINUX_ERROR) - audit_log_user_avc_message(get_audit_fd(), AUDIT_USER_SELINUX_ERR, buf, NULL, NULL, NULL, getuid()); + audit_log_user_avc_message(fd, AUDIT_USER_SELINUX_ERR, buf, NULL, NULL, NULL, getuid()); return 0; } diff --git a/src/sysusers/meson.build b/src/sysusers/meson.build index 123ff41d3f..c968f55110 100644 --- a/src/sysusers/meson.build +++ b/src/sysusers/meson.build @@ -9,6 +9,7 @@ executables += [ 'name' : 'systemd-sysusers', 'public' : true, 'sources' : files('sysusers.c'), + 'dependencies' : libaudit, }, executable_template + { 'name' : 'systemd-sysusers.standalone', @@ -20,6 +21,7 @@ executables += [ libshared_static, libsystemd_static, ], + 'dependencies' : libaudit, 'build_by_default' : have_standalone_binaries, 'install' : have_standalone_binaries, }, diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index d4997bb6f0..8152e5c205 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "audit-util.h" #include "build.h" #include "chase.h" #include "conf-files.h" @@ -106,6 +107,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); typedef struct Context { + int audit_fd; + OrderedHashmap *users, *groups; OrderedHashmap *todo_uids, *todo_gids; OrderedHashmap *members; @@ -126,6 +129,8 @@ typedef struct Context { static void context_done(Context *c) { assert(c); + c->audit_fd = close_audit_fd(c->audit_fd); + ordered_hashmap_free(c->groups); ordered_hashmap_free(c->users); ordered_hashmap_free(c->members); @@ -163,6 +168,48 @@ static void maybe_emit_login_defs_warning(Context *c) { c->login_defs_need_warning = false; } +static void log_audit_accounts(Context *c, ItemType what) { +#if HAVE_AUDIT + assert(c); + assert(IN_SET(what, ADD_USER, ADD_GROUP)); + + if (arg_dry_run || c->audit_fd < 0) + return; + + Item *i; + int type = what == ADD_USER ? AUDIT_ADD_USER : AUDIT_ADD_GROUP; + const char *op = what == ADD_USER ? "adding-user" : "adding-group"; + + /* Notes: + * + * The op must not contain whitespace. The format with a dash matches what Fedora shadow-utils uses. + * + * We send id == -1, even though we know the number, in particular on success. This is because if we + * send the id, the generated audit message will not contain the name. The name seems more useful + * than the number, hence send just the name: + * + * type=ADD_USER msg=audit(01/10/2025 16:02:00.639:3854) : + * pid=3846380 uid=root auid=zbyszek ses=2 msg='op=adding-user id=unknown(952) exe=systemd-sysusers ... res=success' + * vs. + * type=ADD_USER msg=audit(01/10/2025 16:03:15.457:3908) : + * pid=3846607 uid=root auid=zbyszek ses=2 msg='op=adding-user acct=foo5 exe=systemd-sysusers ... res=success' + */ + + ORDERED_HASHMAP_FOREACH(i, what == ADD_USER ? c->todo_uids : c->todo_gids) + audit_log_acct_message( + c->audit_fd, + type, + program_invocation_short_name, + op, + i->name, + /* id= */ (unsigned) -1, + /* host= */ NULL, + /* addr= */ NULL, + /* tty= */ NULL, + /* success= */ 1); +#endif +} + static int load_user_database(Context *c) { _cleanup_free_ char *passwd_path = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -952,6 +999,8 @@ static int write_files(Context *c) { group_tmp, group_path); group_tmp = mfree(group_tmp); } + /* OK, we have written the group entries successfully */ + log_audit_accounts(c, ADD_GROUP); if (gshadow) { r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path); if (r < 0) @@ -969,6 +1018,8 @@ static int write_files(Context *c) { passwd_tmp = mfree(passwd_tmp); } + /* OK, we have written the user entries successfully */ + log_audit_accounts(c, ADD_USER); if (shadow) { r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path); if (r < 0) @@ -2215,6 +2266,7 @@ static int run(int argc, char *argv[]) { #endif _cleanup_close_ int lock = -EBADF; _cleanup_(context_done) Context c = { + .audit_fd = -EBADF, .search_uid = UID_INVALID, }; @@ -2264,6 +2316,10 @@ static int run(int argc, char *argv[]) { assert(!arg_image); #endif + /* Prepare to emit audit events, but only if we're operating on the host system. */ + if (!arg_root) + c.audit_fd = open_audit_fd_or_warn(); + /* If command line arguments are specified along with --replace, read all configuration files and * insert the positional arguments at the specified place. Otherwise, if command line arguments are * specified, execute just them, and finally, without --replace= or any positional arguments, just diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index a10e6d478a..6df9414063 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -5,12 +5,9 @@ #include #include -#if HAVE_AUDIT -#include -#endif - #include "sd-bus.h" +#include "audit-util.h" #include "alloc-util.h" #include "bus-error.h" #include "bus-locator.h" @@ -30,20 +27,14 @@ typedef struct Context { sd_bus *bus; -#if HAVE_AUDIT int audit_fd; -#endif } Context; static void context_clear(Context *c) { assert(c); c->bus = sd_bus_flush_close_unref(c->bus); -#if HAVE_AUDIT - if (c->audit_fd >= 0) - audit_close(c->audit_fd); - c->audit_fd = -EBADF; -#endif + c->audit_fd = close_audit_fd(c->audit_fd); } static int get_startup_monotonic_time(Context *c, usec_t *ret) { @@ -256,22 +247,14 @@ static int run(int argc, char *argv[]) { }; _cleanup_(context_clear) Context c = { -#if HAVE_AUDIT .audit_fd = -EBADF, -#endif }; log_setup(); umask(0022); -#if HAVE_AUDIT - /* If the kernel lacks netlink or audit support, don't worry about it. */ - c.audit_fd = audit_open(); - if (c.audit_fd < 0) - log_full_errno(IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT) ? LOG_DEBUG : LOG_WARNING, - errno, "Failed to connect to audit log, ignoring: %m"); -#endif + c.audit_fd = open_audit_fd_or_warn(); return dispatch_verb(argc, argv, verbs, &c); }