From 7ddb3ead851e4c1269801f9cb25e758ba1beec9b Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 1 Jul 2026 16:45:34 +0100 Subject: [PATCH 1/6] core: avoid using uninitialized buffer on bad systemd.random_seed= unbase64mem() leaves p/sz uninitialized on failure, and the deserialization doesn't bail out in that case, unlike other cases. Follow-up for d247f232a8fd68f91769274f196566a6e9e75d15 --- src/core/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/main.c b/src/core/main.c index 30092626485..f8a7bd0760d 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -543,8 +543,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; r = unbase64mem(value, &p, &sz); - if (r < 0) + if (r < 0) { log_warning_errno(r, "Failed to parse systemd.random_seed= argument, ignoring: %s", value); + return 0; + } free(arg_random_seed); arg_random_seed = sz > 0 ? p : mfree(p); From 61196f5817c1b7fa6d26a5ac05e26a230991c50e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 1 Jul 2026 16:52:59 +0100 Subject: [PATCH 2/6] quotacheck: don't apply an invalid quotacheck.mode= value quota_check_mode_from_string() returns -EINVAL on a bad value, storing it in the global arg_mode. Only change arg_mode on success. Follow-up for d73691c64e05650d838aaeb7da94fd8bdfb60907 Follow-up for dba4fe9a60e8876addcd6a597c9e1d5f529309ca --- src/quotacheck/quotacheck.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c index 52af13fa133..0865e007181 100644 --- a/src/quotacheck/quotacheck.c +++ b/src/quotacheck/quotacheck.c @@ -36,9 +36,11 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (proc_cmdline_value_missing(key, value)) return 0; - arg_mode = quota_check_mode_from_string(value); - if (arg_mode < 0) - log_warning_errno(arg_mode, "Invalid quotacheck.mode= value, ignoring: %s", value); + QuotaCheckMode mode = quota_check_mode_from_string(value); + if (mode < 0) + log_warning_errno(mode, "Invalid quotacheck.mode= value, ignoring: %s", value); + else + arg_mode = mode; } else if (streq(key, "forcequotacheck") && !value) arg_mode = QUOTA_CHECK_FORCE; @@ -54,9 +56,11 @@ static void parse_credentials(void) { if (r < 0) log_debug_errno(r, "Failed to read credential 'quotacheck.mode', ignoring: %m"); else { - arg_mode = quota_check_mode_from_string(value); - if (arg_mode < 0) - log_warning_errno(arg_mode, "Invalid 'quotacheck.mode' credential, ignoring: %s", value); + QuotaCheckMode mode = quota_check_mode_from_string(value); + if (mode < 0) + log_warning_errno(mode, "Invalid 'quotacheck.mode' credential, ignoring: %s", value); + else + arg_mode = mode; } } From 0a869d3882fe6fe8b3c2f16639133d675149471c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 1 Jul 2026 16:55:28 +0100 Subject: [PATCH 3/6] shared/install: give the borrowed name back before bailing on error In the unmask path, when install_changes_add() fails the borrowed *name was not handed back via TAKE_PTR() before returning, so install_info_clear() freed the caller's strv entry, leaving a dangling pointer that is double-freed at the caller's strv_free(). Follow-up for f31f10a6207efc9ae9e0b1f73975b5b610914017 --- src/shared/install.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/install.c b/src/shared/install.c index 6fc43a8df4e..affa4d3033c 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -2438,8 +2438,10 @@ int unit_file_unmask( path_is_generator(&lp, info.path)) { r = install_changes_add(changes, n_changes, INSTALL_CHANGE_IS_MASKED_GENERATOR, info.name, info.path); - if (r < 0) + if (r < 0) { + TAKE_PTR(info.name); /* Return the borrowed name before bailing */ return r; + } } TAKE_PTR(info.name); /* … and give it back here */ From b64a44707d4e0e15974b21cb17cbc6df506b9a7f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 1 Jul 2026 16:59:43 +0100 Subject: [PATCH 4/6] core: donate the fdset to do_reexecute() to avoid a double free do_reexecute() freed the FDSet on the switch-root/soft-reboot fallback but the caller's copy stayed live, so main() freed it again if every fallback exec then failed. Donate the fdset instead: pass it with TAKE_PTR() and take ownership via a _cleanup_ local, freeing it exactly once on every exit path. Follow-up for 3c7878f94b02f65676889fa58a937ff4d4de4a4d --- src/core/main.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index f8a7bd0760d..01911630dc4 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -2123,7 +2123,7 @@ static int do_reexecute( char* argv[], const struct rlimit *saved_rlimit_nofile, const struct rlimit *saved_rlimit_memlock, - FDSet *fds, + FDSet *_fds, /* donated */ const char *switch_root_dir, const char *switch_root_init, uint64_t saved_capability_ambient_set, @@ -2139,6 +2139,9 @@ static int do_reexecute( assert(saved_rlimit_memlock); assert(ret_error_message); + /* The fdset is donated to us, take ownership so it is freed on all exit paths. */ + _cleanup_fdset_free_ FDSet *fds = TAKE_PTR(_fds); + /* Close and disarm the watchdog, so that the new instance can reinitialize it, but the machine * doesn't get rebooted while we do that. */ watchdog_close(/* disarm= */ true); @@ -3848,7 +3851,7 @@ finish: argc, argv, &saved_rlimit_nofile, &saved_rlimit_memlock, - fds, + TAKE_PTR(fds), switch_root_dir, switch_root_init, saved_ambient_set, From 62c182f0d5c2ec6cbdfaf808d143a8a4c7e01f29 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 1 Jul 2026 17:01:27 +0100 Subject: [PATCH 5/6] core/scope: don't assert when start is retried during cgroup chown While a delegated scope waits for the async cgroup chown helper it sits in SCOPE_START_CHOWN (UNIT_ACTIVATING). unit_start() forwards to ->start() while activating, so scope_start() could be re-entered in that state and trip assert(s->state == SCOPE_DEAD), aborting PID 1. Treat SCOPE_START_CHOWN as already-starting instead. Follow-up for 03860190fefce8bbea3a6f0e77919b882ade517c --- src/core/scope.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/scope.c b/src/core/scope.c index 5e116a6f314..6d2e8836d82 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -437,6 +437,10 @@ static int scope_start(Unit *u) { if (IN_SET(s->state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) return -EAGAIN; + /* Already starting up (waiting for the async cgroup chown helper)? Then there's nothing to do. */ + if (s->state == SCOPE_START_CHOWN) + return 0; + assert(s->state == SCOPE_DEAD); if (!u->transient && !MANAGER_IS_RELOADING(u->manager)) From f738dcb3b77bf113014b14069796c25667c04980 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 1 Jul 2026 17:03:06 +0100 Subject: [PATCH 6/6] bpf-restrict-fs: use a 32-bit magic key on big-endian too The inner map is created with a uint32_t key, but the update passed &magic[i] where magic is a (possibly 64-bit) statfs_f_type_t. On little-endian the low 32 bits happen to be read; on big-endian 64-bit (s390x, ppc64 BE) the zero high word is read instead, so every filesystem collides on key 0 (the allow/deny selector) and RestrictFileSystems= is silently broken. Pass a truncated copy. Follow-up for 184b4f78cfbded54a6e06bbe1152256c204a7a73 --- src/core/bpf-restrict-fs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c index 93f7b800b5b..5cae80a3135 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -173,7 +173,10 @@ int bpf_restrict_fs_update(const Set *filesystems, uint64_t cgroup_id, int outer if (magic[i] == 0) break; - if (sym_bpf_map_update_elem(inner_map_fd, &magic[i], &dummy_value, BPF_ANY) != 0) { + /* The map key is uint32_t but statfs_f_type_t may be 64-bit, pass a truncated copy + * to avoid breaking on big endian arches. */ + uint32_t key = magic[i]; + if (sym_bpf_map_update_elem(inner_map_fd, &key, &dummy_value, BPF_ANY) != 0) { r = log_error_errno(errno, "bpf-restrict-fs: Failed to update BPF map: %m"); if (sym_bpf_map_delete_elem(outer_map_fd, &cgroup_id) != 0)