From 7d822ca8afc52f9dd9e32b83b275bf9f1abaa838 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 14 May 2026 17:13:06 +0000 Subject: [PATCH 01/12] bpf-util: rename from bpf-dlopen, unify version-specific symbol handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames src/shared/bpf-dlopen.{c,h} to src/shared/bpf-util.{c,h} and folds the former src/shared/bpf-compat.h (struct forward decl and compat_bpf_map_create() helper) into the new header. Aligns dlopen_bpf() with the standard wrapper pattern: drops the manual dlopen_safe()/dlsym_many_or_warn()/TAKE_PTR(dl) plumbing and the bespoke 'cached' int in favor of dlopen_many_sym_or_warn() inside a FOREACH_STRING() soname-fallback loop. Unifies declaration of the version-specific symbols (bpf_create_map, bpf_map_create, bpf_object__next_map, bpf_token_create) into a single DISABLE_WARNING_REDUNDANT_DECLS block in the header, and alphabetically merges the DLSYM_PROTOTYPE list. DLSYM_OPTIONAL is used to load each one — call sites already handle NULL (compat_bpf_map_create() and the sym_bpf_object__next_map guard in userns-restrict.c). bpf_token_create additionally defaults to a missing_bpf_token_create() stub returning -ENOSYS, so callers can branch on the errno instead of NULL-checking the pointer. Updates test-bpf-token to match: drops the compile-time LIBBPF_MAJOR_VERSION ≥ 1.5 gate and the direct include in favor of dlopen_bpf() + sym_bpf_token_create(), and treats -ENOSYS as the test-skip path (covering both 'libbpf too old' and 'kernel lacks BPF_TOKEN_CREATE support'). --- src/bpf/bpf-skel-wrapper.h.in | 2 +- src/bpf/meson.build | 2 +- src/core/bpf-bind-iface.c | 2 +- src/core/bpf-restrict-fs.c | 2 +- src/core/bpf-restrict-fsaccess.c | 2 +- src/core/bpf-restrict-ifaces.c | 2 +- src/core/bpf-socket-bind.c | 2 +- src/core/exec-invoke.c | 2 +- src/network/networkd-sysctl.c | 2 +- src/nsresourced/nsresourced-manager.c | 2 +- src/nsresourced/userns-restrict.c | 2 +- src/shared/bpf-compat.h | 49 ------ src/shared/bpf-dlopen.c | 221 ------------------------ src/shared/bpf-link.c | 2 +- src/shared/bpf-util.c | 178 +++++++++++++++++++ src/shared/{bpf-dlopen.h => bpf-util.h} | 39 ++++- src/shared/meson.build | 2 +- src/test/meson.build | 2 +- src/test/test-bpf-restrict-fsaccess.c | 2 +- src/test/test-bpf-token.c | 22 ++- src/test/test-dlopen-so.c | 2 +- 21 files changed, 247 insertions(+), 294 deletions(-) delete mode 100644 src/shared/bpf-compat.h delete mode 100644 src/shared/bpf-dlopen.c create mode 100644 src/shared/bpf-util.c rename src/shared/{bpf-dlopen.h => bpf-util.h} (52%) diff --git a/src/bpf/bpf-skel-wrapper.h.in b/src/bpf/bpf-skel-wrapper.h.in index 3dc3cede3e..d9450a12b0 100644 --- a/src/bpf/bpf-skel-wrapper.h.in +++ b/src/bpf/bpf-skel-wrapper.h.in @@ -7,7 +7,7 @@ * fine given that LGPL-2.1-or-later downgrades to GPL if needed. */ -#include "bpf-dlopen.h" /* IWYU pragma: keep */ +#include "bpf-util.h" /* IWYU pragma: keep */ /* libbpf is used via dlopen(), so rename symbols */ #define bpf_object__attach_skeleton sym_bpf_object__attach_skeleton diff --git a/src/bpf/meson.build b/src/bpf/meson.build index 13af4c968e..ac05eb4b18 100644 --- a/src/bpf/meson.build +++ b/src/bpf/meson.build @@ -394,7 +394,7 @@ foreach program : bpf_programs # Keeping it out of bpf_programs_by_name also keeps it out of the # clang-tidy per-source test loop, which would otherwise fall back # to a BPF compile_commands.json entry (no -Isrc/shared) and fail - # to resolve bpf-dlopen.h. + # to resolve bpf-util.h. configure_file( input : 'bpf-skel-wrapper.h.in', output : name + '-skel.h', diff --git a/src/core/bpf-bind-iface.c b/src/core/bpf-bind-iface.c index 74cb2b3d52..1ba643a116 100644 --- a/src/core/bpf-bind-iface.c +++ b/src/core/bpf-bind-iface.c @@ -12,7 +12,7 @@ #if BPF_FRAMEWORK /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "bind-iface-skel.h" diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c index d3c6adc145..d28abfe4dd 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -15,7 +15,7 @@ #if BPF_FRAMEWORK /* libbpf, clang and llc compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "restrict-fs-skel.h" diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c index be66f7da01..e043bf9fd6 100644 --- a/src/core/bpf-restrict-fsaccess.c +++ b/src/core/bpf-restrict-fsaccess.c @@ -37,7 +37,7 @@ const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_ }; #if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "restrict-fsaccess-skel.h" diff --git a/src/core/bpf-restrict-ifaces.c b/src/core/bpf-restrict-ifaces.c index 3c73a682a3..e30d15cd25 100644 --- a/src/core/bpf-restrict-ifaces.c +++ b/src/core/bpf-restrict-ifaces.c @@ -14,7 +14,7 @@ #if BPF_FRAMEWORK /* libbpf, clang and llc compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "restrict-ifaces-skel.h" diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c index 87e7d60c05..b4bf37b41e 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -9,7 +9,7 @@ #if BPF_FRAMEWORK /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "socket-bind-api.bpf.h" #include "socket-bind-skel.h" diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 260e7a36af..b82f7b995b 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -21,7 +21,7 @@ #include "ask-password-api.h" #include "barrier.h" #include "bitfield.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-restrict-fs.h" #include "btrfs-util.h" #include "capability-util.h" diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 0801ba977a..ba90d342c5 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -5,7 +5,7 @@ #include "sd-messages.h" #include "af-list.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "conf-parser.h" #include "alloc-util.h" #include "cgroup-util.h" diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 406db72e7d..c2a6cd6eab 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -5,7 +5,7 @@ #include "sd-daemon.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #if HAVE_VMLINUX_H #include "bpf-link.h" #include "userns-restrict-skel.h" diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index 11bb2e7d8f..1acc42cd02 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -6,7 +6,7 @@ #include "userns-restrict-skel.h" #endif -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "fd-util.h" #include "log.h" diff --git a/src/shared/bpf-compat.h b/src/shared/bpf-compat.h deleted file mode 100644 index 7f76ee87a0..0000000000 --- a/src/shared/bpf-compat.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -/* libbpf has been moving quickly. - * They added new symbols in the 0.x versions and shortly after removed - * deprecated symbols in 1.0. - * We only need bpf_map_create and libbpf_probe_bpf_prog_type so we work - * around the incompatibility here by: - * - declaring both symbols, and looking for either depending on the libbpf - * so version we found - * - having helpers that automatically use the appropriate version behind the - * new API for easy cleanup later - * - * The advantage of doing this instead of only looking for the symbols declared at - * compile time is that we can then load either the old or the new symbols at runtime - * regardless of the version we were compiled with */ - -/* declare the struct for libbpf <= 0.6.0 -- it causes no harm on newer versions */ -struct bpf_map_create_opts; - -/* new symbols available from 0.7.0. - * We need the symbols here: - * - after bpf_map_create_opts struct has been defined for older libbpf - * - before the compat static inline helpers that use them. - * When removing this file move these back to bpf-dlopen.h */ -extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); -extern struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); - -/* compat symbols removed in libbpf 1.0 */ -extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); - -/* helpers to use the available variant behind new API */ -static inline int compat_bpf_map_create( - enum bpf_map_type map_type, - const char *map_name, - __u32 key_size, - __u32 value_size, - __u32 max_entries, - const struct bpf_map_create_opts *opts) { - - if (sym_bpf_map_create) - return sym_bpf_map_create(map_type, map_name, key_size, - value_size, max_entries, opts); - - return sym_bpf_create_map(map_type, key_size, value_size, max_entries, - 0 /* opts->map_flags, but opts is always NULL for us so skip build dependency on the type */); -} diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c deleted file mode 100644 index ca74682823..0000000000 --- a/src/shared/bpf-dlopen.c +++ /dev/null @@ -1,221 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "sd-dlopen.h" - -#include "bpf-dlopen.h" -#include "dlfcn-util.h" -#include "errno-util.h" -#include "initrd-util.h" -#include "log.h" - -#if HAVE_LIBBPF - -/* libbpf changed types of function prototypes around, so we need to disable some type checking for older - * libbpf. We consider everything older than 0.7 too old for accurate type checks. */ -#if defined(__LIBBPF_CURRENT_VERSION_GEQ) -#if __LIBBPF_CURRENT_VERSION_GEQ(0, 7) -#define MODERN_LIBBPF 1 -#endif -#endif -#if !defined(MODERN_LIBBPF) -#define MODERN_LIBBPF 0 -#endif - -static void *bpf_dl = NULL; - -static DLSYM_PROTOTYPE(libbpf_get_error) = NULL; - -DLSYM_PROTOTYPE(bpf_link__destroy) = NULL; -DLSYM_PROTOTYPE(bpf_link__fd) = NULL; -DLSYM_PROTOTYPE(bpf_link__open) = NULL; -DLSYM_PROTOTYPE(bpf_link__pin) = NULL; -DLSYM_PROTOTYPE(bpf_map__fd) = NULL; -DLSYM_PROTOTYPE(bpf_map__name) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_max_entries) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_pin_path) = NULL; -DLSYM_PROTOTYPE(bpf_map_delete_elem) = NULL; -DLSYM_PROTOTYPE(bpf_map_get_fd_by_id) = NULL; -DLSYM_PROTOTYPE(bpf_map_lookup_elem) = NULL; -DLSYM_PROTOTYPE(bpf_map_update_elem) = NULL; -DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd) = NULL; -DLSYM_PROTOTYPE(bpf_object__attach_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__destroy_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__detach_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__load_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__name) = NULL; -DLSYM_PROTOTYPE(bpf_object__open_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__pin_maps) = NULL; -DLSYM_PROTOTYPE(bpf_program__attach) = NULL; -DLSYM_PROTOTYPE(bpf_program__attach_cgroup) = NULL; -DLSYM_PROTOTYPE(bpf_program__attach_lsm) = NULL; -DLSYM_PROTOTYPE(bpf_program__name) = NULL; -DLSYM_PROTOTYPE(bpf_program__set_autoload) = NULL; -DLSYM_PROTOTYPE(libbpf_set_print) = NULL; -DLSYM_PROTOTYPE(ring_buffer__epoll_fd) = NULL; -DLSYM_PROTOTYPE(ring_buffer__free) = NULL; -DLSYM_PROTOTYPE(ring_buffer__new) = NULL; -DLSYM_PROTOTYPE(ring_buffer__poll) = NULL; - -/* new symbols available from libbpf 0.7.0 */ -int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); -struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); - -/* compat symbols removed in libbpf 1.0 */ -int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); - -_printf_(2,0) -static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_list ap) { -#if !LOG_TRACE - /* libbpf logs a lot of details at its debug level, which we don't need to see. */ - if (level == LIBBPF_DEBUG) - return 0; -#endif - /* All other levels are downgraded to LOG_DEBUG */ - - /* errno is used here, on the assumption that if the log message uses %m, errno will be set to - * something useful. Otherwise, it shouldn't matter, we may pass 0 or some bogus value. */ - return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); -} - -int dlopen_bpf(int log_level) { - static int cached = 0; - int r; - - if (cached != 0) - return cached; - - SD_ELF_NOTE_DLOPEN( - "bpf", - "Support firewalling and sandboxing with BPF", - SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libbpf.so.1", "libbpf.so.0"); - - DISABLE_WARNING_DEPRECATED_DECLARATIONS; - - _cleanup_(dlclosep) void *dl = NULL; - r = dlopen_safe("libbpf.so.1", &dl, /* reterr_dlerror= */ NULL); - if (r < 0) { - /* libbpf < 1.0.0 (we rely on 0.1.0+) provide most symbols we care about, but - * unfortunately not all until 0.7.0. See bpf-compat.h for more details. - * Once we consider we can assume 0.7+ is present we can just use the same symbol - * list for both files, and when we assume 1.0+ is present we can remove this dlopen */ - const char *dle = NULL; - r = dlopen_safe("libbpf.so.0", &dl, &dle); - if (r < 0) { - log_full_errno(in_initrd() ? LOG_DEBUG : log_level, r, - "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled: %s", dle ?: STRERROR(r)); - return (cached = -EOPNOTSUPP); /* turn into recognizable error */ - } - - log_debug("Loaded 'libbpf.so.0' via dlopen()"); - - /* symbols deprecated in 1.0 we use as compat */ - r = dlsym_many_or_warn( - dl, LOG_DEBUG, -#if MODERN_LIBBPF - /* Don't exist anymore in new libbpf, hence cannot type check them */ - DLSYM_ARG_FORCE(bpf_create_map) -#else - DLSYM_ARG(bpf_create_map) -#endif - ); - - /* NB: we don't try to load bpf_object__next_map() on old versions */ - } else { - log_debug("Loaded 'libbpf.so.1' via dlopen()"); - - /* symbols available from 0.7.0 */ - r = dlsym_many_or_warn( - dl, LOG_DEBUG, -#if MODERN_LIBBPF - DLSYM_ARG(bpf_map_create), - DLSYM_ARG(bpf_object__next_map) -#else - /* These symbols did not exist in old libbpf, hence we cannot type check them */ - DLSYM_ARG_FORCE(bpf_map_create), - DLSYM_ARG_FORCE(bpf_object__next_map) -#endif - ); - } - if (r < 0) - return cached = log_full_errno(log_level, r, "Failed to load libbpf symbols, cgroup BPF features disabled: %m"); - - r = dlsym_many_or_warn( - dl, LOG_DEBUG, - DLSYM_ARG(bpf_link__destroy), - DLSYM_ARG(bpf_link__fd), - DLSYM_ARG(bpf_link__open), - DLSYM_ARG(bpf_link__pin), - DLSYM_ARG(bpf_map__fd), - DLSYM_ARG(bpf_map__name), - DLSYM_ARG(bpf_map__set_inner_map_fd), - DLSYM_ARG(bpf_map__set_max_entries), - DLSYM_ARG(bpf_map__set_pin_path), - DLSYM_ARG(bpf_map_delete_elem), - DLSYM_ARG(bpf_map_get_fd_by_id), - DLSYM_ARG(bpf_map_lookup_elem), - DLSYM_ARG(bpf_map_update_elem), - DLSYM_ARG(bpf_obj_get_info_by_fd), - DLSYM_ARG(bpf_object__attach_skeleton), - DLSYM_ARG(bpf_object__destroy_skeleton), - DLSYM_ARG(bpf_object__detach_skeleton), - DLSYM_ARG(bpf_object__load_skeleton), - DLSYM_ARG(bpf_object__name), - DLSYM_ARG(bpf_object__open_skeleton), - DLSYM_ARG(bpf_object__pin_maps), -#if MODERN_LIBBPF - DLSYM_ARG(bpf_program__attach), - DLSYM_ARG(bpf_program__attach_cgroup), - DLSYM_ARG(bpf_program__attach_lsm), -#else - /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ - DLSYM_ARG_FORCE(bpf_program__attach), - DLSYM_ARG_FORCE(bpf_program__attach_cgroup), - DLSYM_ARG_FORCE(bpf_program__attach_lsm), -#endif - DLSYM_ARG(bpf_program__name), - DLSYM_ARG(bpf_program__set_autoload), - DLSYM_ARG(libbpf_get_error), - DLSYM_ARG(libbpf_set_print), - DLSYM_ARG(ring_buffer__epoll_fd), - DLSYM_ARG(ring_buffer__free), - DLSYM_ARG(ring_buffer__new), - DLSYM_ARG(ring_buffer__poll)); - if (r < 0) - return cached = log_full_errno(log_level, r, "Failed to load libbpf symbols, cgroup BPF features disabled: %m"); - - /* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */ - (void) sym_libbpf_set_print(bpf_print_func); - - REENABLE_WARNING; - - bpf_dl = TAKE_PTR(dl); - - return cached = true; -} - -int bpf_get_error_translated(const void *ptr) { - int r; - - r = sym_libbpf_get_error(ptr); - - switch (r) { - case -524: - /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until - * it is fixed: - * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 - */ - return -EOPNOTSUPP; - default: - return r; - } -} - -#else - -int dlopen_bpf(int log_level) { - return log_once_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), - "libbpf support is not compiled in, cgroup BPF features disabled."); -} -#endif diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index 80a6db47de..4ae402625b 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "serialize.h" diff --git a/src/shared/bpf-util.c b/src/shared/bpf-util.c new file mode 100644 index 0000000000..03f3281b07 --- /dev/null +++ b/src/shared/bpf-util.c @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "bpf-util.h" +#include "dlfcn-util.h" +#include "initrd-util.h" +#include "log.h" +#include "strv.h" + +#if HAVE_LIBBPF + +/* libbpf changed types of function prototypes around, so we need to disable some type checking for older + * libbpf. We consider everything older than 0.7 too old for accurate type checks. */ +#if defined(__LIBBPF_CURRENT_VERSION_GEQ) +#if __LIBBPF_CURRENT_VERSION_GEQ(0, 7) +#define MODERN_LIBBPF 1 +#endif +#endif +#if !defined(MODERN_LIBBPF) +#define MODERN_LIBBPF 0 +#endif + +static DLSYM_PROTOTYPE(libbpf_get_error) = NULL; + +DLSYM_PROTOTYPE(bpf_create_map) = NULL; +DLSYM_PROTOTYPE(bpf_link__destroy) = NULL; +DLSYM_PROTOTYPE(bpf_link__fd) = NULL; +DLSYM_PROTOTYPE(bpf_link__open) = NULL; +DLSYM_PROTOTYPE(bpf_link__pin) = NULL; +DLSYM_PROTOTYPE(bpf_map__fd) = NULL; +DLSYM_PROTOTYPE(bpf_map__name) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_max_entries) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_pin_path) = NULL; +DLSYM_PROTOTYPE(bpf_map_create) = NULL; +DLSYM_PROTOTYPE(bpf_map_delete_elem) = NULL; +DLSYM_PROTOTYPE(bpf_map_get_fd_by_id) = NULL; +DLSYM_PROTOTYPE(bpf_map_lookup_elem) = NULL; +DLSYM_PROTOTYPE(bpf_map_update_elem) = NULL; +DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd) = NULL; +DLSYM_PROTOTYPE(bpf_object__attach_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__destroy_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__detach_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__load_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__name) = NULL; +DLSYM_PROTOTYPE(bpf_object__next_map) = NULL; +DLSYM_PROTOTYPE(bpf_object__open_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__pin_maps) = NULL; +DLSYM_PROTOTYPE(bpf_program__attach) = NULL; +DLSYM_PROTOTYPE(bpf_program__attach_cgroup) = NULL; +DLSYM_PROTOTYPE(bpf_program__attach_lsm) = NULL; +DLSYM_PROTOTYPE(bpf_program__name) = NULL; +DLSYM_PROTOTYPE(bpf_program__set_autoload) = NULL; +static int missing_bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts) { + return -ENOSYS; +} +DLSYM_PROTOTYPE(bpf_token_create) = missing_bpf_token_create; +DLSYM_PROTOTYPE(libbpf_set_print) = NULL; +DLSYM_PROTOTYPE(ring_buffer__epoll_fd) = NULL; +DLSYM_PROTOTYPE(ring_buffer__free) = NULL; +DLSYM_PROTOTYPE(ring_buffer__new) = NULL; +DLSYM_PROTOTYPE(ring_buffer__poll) = NULL; + +_printf_(2,0) +static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_list ap) { +#if !LOG_TRACE + /* libbpf logs a lot of details at its debug level, which we don't need to see. */ + if (level == LIBBPF_DEBUG) + return 0; +#endif + /* All other levels are downgraded to LOG_DEBUG */ + + /* errno is used here, on the assumption that if the log message uses %m, errno will be set to + * something useful. Otherwise, it shouldn't matter, we may pass 0 or some bogus value. */ + return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); +} + +int dlopen_bpf(int log_level) { + static void *bpf_dl = NULL; + int r; + + SD_ELF_NOTE_DLOPEN( + "bpf", + "Support firewalling and sandboxing with BPF", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libbpf.so.1", "libbpf.so.0"); + + DISABLE_WARNING_DEPRECATED_DECLARATIONS; + + FOREACH_STRING(soname, "libbpf.so.1", "libbpf.so.0") { + r = dlopen_many_sym_or_warn( + &bpf_dl, soname, LOG_DEBUG, + DLSYM_ARG(bpf_link__destroy), + DLSYM_ARG(bpf_link__fd), + DLSYM_ARG(bpf_link__open), + DLSYM_ARG(bpf_link__pin), + DLSYM_ARG(bpf_map__fd), + DLSYM_ARG(bpf_map__name), + DLSYM_ARG(bpf_map__set_inner_map_fd), + DLSYM_ARG(bpf_map__set_max_entries), + DLSYM_ARG(bpf_map__set_pin_path), + DLSYM_ARG(bpf_map_delete_elem), + DLSYM_ARG(bpf_map_get_fd_by_id), + DLSYM_ARG(bpf_map_lookup_elem), + DLSYM_ARG(bpf_map_update_elem), + DLSYM_ARG(bpf_obj_get_info_by_fd), + DLSYM_ARG(bpf_object__attach_skeleton), + DLSYM_ARG(bpf_object__destroy_skeleton), + DLSYM_ARG(bpf_object__detach_skeleton), + DLSYM_ARG(bpf_object__load_skeleton), + DLSYM_ARG(bpf_object__name), + DLSYM_ARG(bpf_object__open_skeleton), + DLSYM_ARG(bpf_object__pin_maps), +#if MODERN_LIBBPF + DLSYM_ARG(bpf_program__attach), + DLSYM_ARG(bpf_program__attach_cgroup), + DLSYM_ARG(bpf_program__attach_lsm), +#else + /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ + DLSYM_ARG_FORCE(bpf_program__attach), + DLSYM_ARG_FORCE(bpf_program__attach_cgroup), + DLSYM_ARG_FORCE(bpf_program__attach_lsm), +#endif + DLSYM_ARG(bpf_program__name), + DLSYM_ARG(bpf_program__set_autoload), + DLSYM_ARG(libbpf_get_error), + DLSYM_ARG(libbpf_set_print), + DLSYM_ARG(ring_buffer__epoll_fd), + DLSYM_ARG(ring_buffer__free), + DLSYM_ARG(ring_buffer__new), + DLSYM_ARG(ring_buffer__poll)); + if (r >= 0) + break; + } + REENABLE_WARNING; + if (r < 0) + return log_full_errno(in_initrd() ? LOG_DEBUG : log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled."); + + /* Version-specific symbols: bpf_create_map exists only in libbpf < 1.0; bpf_map_create and + * bpf_object__next_map only in 0.7+. bpf_token_create only in 1.5+. Unresolved prototypes keep + * their initializers (NULL, or a fallback returning -ENOSYS for bpf_token_create). */ + DLSYM_OPTIONAL(bpf_dl, bpf_create_map); + DLSYM_OPTIONAL(bpf_dl, bpf_map_create); + DLSYM_OPTIONAL(bpf_dl, bpf_object__next_map); + DLSYM_OPTIONAL(bpf_dl, bpf_token_create); + + /* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */ + (void) sym_libbpf_set_print(bpf_print_func); + + return 1; +} + +int bpf_get_error_translated(const void *ptr) { + int r; + + r = sym_libbpf_get_error(ptr); + + switch (r) { + case -524: + /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until + * it is fixed: + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 + */ + return -EOPNOTSUPP; + default: + return r; + } +} + +#else + +int dlopen_bpf(int log_level) { + return log_once_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libbpf support is not compiled in, cgroup BPF features disabled."); +} +#endif diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-util.h similarity index 52% rename from src/shared/bpf-dlopen.h rename to src/shared/bpf-util.h index 62ea9cf707..622e4c2e28 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-util.h @@ -8,10 +8,27 @@ #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ -#include "bpf-compat.h" /* IWYU pragma: export */ #include "dlfcn-util.h" #include "shared-forward.h" +/* Always redeclare these so DLSYM_PROTOTYPE's typeof() resolves regardless of libbpf version; + * suppress the warning when the libbpf headers already declare them. + * - bpf_map_create, bpf_object__next_map: available since libbpf 0.7 + * - bpf_token_create: available since libbpf 1.5 + * - bpf_create_map: removed in libbpf 1.0 + */ +DISABLE_WARNING_REDUNDANT_DECLS; +/* NOLINTBEGIN(readability-redundant-declaration) */ +struct bpf_map_create_opts; +struct bpf_token_create_opts; +extern int bpf_create_map(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); +extern int bpf_map_create(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); +extern struct bpf_map* bpf_object__next_map(const struct bpf_object *obj, const struct bpf_map *map); +extern int bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts); +/* NOLINTEND(readability-redundant-declaration) */ +REENABLE_WARNING; + +extern DLSYM_PROTOTYPE(bpf_create_map); extern DLSYM_PROTOTYPE(bpf_link__destroy); extern DLSYM_PROTOTYPE(bpf_link__fd); extern DLSYM_PROTOTYPE(bpf_link__open); @@ -21,6 +38,7 @@ extern DLSYM_PROTOTYPE(bpf_map__name); extern DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd); extern DLSYM_PROTOTYPE(bpf_map__set_max_entries); extern DLSYM_PROTOTYPE(bpf_map__set_pin_path); +extern DLSYM_PROTOTYPE(bpf_map_create); extern DLSYM_PROTOTYPE(bpf_map_delete_elem); extern DLSYM_PROTOTYPE(bpf_map_get_fd_by_id); extern DLSYM_PROTOTYPE(bpf_map_lookup_elem); @@ -33,6 +51,7 @@ extern DLSYM_PROTOTYPE(bpf_object__destroy_skeleton); extern DLSYM_PROTOTYPE(bpf_object__detach_skeleton); extern DLSYM_PROTOTYPE(bpf_object__load_skeleton); extern DLSYM_PROTOTYPE(bpf_object__name); +extern DLSYM_PROTOTYPE(bpf_object__next_map); extern DLSYM_PROTOTYPE(bpf_object__open_skeleton); extern DLSYM_PROTOTYPE(bpf_object__pin_maps); extern DLSYM_PROTOTYPE(bpf_program__attach); @@ -40,12 +59,30 @@ extern DLSYM_PROTOTYPE(bpf_program__attach_cgroup); extern DLSYM_PROTOTYPE(bpf_program__attach_lsm); extern DLSYM_PROTOTYPE(bpf_program__name); extern DLSYM_PROTOTYPE(bpf_program__set_autoload); +extern DLSYM_PROTOTYPE(bpf_token_create); extern DLSYM_PROTOTYPE(libbpf_set_print); extern DLSYM_PROTOTYPE(ring_buffer__epoll_fd); extern DLSYM_PROTOTYPE(ring_buffer__free); extern DLSYM_PROTOTYPE(ring_buffer__new); extern DLSYM_PROTOTYPE(ring_buffer__poll); +/* Helper that uses the available variant behind the new API. */ +static inline int compat_bpf_map_create( + enum bpf_map_type map_type, + const char *map_name, + __u32 key_size, + __u32 value_size, + __u32 max_entries, + const struct bpf_map_create_opts *opts) { + + if (sym_bpf_map_create) + return sym_bpf_map_create(map_type, map_name, key_size, + value_size, max_entries, opts); + + return sym_bpf_create_map(map_type, key_size, value_size, max_entries, + 0 /* opts->map_flags, but opts is always NULL for us so skip build dependency on the type */); +} + /* libbpf sometimes returns error codes that make sense only in the kernel, like 524 for EOPNOTSUPP. Use * this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos * we understand. */ diff --git a/src/shared/meson.build b/src/shared/meson.build index 9b539f7983..ce96ce3025 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -19,8 +19,8 @@ shared_sources = files( 'boot-entry.c', 'boot-timestamps.c', 'bootspec.c', - 'bpf-dlopen.c', 'bpf-program.c', + 'bpf-util.c', 'bridge-util.c', 'btrfs-util.c', 'bus-get-properties.c', diff --git a/src/test/meson.build b/src/test/meson.build index 4871c5517f..c8279bc751 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -554,7 +554,7 @@ executables += [ }, core_test_template + { 'sources' : files('test-bpf-token.c'), - 'dependencies' : common_test_dependencies + libbpf, + 'dependencies' : common_test_dependencies + libbpf_cflags, 'conditions' : ['BPF_FRAMEWORK'], 'type' : 'manual', }, diff --git a/src/test/test-bpf-restrict-fsaccess.c b/src/test/test-bpf-restrict-fsaccess.c index e95e706c27..0d4e18671a 100644 --- a/src/test/test-bpf-restrict-fsaccess.c +++ b/src/test/test-bpf-restrict-fsaccess.c @@ -92,7 +92,7 @@ static int do_mprotect_exec(const char *path) { } #if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "restrict-fsaccess-skel.h" static struct restrict_fsaccess_bpf *restrict_fsaccess_bpf_free(struct restrict_fsaccess_bpf *obj) { diff --git a/src/test/test-bpf-token.c b/src/test/test-bpf-token.c index 7e83a048e5..f991f92601 100644 --- a/src/test/test-bpf-token.c +++ b/src/test/test-bpf-token.c @@ -1,26 +1,34 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "bpf-util.h" #include "fd-util.h" #include "main-func.h" -#include "tests.h" /* NOLINT(misc-include-cleaner): this is needed conditionally */ +#include "tests.h" static int run(int argc, char *argv[]) { -#if defined(LIBBPF_MAJOR_VERSION) && (LIBBPF_MAJOR_VERSION > 1 || (LIBBPF_MAJOR_VERSION == 1 && LIBBPF_MINOR_VERSION >= 5)) - _cleanup_close_ int bpffs_fd = open("/sys/fs/bpf", O_RDONLY); +#if HAVE_LIBBPF + int r; + + r = dlopen_bpf(LOG_ERR); + if (r < 0) + return r; + + _cleanup_close_ int bpffs_fd = open("/sys/fs/bpf", O_CLOEXEC|O_RDONLY); if (bpffs_fd < 0) return log_error_errno(errno, "Failed to open '/sys/fs/bpf': %m"); - _cleanup_close_ int token_fd = bpf_token_create(bpffs_fd, /* opts= */ NULL); + _cleanup_close_ int token_fd = sym_bpf_token_create(bpffs_fd, /* opts= */ NULL); + if (token_fd == -ENOSYS) + return log_tests_skipped("bpf_token_create() unavailable (libbpf too old or kernel lacks BPF_TOKEN_CREATE support)"); if (token_fd < 0) - return log_error_errno(errno, "Failed to create bpf token: %m"); + return log_error_errno(token_fd, "Failed to create bpf token: %m"); log_info("Successfully created token fd."); return 0; #else - return log_tests_skipped("libbpf is older than v1.5"); + return log_tests_skipped("BPF framework support is disabled"); #endif } diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 7421a77024..0eca1356c0 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -3,7 +3,7 @@ #include "acl-util.h" #include "apparmor-util.h" #include "blkid-util.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "compress.h" #include "crypto-util.h" #include "cryptsetup-util.h" From 94d745c42c389ac2de89ca5b5e300678b9c23f84 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 12:16:01 +0000 Subject: [PATCH 02/12] cryptsetup: dlopen libcryptsetup in tokens This avoids having to subpackage the tokens separately. If they link directly against libcryptsetup, package manager will automatically add a dependency on libcryptsetup to the package containing the tokens. With this change, the tokens can ship in the main systemd package without necessarily pulling in libcryptsetup. It also makes things more consistent. Once we also do the same for pam, any direct linking will be limited to just libc, which for example simplifies writing tests for ensuring we don't link unnecessarily as we don't have to add exceptions for the cryptsetup tokens. This actually drops the dependency on cryptsetup-libs for the fedora/centos/opensuse systemd-udev package so install it explicitly in the initrd now to keep the tests working. --- .../mkosi.conf.d/centos-fedora.conf | 1 + mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf | 1 + .../cryptsetup-token-systemd-fido2.c | 14 +++++++++++++- .../cryptsetup-token-systemd-pkcs11.c | 14 +++++++++++++- .../cryptsetup-token-systemd-tpm2.c | 14 +++++++++++++- .../cryptsetup-tokens/cryptsetup-token-util.h | 13 ++++++------- src/cryptsetup/cryptsetup-tokens/meson.build | 8 ++++---- src/shared/cryptsetup-util.c | 2 ++ src/shared/cryptsetup-util.h | 1 + 9 files changed, 54 insertions(+), 14 deletions(-) diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf index e753749dc4..ab64813060 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/centos-fedora.conf @@ -8,6 +8,7 @@ Distribution=|fedora PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= coreutils + cryptsetup-libs policycoreutils swtpm-tools tpm2-tools diff --git a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf index c30d970c85..b863f7b2d1 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf @@ -9,6 +9,7 @@ Packages= btrfs-progs coreutils kmod + libcryptsetup12 policycoreutils tpm2.0-tools diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index a2804e033a..0265b2d5a0 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -2,6 +2,7 @@ #include #include +#include #include "sd-json.h" @@ -37,8 +38,12 @@ _public_ int cryptsetup_token_open_pin( assert(pin || pin_size == 0); assert(token >= 0); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ assert_se(token == r); assert(json); @@ -98,6 +103,9 @@ _public_ void cryptsetup_token_dump( assert(json); + if (dlopen_cryptsetup(LOG_DEBUG) < 0) + return; + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -162,6 +170,10 @@ _public_ int cryptsetup_token_validate( assert(json); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c index 16cf910fe6..e99fcfea85 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-json.h" @@ -36,8 +37,12 @@ _public_ int cryptsetup_token_open_pin( assert(pin || pin_size == 0); assert(token >= 0); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ assert_se(token == r); assert(json); @@ -89,6 +94,9 @@ _public_ void cryptsetup_token_dump( _cleanup_free_ char *pkcs11_uri = NULL, *key_str = NULL; _cleanup_free_ void *pkcs11_key = NULL; + if (dlopen_cryptsetup(LOG_DEBUG) < 0) + return; + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -115,6 +123,10 @@ _public_ int cryptsetup_token_validate( sd_json_variant *w; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 58dc37c5bf..ad14942622 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "alloc-util.h" #include "cryptsetup-token.h" @@ -60,8 +61,12 @@ _public_ int cryptsetup_token_open_pin( assert(ret_password); assert(ret_password_len); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); assert(token == r); assert(json); @@ -186,6 +191,9 @@ _public_ void cryptsetup_token_dump( assert(json); + if (dlopen_cryptsetup(LOG_DEBUG) < 0) + return; + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); @@ -275,6 +283,10 @@ _public_ int cryptsetup_token_validate( assert(json); + r = dlopen_cryptsetup(LOG_DEBUG); + if (r < 0) + return r; + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h index 68b09fc458..c6a7d25f39 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -2,22 +2,21 @@ #pragma once -#include - +#include "cryptsetup-util.h" #include "shared-forward.h" /* crypt_dump() internal indentation magic */ #define CRYPT_DUMP_LINE_SEP "\n\t " -#define crypt_log_debug(cd, ...) crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) -#define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) -#define crypt_log_verbose(cd, ...) crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) -#define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) +#define crypt_log_debug(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) +#define crypt_log_error(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) +#define crypt_log_verbose(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) +#define crypt_log(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) #define crypt_log_full_errno(cd, e, lvl, ...) ({ \ int _e = ABS(e), _s = errno; \ errno = _e; \ - crypt_logf(cd, lvl, __VA_ARGS__); \ + sym_crypt_logf(cd, lvl, __VA_ARGS__); \ errno = _s; \ -_e; \ }) diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 0fd6309201..772c29f50f 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -45,8 +45,8 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_tpm2_sources, 'dependencies' : [ - libcryptsetup, - tpm2, + libcryptsetup_cflags, + tpm2_cflags, ], }, template + { @@ -57,7 +57,7 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_fido2_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libfido2_cflags, ], }, @@ -69,7 +69,7 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_pkcs11_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libp11kit_cflags, ], }, diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 7aa3d5d9c5..a51783cb29 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -45,6 +45,7 @@ DLSYM_PROTOTYPE(crypt_keyslot_destroy) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_max) = NULL; DLSYM_PROTOTYPE(crypt_keyslot_status) = NULL; DLSYM_PROTOTYPE(crypt_load) = NULL; +DLSYM_PROTOTYPE(crypt_logf) = NULL; DLSYM_PROTOTYPE(crypt_metadata_locking) = NULL; DLSYM_PROTOTYPE(crypt_persistent_flags_get) = NULL; DLSYM_PROTOTYPE(crypt_persistent_flags_set) = NULL; @@ -315,6 +316,7 @@ int dlopen_cryptsetup(int log_level) { DLSYM_ARG(crypt_keyslot_max), DLSYM_ARG(crypt_keyslot_status), DLSYM_ARG(crypt_load), + DLSYM_ARG(crypt_logf), DLSYM_ARG(crypt_metadata_locking), DLSYM_ARG(crypt_persistent_flags_get), DLSYM_ARG(crypt_persistent_flags_set), diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 5c83aad8ee..23d52d111a 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -46,6 +46,7 @@ extern DLSYM_PROTOTYPE(crypt_keyslot_destroy); extern DLSYM_PROTOTYPE(crypt_keyslot_max); extern DLSYM_PROTOTYPE(crypt_keyslot_status); extern DLSYM_PROTOTYPE(crypt_load); +extern DLSYM_PROTOTYPE(crypt_logf); extern DLSYM_PROTOTYPE(crypt_metadata_locking); extern DLSYM_PROTOTYPE(crypt_persistent_flags_get); extern DLSYM_PROTOTYPE(crypt_persistent_flags_set); From 62053ee28955ed339d44e7725bb1b555c51e7143 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 14:54:04 +0000 Subject: [PATCH 03/12] tree-wide: dlopen libpam in pam plugins Same reasoning as for cryptsetup tokens. It means we can include the pam plugins in the main systemd package without the package manager introducing a dependency on libpam. It also makes things more consistent and makes writing the upcoming linking test script a lot simpler. At the same time, we get rid of the libpam_misc dependency as the one symbol we were using from it is trivial to reimplement ourselves. --- meson.build | 23 +------- src/home/meson.build | 3 +- src/home/pam_systemd_home.c | 73 ++++++++++++----------- src/login/pam_systemd.c | 100 ++++++++++++++++---------------- src/login/pam_systemd_loadkey.c | 4 +- src/shared/pam-util.c | 23 ++++++++ src/shared/pam-util.h | 16 ++++- 7 files changed, 132 insertions(+), 110 deletions(-) diff --git a/meson.build b/meson.build index c69b96e1c6..92129bee7d 100644 --- a/meson.build +++ b/meson.build @@ -1133,12 +1133,7 @@ if not libpam.found() # Debian older than bookworm and Ubuntu older than 22.10 do not provide the .pc file. libpam = cc.find_library('pam', required : feature) endif -libpam_misc = dependency('pam_misc', - required : feature.disabled() ? feature : false) -if not libpam_misc.found() - libpam_misc = cc.find_library('pam_misc', required : feature) -endif -conf.set10('HAVE_PAM', libpam.found() and libpam_misc.found()) +conf.set10('HAVE_PAM', libpam.found()) libpam_cflags = libpam.partial_dependency(includes: true, compile_args: true) libmicrohttpd = dependency('libmicrohttpd', @@ -1949,21 +1944,7 @@ pam_template = { libshared_static, ], 'dependencies' : [ - # Note: our PAM modules also call dlopen_libpam() and use - # symbols acquired through that, hence the explicit dep here is - # strictly speaking unnecessary. We put it in place anyway, - # since for the PAM modules we cannot avoid libpam anyway, - # after all they are loaded *by* libpam, and hence there's no - # loss in having explicit deps here, but there's a win: it - # makes the deps more visible. - # - # (In case you wonder why we do dlopen_libpam() from the PAM - # modules in the first place: that's mostly so that all our PAM - # code (regardless if our PAM modules or our PAM consuming - # programs) can use the same helpers, which hence go via - # dlopen_libpam(). - libpam_misc, - libpam, + libpam_cflags, threads, ], 'install' : true, diff --git a/src/home/meson.build b/src/home/meson.build index 600f00b4ac..631eeb13aa 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -116,8 +116,7 @@ modules += [ 'sources' : pam_systemd_home_sources, 'dependencies' : [ libintl, - libpam_misc, - libpam, + libpam_cflags, threads, ], 'version-script' : meson.current_source_dir() / 'pam_systemd_home.sym', diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 1de66b4c03..5fe6dcbec2 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include "sd-bus.h" @@ -45,7 +44,7 @@ static int parse_argv( k = parse_boolean(v); if (k < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); else if (flags) SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k); @@ -57,12 +56,12 @@ static int parse_argv( int k; k = parse_boolean(v); if (k < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v); else if (debug) *debug = k; } else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } return 0; @@ -76,7 +75,7 @@ static int parse_env(pam_handle_t *pamh, AcquireHomeFlags *flags) { * easy to declare the features of a display manager in code rather than configuration, and this is * really a feature of code */ - v = pam_getenv(pamh, "SYSTEMD_HOME_SUSPEND"); + v = sym_pam_getenv(pamh, "SYSTEMD_HOME_SUSPEND"); if (!v) { /* Also check the process env block, so that people can control this via an env var from the * outside of our process. */ @@ -87,7 +86,7 @@ static int parse_env(pam_handle_t *pamh, AcquireHomeFlags *flags) { r = parse_boolean(v); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); else if (flags) SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r); @@ -106,7 +105,7 @@ static int acquire_user_record( assert(pamh); if (!username) { - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -149,7 +148,7 @@ static int acquire_user_record( return pam_log_oom(pamh); /* Let's use the cache, so that we can share it between the session and the authentication hooks */ - r = pam_get_data(pamh, homed_field, (const void**) &json); + r = sym_pam_get_data(pamh, homed_field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); if (r == PAM_SUCCESS && json) { @@ -211,13 +210,13 @@ static int acquire_user_record( return pam_syslog_pam_error(pamh, LOG_ERR, PAM_SERVICE_ERR, "Acquired user record does not match user name."); - /* Update the 'username' pointer to point to our own record now. The pam_set_item() call below is + /* Update the 'username' pointer to point to our own record now. The sym_pam_set_item() call below is * going to invalidate the old version after all */ username = ur->user_name; /* We passed all checks. Let's now make sure the rest of the PAM stack continues with the primary, * normalized name of the user record (i.e. not an alias or so). */ - r = pam_set_item(pamh, PAM_USER, ur->user_name); + r = sym_pam_set_item(pamh, PAM_USER, ur->user_name); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to update username PAM item to '%s': @PAMERR@", ur->user_name); @@ -230,7 +229,7 @@ static int acquire_user_record( if (!json_copy) return pam_log_oom(pamh); - r = pam_set_data(pamh, homed_field, json_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, homed_field, json_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", homed_field); @@ -246,7 +245,7 @@ static int acquire_user_record( if (!generic_field) return pam_log_oom(pamh); - r = pam_set_data(pamh, generic_field, json_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, generic_field, json_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", generic_field); @@ -256,7 +255,7 @@ static int acquire_user_record( /* Let's store the area we parsed out of the name in an env var, so that pam_systemd later can honour it. */ if (area) { - r = pam_misc_setenv(pamh, "XDG_AREA", area, /* readonly= */ 0); + r = pam_putenv_assign(pamh, "XDG_AREA", area); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable $XDG_AREA to '%s': @PAMERR@", area); @@ -269,7 +268,7 @@ static int acquire_user_record( user_unknown: /* Cache this, so that we don't check again */ - r = pam_set_data(pamh, homed_field, POINTER_MAX, NULL); + r = sym_pam_set_data(pamh, homed_field, POINTER_MAX, NULL); if (r != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s' to invalid, ignoring: @PAMERR@", @@ -289,7 +288,7 @@ static int release_user_record(pam_handle_t *pamh, const char *username) { if (!homed_field) return pam_log_oom(pamh); - r = pam_set_data(pamh, homed_field, NULL, NULL); + r = sym_pam_set_data(pamh, homed_field, NULL, NULL); if (r != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to release PAM user record data '%s': @PAMERR@", homed_field); @@ -298,7 +297,7 @@ static int release_user_record(pam_handle_t *pamh, const char *username) { if (!generic_field) return pam_log_oom(pamh); - k = pam_set_data(pamh, generic_field, NULL, NULL); + k = sym_pam_set_data(pamh, generic_field, NULL, NULL); if (k != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, k, "Failed to release PAM user record data '%s': @PAMERR@", generic_field); @@ -569,7 +568,7 @@ static int acquire_home( * prompt the user for the missing unlock credentials, and then chainload the real shell. */ - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -580,7 +579,7 @@ static int acquire_home( if (!fd_field) return pam_log_oom(pamh); - r = pam_get_data(pamh, fd_field, &home_fd_ptr); + r = sym_pam_get_data(pamh, fd_field, &home_fd_ptr); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to retrieve PAM home reference fd: @PAMERR@"); @@ -618,7 +617,7 @@ static int acquire_home( /* If there's already a cached password, use it. But if not let's authenticate * without anything, maybe some other authentication mechanism systemd-homed * implements (such as PKCS#11) allows us to authenticate without anything else. */ - r = pam_get_item(pamh, PAM_AUTHTOK, (const void**) &cached_password); + r = sym_pam_get_item(pamh, PAM_AUTHTOK, (const void**) &cached_password); if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get cached password: @PAMERR@"); @@ -681,7 +680,7 @@ static int acquire_home( (void) pam_prompt_graceful(pamh, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name); if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE)) - pam_syslog(pamh, LOG_ERR, "Failed to prompt for password/prompt."); + sym_pam_syslog(pamh, LOG_ERR, "Failed to prompt for password/prompt."); else pam_debug_syslog(pamh, debug, "Failed to prompt for password/prompt."); @@ -720,12 +719,12 @@ static int acquire_home( /* Later PAM modules may need the auth token, but only during pam_authenticate. */ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) { - r = pam_set_item(pamh, PAM_AUTHTOK, *secret->password); + r = sym_pam_set_item(pamh, PAM_AUTHTOK, *secret->password); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); } - r = pam_set_data(pamh, fd_field, FD_TO_PTR(acquired_fd), cleanup_home_fd); + r = sym_pam_set_data(pamh, fd_field, FD_TO_PTR(acquired_fd), cleanup_home_fd); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@"); TAKE_FD(acquired_fd); @@ -744,13 +743,13 @@ static int acquire_home( * manager for us (since it would see an inaccessible home directory). Hence set an environment * variable that pam_systemd looks for). */ if (unrestricted) { - r = pam_putenv(pamh, "XDG_SESSION_INCOMPLETE=1"); + r = sym_pam_putenv(pamh, "XDG_SESSION_INCOMPLETE=1"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_WARNING, r, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@"); - pam_syslog(pamh, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); + sym_pam_syslog(pamh, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); } else - pam_syslog(pamh, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); + sym_pam_syslog(pamh, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); return PAM_SUCCESS; } @@ -767,13 +766,13 @@ static int release_home_fd(pam_handle_t *pamh, const char *username) { if (!fd_field) return pam_log_oom(pamh); - r = pam_get_data(pamh, fd_field, &home_fd_ptr); + r = sym_pam_get_data(pamh, fd_field, &home_fd_ptr); if (r == PAM_NO_MODULE_DATA || (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) < 0)) return PAM_NO_MODULE_DATA; if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to retrieve PAM home reference fd: @PAMERR@"); - r = pam_set_data(pamh, fd_field, NULL, NULL); + r = sym_pam_set_data(pamh, fd_field, NULL, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to release PAM home reference fd: @PAMERR@"); @@ -887,12 +886,12 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r != PAM_SUCCESS) return r; - r = pam_putenv(pamh, "SYSTEMD_HOME=1"); + r = sym_pam_putenv(pamh, "SYSTEMD_HOME=1"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@"); - r = pam_putenv(pamh, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); + r = sym_pam_putenv(pamh, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@"); @@ -911,6 +910,10 @@ _public_ PAM_EXTERN int pam_sm_close_session( bool debug = false; int r; + r = dlopen_libpam(LOG_DEBUG); + if (r < 0) + return PAM_SERVICE_ERR; + pam_log_setup(); if (parse_argv(pamh, @@ -921,7 +924,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( pam_debug_syslog(pamh, debug, "pam-systemd-homed: closing session..."); - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -954,7 +957,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( return pam_syslog_pam_error(pamh, LOG_ERR, PAM_SESSION_ERR, "Failed to release user home: %s", bus_error_message(&error, r)); - pam_syslog(pamh, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username); + sym_pam_syslog(pamh, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username); } return PAM_SUCCESS; @@ -1005,7 +1008,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( switch (r) { case -ESTALE: - pam_syslog(pamh, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name); + sym_pam_syslog(pamh, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name); break; case -ENOLCK: @@ -1062,7 +1065,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( case -ESTALE: /* If the system clock is wrong, let's log but continue */ - pam_syslog(pamh, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong."); + sym_pam_syslog(pamh, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong."); break; case -EROFS: @@ -1121,7 +1124,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( /* No, it's not cached, then let's ask for the password and its verification, and cache * it. */ - r = pam_get_authtok_noverify(pamh, &new_password, "New password: "); + r = sym_pam_get_authtok_noverify(pamh, &new_password, "New password: "); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get new password: @PAMERR@"); @@ -1130,7 +1133,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( return PAM_AUTHTOK_ERR; } - r = pam_get_authtok_verify(pamh, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */ + r = sym_pam_get_authtok_verify(pamh, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */ if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get password again: @PAMERR@"); diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 4e571e7056..1b0adfae9e 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -88,7 +87,7 @@ static int parse_caps( c = capability_from_name(s); if (c < 0) { - pam_syslog(pamh, LOG_WARNING, "Unknown capability, ignoring: %s", s); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown capability, ignoring: %s", s); continue; } @@ -146,7 +145,7 @@ static int parse_argv( } else if ((p = startswith(argv[i], "area="))) { if (!isempty(p) && !filename_is_valid(p)) - pam_syslog(pamh, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %s", p); else if (area) *area = p; @@ -157,22 +156,22 @@ static int parse_argv( } else if ((p = startswith(argv[i], "debug="))) { r = parse_boolean(p); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); else if (debug) *debug = r; } else if ((p = startswith(argv[i], "default-capability-bounding-set="))) { r = parse_caps(pamh, p, default_capability_bounding_set); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); } else if ((p = startswith(argv[i], "default-capability-ambient-set="))) { r = parse_caps(pamh, p, default_capability_ambient_set); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); } else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } return 0; @@ -184,7 +183,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { assert(pamh); const char *username = NULL; - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -198,7 +197,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; const char *json = NULL; - r = pam_get_data(pamh, field, (const void**) &json); + r = sym_pam_get_data(pamh, field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); if (r == PAM_SUCCESS && json) { @@ -240,7 +239,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to format user JSON: %m"); /* And cache it for everyone else */ - r = pam_set_data(pamh, field, formatted, pam_cleanup_free); + r = sym_pam_set_data(pamh, field, formatted, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", field); @@ -382,7 +381,7 @@ static int append_session_memory_max(pam_handle_t *pamh, sd_bus_message *m, cons uint64_t val; r = parse_size(limit, 1024, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); return 0; } @@ -402,7 +401,7 @@ static int append_session_runtime_max_sec(pam_handle_t *pamh, sd_bus_message *m, usec_t val; r = parse_sec(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); return 0; } @@ -422,7 +421,7 @@ static int append_session_tasks_max(pam_handle_t *pamh, sd_bus_message *m, const uint64_t val; r = safe_atou64(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); return 0; } @@ -441,7 +440,7 @@ static int append_session_cpu_weight(pam_handle_t *pamh, sd_bus_message *m, cons uint64_t val; r = cg_cpu_weight_parse(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); return 0; } @@ -460,7 +459,7 @@ static int append_session_io_weight(pam_handle_t *pamh, sd_bus_message *m, const uint64_t val; r = cg_weight_parse(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); return 0; } @@ -479,7 +478,7 @@ static const char* getenv_harder(pam_handle_t *pamh, const char *key, const char * PAM services don't have to be reworked to set systemd-specific properties, but these properties * can still be set from the unit file Environment= block. */ - v = pam_getenv(pamh, key); + v = sym_pam_getenv(pamh, key); if (!isempty(v)) return v; @@ -507,9 +506,9 @@ static bool getenv_harder_bool(pam_handle_t *pamh, const char *key, bool fallbac r = parse_boolean(v); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, - "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", - v, key, true_false(fallback)); + sym_pam_syslog(pamh, LOG_WARNING, + "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", + v, key, true_false(fallback)); return fallback; } @@ -529,9 +528,9 @@ static uint32_t getenv_harder_uint32(pam_handle_t *pamh, const char *key, uint32 uint32_t u; r = safe_atou32(v, &u); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, - "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", - v, key, fallback); + sym_pam_syslog(pamh, LOG_WARNING, + "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", + v, key, fallback); return fallback; } @@ -548,14 +547,14 @@ static int update_environment(pam_handle_t *pamh, const char *key, const char *v * about errors. */ if (isempty(value)) { - /* Unset the variable if set. Note that pam_putenv() would log nastily behind our back if we + /* Unset the variable if set. Note that sym_pam_putenv() would log nastily behind our back if we * call it without the variable actually being set. Hence we check explicitly if it's set * before. */ - if (!pam_getenv(pamh, key)) + if (!sym_pam_getenv(pamh, key)) return PAM_SUCCESS; - r = pam_putenv(pamh, key); + r = sym_pam_putenv(pamh, key); if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM)) return pam_syslog_pam_error(pamh, LOG_WARNING, r, "Failed to unset %s environment variable: @PAMERR@", key); @@ -563,7 +562,7 @@ static int update_environment(pam_handle_t *pamh, const char *key, const char *v return PAM_SUCCESS; } - r = pam_misc_setenv(pamh, key, value, /* readonly= */ false); + r = pam_putenv_assign(pamh, key, value); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable %s: @PAMERR@", key); @@ -588,7 +587,7 @@ static int propagate_credential_to_environment(pam_handle_t *pamh, bool debug, c return PAM_SUCCESS; } - r = pam_misc_setenv(pamh, varname, value, 0); + r = pam_putenv_assign(pamh, varname, value); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable %s: @PAMERR@", varname); @@ -610,7 +609,7 @@ static bool validate_runtime_directory(pam_handle_t *pamh, const char *path, uid * otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */ if (!path_is_absolute(path)) { - pam_syslog(pamh, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); + sym_pam_syslog(pamh, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); goto fail; } @@ -620,19 +619,19 @@ static bool validate_runtime_directory(pam_handle_t *pamh, const char *path, uid } if (!S_ISDIR(st.st_mode)) { - pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); + sym_pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); goto fail; } if (st.st_uid != uid) { - pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); + sym_pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); goto fail; } return true; fail: - pam_syslog(pamh, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); + sym_pam_syslog(pamh, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); return false; } @@ -642,7 +641,7 @@ static int pam_putenv_and_log(pam_handle_t *pamh, const char *e, bool debug) { assert(pamh); assert(e); - r = pam_putenv(pamh, e); + r = sym_pam_putenv(pamh, e); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable %s: @PAMERR@", e); @@ -992,7 +991,7 @@ static void session_context_mangle( c->area = ur->default_area; if (!isempty(c->area) && !filename_is_valid(c->area)) { - pam_syslog(pamh, LOG_WARNING, "Specified area '%s' is not a valid filename, ignoring area request.", c->area); + sym_pam_syslog(pamh, LOG_WARNING, "Specified area '%s' is not a valid filename, ignoring area request.", c->area); c->area = NULL; } @@ -1048,7 +1047,7 @@ static void session_context_mangle( if (streq(c->class, "user")) c->class = "user-incomplete"; else - pam_syslog(pamh, LOG_WARNING, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class); + sym_pam_syslog(pamh, LOG_WARNING, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class); } c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host); @@ -1253,8 +1252,7 @@ static int register_session( return PAM_SUCCESS; } - pam_syslog(pamh, LOG_ERR, - "Failed to create session: %s", bus_error_message(&error, r)); + sym_pam_syslog(pamh, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r)); return PAM_SESSION_ERR; } @@ -1282,7 +1280,7 @@ static int register_session( if (fd < 0) return pam_syslog_errno(pamh, LOG_ERR, errno, "Failed to dup session fd: %m"); - r = pam_set_data(pamh, "systemd.session-fd", FD_TO_PTR(fd), NULL); + r = sym_pam_set_data(pamh, "systemd.session-fd", FD_TO_PTR(fd), NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to install session fd: @PAMERR@"); TAKE_FD(fd); @@ -1580,7 +1578,7 @@ static int setup_environment( pam_syslog_errno(pamh, LOG_WARNING, r, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory: %m", j, area); /* Also tell the user directly at login, but a bit more vague */ - pam_info(pamh, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area); + sym_pam_info(pamh, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area); area = NULL; } else { /* Validate that the target is definitely owned by user */ @@ -1589,10 +1587,10 @@ static int setup_environment( return pam_syslog_errno(pamh, LOG_ERR, errno, "Unable to fstat() target area directory '%s': %m", ha); if (st.st_uid != ur->uid) { - pam_syslog(pamh, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); + sym_pam_syslog(pamh, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); /* Also tell the user directly at login. */ - pam_info(pamh, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); + sym_pam_info(pamh, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); area = NULL; } else { /* All good, now make a copy of the area string, since we quite likely are @@ -1631,13 +1629,13 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe if (!streq_ptr(session_type, "tty")) return PAM_SUCCESS; - const char *e = pam_getenv(pamh, "TERM"); + const char *e = sym_pam_getenv(pamh, "TERM"); if (!e) e = getenv("TERM"); if (streq_ptr(e, "dumb")) return PAM_SUCCESS; - /* NB: we output directly to stdout, instead of going via pam_info() or so, because that's too + /* NB: we output directly to stdout, instead of going via sym_pam_info() or so, because that's too * high-level for us, as it suffixes the output with a newline, expecting a full blown text message * as prompt string, not just an ANSI sequence. Note that PAM's conv_misc() actually goes to stdout * anyway, hence let's do so here too, but only after careful validation. */ @@ -1657,7 +1655,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe sd_id128_t osc_id; r = osc_context_open_session( ur->user_name, - pam_getenv(pamh, "XDG_SESSION_ID"), + sym_pam_getenv(pamh, "XDG_SESSION_ID"), &osc, &osc_id); if (r < 0) @@ -1672,7 +1670,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe if (!osc_id_copy) return pam_log_oom(pamh); - r = pam_set_data(pamh, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM OSC sequence ID data: @PAMERR@"); @@ -1680,7 +1678,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe TAKE_PTR(osc_id_copy); if (tty_opath_fd >= 0) { - r = pam_set_data(pamh, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close); + r = sym_pam_set_data(pamh, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM OSC sequence fd data: @PAMERR@"); @@ -1698,7 +1696,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { const void *p; int tty_opath_fd = -EBADF; - r = pam_get_data(pamh, "systemd.osc-context-fd", &p); + r = sym_pam_get_data(pamh, "systemd.osc-context-fd", &p); if (r == PAM_SUCCESS) tty_opath_fd = PTR_TO_FD(p); else if (r != PAM_NO_MODULE_DATA) @@ -1707,7 +1705,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { return PAM_SUCCESS; const sd_id128_t *osc_id = NULL; - r = pam_get_data(pamh, "systemd.osc-context-id", (const void**) &osc_id); + r = sym_pam_get_data(pamh, "systemd.osc-context-id", (const void**) &osc_id); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM OSC context id data: @PAMERR@"); if (!osc_id) @@ -1721,7 +1719,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { } /* /bin/login calls us with fds 0, 1, 2 closed, which is just weird. Let's step outside of that - * range, just in case pam_syslog() or so logs to stderr */ + * range, just in case sym_pam_syslog() or so logs to stderr */ fd = fd_move_above_stdio(fd); /* Safety check, let's verify this is a valid TTY we just opened */ @@ -1849,6 +1847,10 @@ _public_ PAM_EXTERN int pam_sm_close_session( assert(pamh); + r = dlopen_libpam(LOG_DEBUG); + if (r < 0) + return PAM_SERVICE_ERR; + pam_log_setup(); if (parse_argv(pamh, @@ -1866,7 +1868,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( (void) close_osc_context(pamh, debug); - id = pam_getenv(pamh, "XDG_SESSION_ID"); + id = sym_pam_getenv(pamh, "XDG_SESSION_ID"); if (id) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; bool done = false; diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 93ccddc25b..9d44863b02 100644 --- a/src/login/pam_systemd_loadkey.c +++ b/src/login/pam_systemd_loadkey.c @@ -41,7 +41,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( else if (streq(argv[i], "debug")) debug = true; else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } pam_debug_syslog(pamh, debug, "pam-systemd-loadkey: initializing..."); @@ -81,7 +81,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( } else if (passwords_len > 1) pam_debug_syslog(pamh, debug, "Multiple passwords found in the key. Using the last one."); - r = pam_set_item(pamh, PAM_AUTHTOK, passwords[passwords_len - 1]); + r = sym_pam_set_item(pamh, PAM_AUTHTOK, passwords[passwords_len - 1]); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index 9728c3e00d..37f2caf31e 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -25,10 +25,15 @@ static void *libpam_dl = NULL; DLSYM_PROTOTYPE(pam_acct_mgmt) = NULL; DLSYM_PROTOTYPE(pam_close_session) = NULL; DLSYM_PROTOTYPE(pam_end) = NULL; +DLSYM_PROTOTYPE(pam_get_authtok_noverify) = NULL; +DLSYM_PROTOTYPE(pam_get_authtok_verify) = NULL; DLSYM_PROTOTYPE(pam_get_data) = NULL; DLSYM_PROTOTYPE(pam_get_item) = NULL; +DLSYM_PROTOTYPE(pam_get_user) = NULL; +DLSYM_PROTOTYPE(pam_getenv) = NULL; DLSYM_PROTOTYPE(pam_getenvlist) = NULL; DLSYM_PROTOTYPE(pam_open_session) = NULL; +DLSYM_PROTOTYPE(pam_prompt) = NULL; DLSYM_PROTOTYPE(pam_putenv) = NULL; DLSYM_PROTOTYPE(pam_set_data) = NULL; DLSYM_PROTOTYPE(pam_set_item) = NULL; @@ -361,6 +366,19 @@ int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, cons return PAM_SUCCESS; } +int pam_putenv_assign(pam_handle_t *pamh, const char *name, const char *value) { + _cleanup_(erase_and_freep) char *s = NULL; + + assert(pamh); + assert(name); + assert(value); + + if (asprintf(&s, "%s=%s", name, value) < 0) + return PAM_BUF_ERR; + + return sym_pam_putenv(pamh, s); +} + #endif int dlopen_libpam(int log_level) { @@ -378,10 +396,15 @@ int dlopen_libpam(int log_level) { DLSYM_ARG(pam_acct_mgmt), DLSYM_ARG(pam_close_session), DLSYM_ARG(pam_end), + DLSYM_ARG(pam_get_authtok_noverify), + DLSYM_ARG(pam_get_authtok_verify), DLSYM_ARG(pam_get_data), DLSYM_ARG(pam_get_item), + DLSYM_ARG(pam_get_user), + DLSYM_ARG(pam_getenv), DLSYM_ARG(pam_getenvlist), DLSYM_ARG(pam_open_session), + DLSYM_ARG(pam_prompt), DLSYM_ARG(pam_putenv), DLSYM_ARG(pam_set_data), DLSYM_ARG(pam_set_item), diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 96d522d8f9..ca3ed0c7c8 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -6,7 +6,7 @@ #if HAVE_PAM #include #include -#include /* IWYU pragma: export */ +#include /* IWYU pragma: export */ #include #include "dlfcn-util.h" @@ -14,10 +14,15 @@ extern DLSYM_PROTOTYPE(pam_acct_mgmt); extern DLSYM_PROTOTYPE(pam_close_session); extern DLSYM_PROTOTYPE(pam_end); +extern DLSYM_PROTOTYPE(pam_get_authtok_noverify); +extern DLSYM_PROTOTYPE(pam_get_authtok_verify); extern DLSYM_PROTOTYPE(pam_get_data); extern DLSYM_PROTOTYPE(pam_get_item); +extern DLSYM_PROTOTYPE(pam_get_user); +extern DLSYM_PROTOTYPE(pam_getenv); extern DLSYM_PROTOTYPE(pam_getenvlist); extern DLSYM_PROTOTYPE(pam_open_session); +extern DLSYM_PROTOTYPE(pam_prompt); extern DLSYM_PROTOTYPE(pam_putenv); extern DLSYM_PROTOTYPE(pam_set_data); extern DLSYM_PROTOTYPE(pam_set_item); @@ -27,6 +32,11 @@ extern DLSYM_PROTOTYPE(pam_strerror); extern DLSYM_PROTOTYPE(pam_syslog); extern DLSYM_PROTOTYPE(pam_vsyslog); +/* sym_pam_prompt() replacement for the pam_info() macro from , which expands to a direct + * pam_prompt() call. */ +#define sym_pam_info(pamh, fmt, ...) \ + sym_pam_prompt((pamh), PAM_TEXT_INFO, NULL, (fmt), ## __VA_ARGS__) + void pam_log_setup(void); int errno_to_pam_error(int error) _const_; @@ -90,6 +100,10 @@ int pam_get_data_many_internal(pam_handle_t *pamh, ...) _sentinel_; int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, const char *fmt, ...) _printf_(4,5); +/* Equivalent of pam_misc_setenv(pamh, name, value, 0), without the libpam_misc dep — builds "name=value" + * and hands it to sym_pam_putenv(), then erases the buffer before freeing in case it carried a secret. */ +int pam_putenv_assign(pam_handle_t *pamh, const char *name, const char *value); + #endif int dlopen_libpam(int log_level); From 0af0161f554b822d2f72eb6cf343b520924bd72b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 18:24:11 +0000 Subject: [PATCH 04/12] test-bus-marshal: dlopen() glib and libdbus instead of linking directly The test only uses 9 symbols (5 from glib, 4 from libdbus) for its interop checks; dlopen them at runtime so the binary no longer carries a hard link-time dependency on either library. Headers are still pulled in through the *_cflags partial dependencies for the type declarations. While we're at it, drop the compat glue for glib 2.36 which is long obsolete at this point. --- meson.build | 4 ++ src/libsystemd/meson.build | 8 +-- src/libsystemd/sd-bus/test-bus-marshal.c | 62 +++++++++++++++++------- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/meson.build b/meson.build index 92129bee7d..05739e517b 100644 --- a/meson.build +++ b/meson.build @@ -1319,11 +1319,15 @@ libgobject = dependency('gobject-2.0', libgio = dependency('gio-2.0', required : get_option('glib')) conf.set10('HAVE_GLIB', libglib.found() and libgobject.found() and libgio.found()) +libglib_cflags = libglib.partial_dependency(includes: true, compile_args: true) +libgobject_cflags = libgobject.partial_dependency(includes: true, compile_args: true) +libgio_cflags = libgio.partial_dependency(includes: true, compile_args: true) libdbus = dependency('dbus-1', version : '>= 1.3.2', required : get_option('dbus')) conf.set10('HAVE_DBUS', libdbus.found()) +libdbus_cflags = libdbus.partial_dependency(includes: true, compile_args: true) dbusdatadir = libdbus.get_variable(pkgconfig: 'datadir', default_value: datadir) / 'dbus-1' diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 2fab547194..2c86a23106 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -221,10 +221,10 @@ libsystemd_tests += [ { 'sources' : files('sd-bus/test-bus-marshal.c'), 'dependencies' : [ - libdbus, - libgio, - libglib, - libgobject, + libdbus_cflags, + libgio_cflags, + libglib_cflags, + libgobject_cflags, libm, threads, ], diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index e26ca43344..8229eef54a 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -23,6 +23,7 @@ REENABLE_WARNING #include "bus-label.h" #include "bus-message.h" #include "bus-util.h" +#include "dlfcn-util.h" #include "escape.h" #include "fd-util.h" #include "log.h" @@ -31,6 +32,40 @@ REENABLE_WARNING #include "stat-util.h" #include "tests.h" +#if HAVE_GLIB +static void *glib_dl = NULL; +static DLSYM_PROTOTYPE(g_dbus_message_new_from_blob) = NULL; +static DLSYM_PROTOTYPE(g_dbus_message_print) = NULL; +static DLSYM_PROTOTYPE(g_free) = NULL; +static DLSYM_PROTOTYPE(g_object_unref) = NULL; + +static int dlopen_glib(void) { + return dlopen_many_sym_or_warn( + &glib_dl, "libgio-2.0.so.0", LOG_DEBUG, + DLSYM_ARG(g_dbus_message_new_from_blob), + DLSYM_ARG(g_dbus_message_print), + DLSYM_ARG(g_free), + DLSYM_ARG(g_object_unref)); +} +#endif + +#if HAVE_DBUS +static void *libdbus_dl = NULL; +static DLSYM_PROTOTYPE(dbus_error_init) = NULL; +static DLSYM_PROTOTYPE(dbus_error_free) = NULL; +static DLSYM_PROTOTYPE(dbus_message_demarshal) = NULL; +static DLSYM_PROTOTYPE(dbus_message_unref) = NULL; + +static int dlopen_libdbus(void) { + return dlopen_many_sym_or_warn( + &libdbus_dl, "libdbus-1.so.3", LOG_DEBUG, + DLSYM_ARG(dbus_error_init), + DLSYM_ARG(dbus_error_free), + DLSYM_ARG(dbus_message_demarshal), + DLSYM_ARG(dbus_message_unref)); +} +#endif + static void test_bus_path_encode_unique(void) { _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL; @@ -322,39 +357,32 @@ int main(int argc, char *argv[]) { log_info("message size = %zu, contents =\n%s", sz, h); #if HAVE_GLIB - /* Work-around for asan bug. See c8d980a3e962aba2ea3a4cedf75fa94890a6d746. */ -#if !HAS_FEATURE_ADDRESS_SANITIZER - { + if (dlopen_glib() >= 0) { GDBusMessage *g; char *p; -#if !defined(GLIB_VERSION_2_36) - g_type_init(); -#endif - - g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); - p = g_dbus_message_print(g, 0); + g = sym_g_dbus_message_new_from_blob(buffer, sz, 0, NULL); + p = sym_g_dbus_message_print(g, 0); log_info("%s", p); - g_free(p); - g_object_unref(g); + sym_g_free(p); + sym_g_object_unref(g); } #endif -#endif #if HAVE_DBUS - { + if (dlopen_libdbus() >= 0) { DBusMessage *w; DBusError error; - dbus_error_init(&error); + sym_dbus_error_init(&error); - w = dbus_message_demarshal(buffer, sz, &error); + w = sym_dbus_message_demarshal(buffer, sz, &error); if (!w) log_error("%s", error.message); else - dbus_message_unref(w); + sym_dbus_message_unref(w); - dbus_error_free(&error); + sym_dbus_error_free(&error); } #endif From bcb7cf4a61f4ac5c731b32b30830bea55a4c456b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 08:57:24 +0000 Subject: [PATCH 05/12] lock-util: Simplify timeout for lock_generic_with_timeout() We can just put a timeout on the parent process completing rather than doing it inside the subprocess. --- src/basic/lock-util.c | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index bd267eabf9..24eee3b9e4 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -227,25 +226,6 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo if (r < 0) return log_error_errno(r, "Failed to flock block device in child process: %m"); if (r == 0) { - struct sigevent sev = { - .sigev_notify = SIGEV_SIGNAL, - .sigev_signo = SIGALRM, - }; - timer_t id; - - if (timer_create(CLOCK_MONOTONIC, &sev, &id) < 0) { - log_error_errno(errno, "Failed to allocate CLOCK_MONOTONIC timer: %m"); - _exit(EXIT_FAILURE); - } - - struct itimerspec its = {}; - timespec_store(&its.it_value, timeout); - - if (timer_settime(id, /* flags= */ 0, &its, NULL) < 0) { - log_error_errno(errno, "Failed to start CLOCK_MONOTONIC timer: %m"); - _exit(EXIT_FAILURE); - } - if (lock_generic(fd, type, operation) < 0) { log_error_errno(errno, "Unable to get an exclusive lock on the device: %m"); _exit(EXIT_FAILURE); @@ -255,7 +235,7 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo } siginfo_t status; - r = pidref_wait_for_terminate(&pidref, &status); + r = pidref_wait_for_terminate_full(&pidref, timeout, &status); if (r < 0) return r; @@ -270,11 +250,6 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo return 0; case CLD_KILLED: - if (status.si_status == SIGALRM) - return -ETIMEDOUT; - - _fallthrough_; - case CLD_DUMPED: return -EPROTO; From 7d2925fcde70ed221658e01ffb6f2797061d3520 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 17 May 2026 11:28:00 +0000 Subject: [PATCH 06/12] color-util: simplify hsv_to_rgb, fix rgb_to_hsv negative-hue wrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In hsv_to_rgb, restructure the conversion around the sector index k = (int)(h/60) and fractional offset f = h/60 - k. The auxiliary x value becomes c * (k & 1 ? 1.0 - f : f) and the six branches turn into a switch on k. This drops the two xfmod() calls that were doing the modulo work, in exchange for a single assert(h >= 0 && h < 360) — all in-tree callers satisfy this and never relied on the wrap. In rgb_to_hsv, the two xfmod() calls were no-ops (their arguments were always within the divisor's magnitude). The trailing xfmod(*ret_h, 360) appeared to be wrapping negative hues from the r-max branch back into [0, 360), but fmod is sign-preserving so it never did. Drop the no-ops and add an explicit +360 wrap so magenta (1, 0, 1) now yields h ≈ 300 instead of -60. Extend the tests to cover all six primary/secondary colors at sector boundaries, all six sector midpoints (to catch any future inversion of the ramp direction), the h-near-360 edge of the last sector, and the rgb_to_hsv negative-wrap path via magenta. Switch the new and existing integer-channel checks to ASSERT_EQ from tests.h; the double-typed h/s/v range checks stay on ASSERT_TRUE since the ASSERT_* comparison macros only support integer types. Co-developed-by: Claude Opus 4.7 --- src/shared/color-util.c | 40 +++++++----- src/test/test-color-util.c | 126 ++++++++++++++++++++++++++++--------- 2 files changed, 120 insertions(+), 46 deletions(-) diff --git a/src/shared/color-util.c b/src/shared/color-util.c index 1912785954..ed417a1a20 100644 --- a/src/shared/color-util.c +++ b/src/shared/color-util.c @@ -32,13 +32,15 @@ void rgb_to_hsv(double r, double g, double b, if (ret_h) { if (delta > 0) { if (r >= max_color) - *ret_h = 60 * fmod((g - b) / delta, 6); + *ret_h = 60 * (g - b) / delta; else if (g >= max_color) *ret_h = 60 * (((b - r) / delta) + 2); - else if (b >= max_color) + else /* b >= max_color */ *ret_h = 60 * (((r - g) / delta) + 4); - *ret_h = fmod(*ret_h, 360); + /* The r-max branch produces (-60, 60); fold the negative half up. */ + if (*ret_h < 0) + *ret_h += 360; } else *ret_h = NAN; } @@ -49,29 +51,33 @@ void hsv_to_rgb(double h, double s, double v, double c, x, m, r, g, b; + assert(h >= 0 && h <= 360); assert(s >= 0 && s <= 100); assert(v >= 0 && v <= 100); assert(ret_r); assert(ret_g); assert(ret_b); - h = fmod(h, 360); c = (s / 100.0) * (v / 100.0); - x = c * (1 - fabs(fmod(h / 60.0, 2) - 1)); m = (v / 100) - c; - if (h >= 0 && h < 60) - r = c, g = x, b = 0.0; - else if (h >= 60 && h < 120) - r = x, g = c, b = 0.0; - else if (h >= 120 && h < 180) - r = 0.0, g = c, b = x; - else if (h >= 180 && h < 240) - r = 0.0, g = x, b = c; - else if (h >= 240 && h < 300) - r = x, g = 0.0, b = c; - else - r = c, g = 0.0, b = x; + /* Split h into sector index k ∈ [0, 6] and fractional offset f ∈ [0, 1) within the sector. + * Within each sector exactly one of {r, g, b} equals c, one equals 0, and the third (x) + * ramps linearly between them — ascending in even sectors, descending in odd. h == 360 is + * the cyclic boundary equivalent to h == 0, and maps to sector 0. */ + double seg = h / 60.0; + int k = (int) seg % 6; + double f = seg - (int) seg; + x = c * (k & 1 ? 1.0 - f : f); + + switch (k) { + case 0: r = c; g = x; b = 0.0; break; + case 1: r = x; g = c; b = 0.0; break; + case 2: r = 0.0; g = c; b = x; break; + case 3: r = 0.0; g = x; b = c; break; + case 4: r = x; g = 0.0; b = c; break; + default: r = c; g = 0.0; b = x; break; + } *ret_r = (uint8_t) ((r + m) * 255); *ret_g = (uint8_t) ((g + m) * 255); diff --git a/src/test/test-color-util.c b/src/test/test-color-util.c index 3d00d8719d..a1369f4ef5 100644 --- a/src/test/test-color-util.c +++ b/src/test/test-color-util.c @@ -6,58 +6,126 @@ TEST(hsv_to_rgb) { uint8_t r, g, b; + /* Black at any hue. */ hsv_to_rgb(0, 0, 0, &r, &g, &b); - assert(r == 0 && g == 0 && b == 0); + ASSERT_EQ(r, 0); + ASSERT_EQ(g, 0); + ASSERT_EQ(b, 0); hsv_to_rgb(60, 0, 0, &r, &g, &b); - assert(r == 0 && g == 0 && b == 0); + ASSERT_EQ(r, 0); + ASSERT_EQ(g, 0); + ASSERT_EQ(b, 0); + /* White: s=0 must ignore hue. */ hsv_to_rgb(0, 0, 100, &r, &g, &b); - assert(r == 255 && g == 255 && b == 255); + ASSERT_EQ(r, 255); + ASSERT_EQ(g, 255); + ASSERT_EQ(b, 255); - hsv_to_rgb(0, 100, 100, &r, &g, &b); - assert(r == 255 && g == 0 && b == 0); + hsv_to_rgb(180, 0, 100, &r, &g, &b); + ASSERT_EQ(r, 255); + ASSERT_EQ(g, 255); + ASSERT_EQ(b, 255); - hsv_to_rgb(120, 100, 100, &r, &g, &b); - assert(r == 0 && g == 255 && b == 0); + /* Pure primary and secondary colors — one per sector boundary. */ + hsv_to_rgb(0, 100, 100, &r, &g, &b); /* red */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 0); + hsv_to_rgb(60, 100, 100, &r, &g, &b); /* yellow */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(120, 100, 100, &r, &g, &b); /* green */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(180, 100, 100, &r, &g, &b); /* cyan */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 255); + hsv_to_rgb(240, 100, 100, &r, &g, &b); /* blue */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + hsv_to_rgb(300, 100, 100, &r, &g, &b); /* magenta */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); - hsv_to_rgb(240, 100, 100, &r, &g, &b); - assert(r == 0 && g == 0 && b == 255); + /* Sector midpoints. Catches inverted ramp direction (k & 1 ? 1-f : f) in any single + * sector — a regression would shift exactly one channel by ±half-intensity. */ + hsv_to_rgb(30, 100, 100, &r, &g, &b); /* orange — sector 0 */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 127); ASSERT_EQ(b, 0); + hsv_to_rgb(90, 100, 100, &r, &g, &b); /* chartreuse — sector 1 */ + ASSERT_EQ(r, 127); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(150, 100, 100, &r, &g, &b); /* spring green — sector 2 */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 127); + hsv_to_rgb(210, 100, 100, &r, &g, &b); /* azure — sector 3 */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 127); ASSERT_EQ(b, 255); + hsv_to_rgb(270, 100, 100, &r, &g, &b); /* violet — sector 4 */ + ASSERT_EQ(r, 127); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + hsv_to_rgb(330, 100, 100, &r, &g, &b); /* rose — sector 5 */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 127); + /* Just below 360 — ensures (int) seg stays in sector 5 and doesn't wrap to 6. */ + hsv_to_rgb(359.5, 100, 100, &r, &g, &b); + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 2); + + /* Exactly 360 — cyclic boundary, equivalent to 0 (red). Callers like color_for_pcr() in + * pcrlock.c can reach this via floating-point arithmetic (e.g. 360.0/23*23 == 360.0). */ + hsv_to_rgb(360, 100, 100, &r, &g, &b); + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 0); + + /* Non-trivial s/v: regression check for the multiply-and-cast path. */ hsv_to_rgb(311, 52, 62, &r, &g, &b); - assert(r == 158 && g == 75 && b == 143); + ASSERT_EQ(r, 158); ASSERT_EQ(g, 75); ASSERT_EQ(b, 143); } TEST(rgb_to_hsv) { double h, s, v; + + /* Grayscale: delta == 0, h is NaN, s == 0. */ rgb_to_hsv(0, 0, 0, &h, &s, &v); - assert(s <= 0); - assert(v <= 0); + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v <= 0); rgb_to_hsv(1, 1, 1, &h, &s, &v); - assert(s <= 0); - assert(v >= 100); + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v >= 100); - rgb_to_hsv(1, 0, 0, &h, &s, &v); - assert(h >= 359 || h <= 1); - assert(s >= 100); - assert(v >= 100); + rgb_to_hsv(0.5, 0.5, 0.5, &h, &s, &v); + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v >= 49 && v <= 51); - rgb_to_hsv(0, 1, 0, &h, &s, &v); - assert(h >= 119 && h <= 121); - assert(s >= 100); - assert(v >= 100); + /* Pure primary colors. */ + rgb_to_hsv(1, 0, 0, &h, &s, &v); /* red */ + ASSERT_TRUE(h >= 0 && h <= 1); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); - rgb_to_hsv(0, 0, 1, &h, &s, &v); - assert(h >= 239 && h <= 241); - assert(s >= 100); - assert(v >= 100); + rgb_to_hsv(0, 1, 0, &h, &s, &v); /* green */ + ASSERT_TRUE(h >= 119 && h <= 121); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + rgb_to_hsv(0, 0, 1, &h, &s, &v); /* blue */ + ASSERT_TRUE(h >= 239 && h <= 241); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + /* Pure secondary colors — each exercises a different "max" branch. Magenta exercises + * the negative-hue wrap from the r-max branch (raw value is -60). */ + rgb_to_hsv(1, 1, 0, &h, &s, &v); /* yellow — r-max branch, positive */ + ASSERT_TRUE(h >= 59 && h <= 61); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0, 1, 1, &h, &s, &v); /* cyan — g-max branch */ + ASSERT_TRUE(h >= 179 && h <= 181); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(1, 0, 1, &h, &s, &v); /* magenta — r-max branch, wrapped */ + ASSERT_TRUE(h >= 299 && h <= 301); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + /* Mixed values. */ rgb_to_hsv(0.5, 0.6, 0.7, &h, &s, &v); - assert(h >= 209 && h <= 211); - assert(s >= 28 && s <= 31); - assert(v >= 69 && v <= 71); + ASSERT_TRUE(h >= 209 && h <= 211); + ASSERT_TRUE(s >= 28 && s <= 31); + ASSERT_TRUE(v >= 69 && v <= 71); } DEFINE_TEST_MAIN(LOG_DEBUG); From b2bfe5b4748a804e7ea25a4925102412967a5e4e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 11:06:21 +0000 Subject: [PATCH 07/12] tree-wide: Use our own macros instead of fabs()/fmax()/fmin() To make this work, ABS() is made generic so it also works on floats and doubles. While at it, fold the __ABS_INTEGER indirection and the assert_cc(sizeof(long long) == sizeof(intmax_t)) away. The previous form switched between __builtin_llabs (clang) and __builtin_imaxabs (gcc), with the assert keeping the two paths behaviorally identical on every platform we build for. imaxabs was originally chosen because intmax_t is conceptually the widest signed integer type the platform exposes, but the _Generic ABS already casts to (long long) before the call, so the extra width imaxabs could in theory carry was being narrowed away immediately anyway. With both paths collapsed to __builtin_llabs((long long) (a)), the size relationship between long long and intmax_t is no longer relevant. Also add explicit unsigned long long / unsigned long / unsigned int cases that pass the argument through unchanged. The previous default branch cast unsigned values to (long long); for values above LLONG_MAX this reinterprets them as negative, and __builtin_llabs(LLONG_MIN) is UB. Unsigned values are already non-negative, so passing them through is both correct and avoids the narrowing. Smaller unsigned types (unsigned char, unsigned short) still go through the default branch but promote to int first and fit in long long losslessly. Co-developed-by: Claude Opus 4.7 --- src/fundamental/macro-fundamental.h | 14 ++++++++------ src/libsystemd/sd-bus/test-bus-marshal.c | 4 +--- src/libsystemd/sd-json/test-json.c | 20 ++++++++++---------- src/shared/color-util.c | 4 ++-- src/test/test-parse-util.c | 5 ++--- src/test/test-random-util.c | 2 +- src/timesync/timesyncd-manager.c | 14 +++++++------- 7 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 82e2cb4c3b..12f2aa81dc 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -180,12 +180,14 @@ UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \ }) -#ifdef __clang__ -# define ABS(a) __builtin_llabs(a) -#else -# define ABS(a) __builtin_imaxabs(a) -#endif -assert_cc(sizeof(long long) == sizeof(intmax_t)); +#define ABS(a) _Generic((a), \ + float: __builtin_fabsf((float) (a)), \ + double: __builtin_fabs((double) (a)), \ + long double: __builtin_fabsl((long double) (a)), \ + unsigned long long: (a), \ + unsigned long: (a), \ + unsigned int: (a), \ + default: __builtin_llabs((long long) (a))) #define IS_UNSIGNED_INTEGER_TYPE(type) \ (__builtin_types_compatible_p(typeof(type), unsigned char) || \ diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 8229eef54a..5c2fca0f83 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - /* We make an exception here to our usual "include system headers first" rule because we need one of these * macros to disable a warning triggered by the glib headers. */ #include "macro-fundamental.h" @@ -479,7 +477,7 @@ int main(int argc, char *argv[]) { assert_se(r > 0); assert_se(streq(x, "foo")); assert_se(u64 == 815ULL); - assert_se(fabs(dbl - 47.0) < 0.1); + assert_se(ABS(dbl - 47.0) < 0.1); assert_se(streq(y, "/")); r = sd_bus_message_peek_type(m, NULL, NULL); diff --git a/src/libsystemd/sd-json/test-json.c b/src/libsystemd/sd-json/test-json.c index 3be4b09660..59724b00c3 100644 --- a/src/libsystemd/sd-json/test-json.c +++ b/src/libsystemd/sd-json/test-json.c @@ -60,8 +60,8 @@ static void test_tokenizer_one(const char *data, ...) { d = va_arg(ap, double); - assert_se(fabs(d - v.real) < 1e-10 || - fabs((d - v.real) / v.real) < 1e-10); + assert_se(ABS(d - v.real) < 1e-10 || + ABS((d - v.real) / v.real) < 1e-10); } else if (t == JSON_TOKEN_INTEGER) { int64_t i; @@ -242,7 +242,7 @@ static void test_2(sd_json_variant *v) { /* has thisisaverylongproperty */ p = sd_json_variant_by_key(v, "thisisaverylongproperty"); - assert_se(p && sd_json_variant_type(p) == SD_JSON_VARIANT_REAL && fabs(sd_json_variant_real(p) - 1.27) < 0.001); + assert_se(p && sd_json_variant_type(p) == SD_JSON_VARIANT_REAL && ABS(sd_json_variant_real(p) - 1.27) < 0.001); } static void test_zeroes(sd_json_variant *v) { @@ -690,14 +690,14 @@ static void test_float_match(sd_json_variant *v) { assert_se(sd_json_variant_is_array(v)); assert_se(sd_json_variant_elements(v) == 11); assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 0)))); - assert_se(fabs(1.0 - (DBL_MIN / sd_json_variant_real(sd_json_variant_by_index(v, 0)))) <= delta); + assert_se(ABS(1.0 - (DBL_MIN / sd_json_variant_real(sd_json_variant_by_index(v, 0)))) <= delta); assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 1)))); - assert_se(fabs(1.0 - (DBL_MAX / sd_json_variant_real(sd_json_variant_by_index(v, 1)))) <= delta); + assert_se(ABS(1.0 - (DBL_MAX / sd_json_variant_real(sd_json_variant_by_index(v, 1)))) <= delta); assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 2))); /* nan is not supported by json → null */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 3))); /* +inf is not supported by json → null */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 4))); /* -inf is not supported by json → null */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 5)) || - fabs(1.0 - (HUGE_VAL / sd_json_variant_real(sd_json_variant_by_index(v, 5)))) <= delta); /* HUGE_VAL might be +inf, but might also be something else */ + ABS(1.0 - (HUGE_VAL / sd_json_variant_real(sd_json_variant_by_index(v, 5)))) <= delta); /* HUGE_VAL might be +inf, but might also be something else */ assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 6)) && sd_json_variant_is_integer(sd_json_variant_by_index(v, 6)) && sd_json_variant_integer(sd_json_variant_by_index(v, 6)) == 0); @@ -710,11 +710,11 @@ static void test_float_match(sd_json_variant *v) { assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 9)) && !sd_json_variant_is_integer(sd_json_variant_by_index(v, 9))); assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 9)))); - assert_se(fabs(1.0 - (DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 9)))) <= delta); + assert_se(ABS(1.0 - (DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 9)))) <= delta); assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 10)) && !sd_json_variant_is_integer(sd_json_variant_by_index(v, 10))); assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 10)))); - assert_se(fabs(1.0 - (-DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 10)))) <= delta); + assert_se(ABS(1.0 - (-DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 10)))) <= delta); } TEST(float) { @@ -1080,8 +1080,8 @@ TEST(json_dispatch_double) { /* flags= */ 0, &data) >= 0); - assert_se(fabs(data.x1 - 0.5) < 0.01); - assert_se(fabs(data.x2 + 0.5) < 0.01); + assert_se(ABS(data.x1 - 0.5) < 0.01); + assert_se(ABS(data.x2 + 0.5) < 0.01); assert_se(isinf(data.x3)); assert_se(data.x3 > 0); assert_se(isinf(data.x4)); diff --git a/src/shared/color-util.c b/src/shared/color-util.c index ed417a1a20..f2add33b1d 100644 --- a/src/shared/color-util.c +++ b/src/shared/color-util.c @@ -11,8 +11,8 @@ void rgb_to_hsv(double r, double g, double b, assert(g >= 0 && g <= 1); assert(b >= 0 && b <= 1); - double max_color = fmax(r, fmax(g, b)); - double min_color = fmin(r, fmin(g, b)); + double max_color = MAX(r, MAX(g, b)); + double min_color = MIN(r, MIN(g, b)); double delta = max_color - min_color; if (ret_v) diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c index 0c2cc500b1..bc74856fde 100644 --- a/src/test/test-parse-util.c +++ b/src/test/test-parse-util.c @@ -2,7 +2,6 @@ #include #include -#include #include #include "capability-util.h" @@ -660,7 +659,7 @@ TEST(safe_atod) { ASSERT_ERROR(safe_atod("junk", &d), EINVAL); ASSERT_OK_ZERO(safe_atod("0.2244", &d)); - assert_se(fabs(d - 0.2244) < 0.000001); + assert_se(ABS(d - 0.2244) < 0.000001); ASSERT_ERROR(safe_atod("0,5", &d), EINVAL); ASSERT_ERROR(safe_atod("", &d), EINVAL); @@ -672,7 +671,7 @@ TEST(safe_atod) { return (void) log_tests_skipped_errno(errno, "locale de_DE.utf8 not found"); ASSERT_OK_ZERO(safe_atod("0.2244", &d)); - assert_se(fabs(d - 0.2244) < 0.000001); + assert_se(ABS(d - 0.2244) < 0.000001); ASSERT_ERROR(safe_atod("0,5", &d), EINVAL); ASSERT_ERROR(safe_atod("", &d), EINVAL); diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index e5972718b4..d5043779b7 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -65,7 +65,7 @@ static void test_random_u64_range_one(unsigned mod) { i, count[i], dev, (int) (count[i] / scale), "x"); - assert_se(fabs(dev) < 6); /* 6 sigma is excessive, but this check should be enough to + assert_se(ABS(dev) < 6); /* 6 sigma is excessive, but this check should be enough to * identify catastrophic failure while minimizing false * positives. */ } diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index e4f471e2aa..b0f22516df 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -244,7 +244,7 @@ static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { /* For small deltas, tell the kernel to gradually adjust the system clock to the NTP time, larger * deltas are just directly set. */ - if (fabs(offset) < NTP_MAX_ADJUST) { + if (ABS(offset) < NTP_MAX_ADJUST) { tmx = (struct timex) { .modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR, .status = STA_PLL, @@ -346,7 +346,7 @@ static bool manager_sample_spike_detection(Manager *m, double offset, double del return false; /* always accept offset if we are farther off than the round-trip delay */ - if (fabs(offset) > delay) + if (ABS(offset) > delay) return false; /* we need a few samples before looking at them */ @@ -354,11 +354,11 @@ static bool manager_sample_spike_detection(Manager *m, double offset, double del return false; /* do not accept anything worse than the maximum possible error of the best sample */ - if (fabs(offset) > m->samples[idx_min].delay) + if (ABS(offset) > m->samples[idx_min].delay) return true; /* compare the difference between the current offset to the previous offset and jitter */ - return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; + return ABS(offset - m->samples[idx_cur].offset) > 3 * jitter; } static void manager_adjust_poll(Manager *m, double offset, bool spike) { @@ -371,20 +371,20 @@ static void manager_adjust_poll(Manager *m, double offset, bool spike) { } /* set to minimal poll interval */ - if (!spike && fabs(offset) > NTP_ACCURACY_SEC) { + if (!spike && ABS(offset) > NTP_ACCURACY_SEC) { m->poll_interval_usec = m->poll_interval_min_usec; return; } /* increase polling interval */ - if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) { + if (ABS(offset) < NTP_ACCURACY_SEC * 0.25) { if (m->poll_interval_usec < m->poll_interval_max_usec) m->poll_interval_usec *= 2; return; } /* decrease polling interval */ - if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) { + if (spike || ABS(offset) > NTP_ACCURACY_SEC * 0.75) { if (m->poll_interval_usec > m->poll_interval_min_usec) m->poll_interval_usec /= 2; return; From e6e65dc26157207a6b720fccc49632ac77236384 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 18:33:43 +0000 Subject: [PATCH 08/12] locale-util: dlopen() libintl instead of linking against it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dgettext() lives in libc on glibc and in libintl.so.8 on musl with gettext. Resolve it via dlsym() so neither configuration produces a hard link-time dependency on libintl: try libintl.so.8 first and fall back to RTLD_DEFAULT (which finds dgettext in libc on glibc). The _() macro now expands to a runtime check that returns the untranslated string if dlopen_libintl() has not run successfully, so callers don't have to gate every translatable message on a runtime check. pam_systemd_home — currently the only consumer of _() — calls dlopen_libintl() best-effort from each PAM entry point. The meson find_library('intl') dance is replaced with a has_header() check; the only thing we need at build time is the prototype. --- meson.build | 19 +++++-------------- src/basic/locale-util.c | 31 +++++++++++++++++++++++++++++++ src/basic/locale-util.h | 13 ++++++++++++- src/home/meson.build | 1 - src/home/pam_systemd_home.c | 12 ++++++++++-- src/test/test-dlopen-so.c | 2 ++ 6 files changed, 60 insertions(+), 18 deletions(-) diff --git a/meson.build b/meson.build index 05739e517b..b26ef7979c 100644 --- a/meson.build +++ b/meson.build @@ -983,20 +983,11 @@ librt = cc.find_library('rt') libm = cc.find_library('m') libdl = cc.find_library('dl') -# On some distributions that use musl (e.g. Alpine), libintl.h may be provided by gettext rather than musl. -# In that case, we need to explicitly link with libintl.so. -if cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE') - libintl = [] -else - libintl = cc.find_library('intl') - if not cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE', - dependencies : libintl) - error('dgettext() not found') - endif +# Header presence check only — dgettext itself is resolved via dlopen_libintl() at runtime, so we never +# link against libintl. On glibc dgettext lives in libc; on musl gettext-dev provides libintl.h alongside +# libintl.so.8 which we dlopen() if present. +if not cc.has_header('libintl.h') + error('libintl.h not found (install gettext / gettext-dev)') endif # On some architectures, libatomic is required. But on some installations, diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index 3b869d70f9..6d4493c0bb 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -7,7 +7,12 @@ #include #include +#ifndef __GLIBC__ +#include "sd-dlopen.h" +#endif + #include "dirent-util.h" +#include "dlfcn-util.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" @@ -22,6 +27,32 @@ #include "strv.h" #include "utf8.h" +#ifdef __GLIBC__ +DLSYM_PROTOTYPE(dgettext) = dgettext; +#else +DLSYM_PROTOTYPE(dgettext) = NULL; +#endif + +int dlopen_libintl(int log_level) { +#ifdef __GLIBC__ + return 1; +#else + static void *libintl_dl = NULL; + + SD_ELF_NOTE_DLOPEN( + "intl", + "Support for message translation via gettext", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libintl.so.8"); + + return dlopen_many_sym_or_warn( + &libintl_dl, + "libintl.so.8", + log_level, + DLSYM_ARG(dgettext)); +#endif +} + static char* normalize_locale(const char *name) { const char *e; diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index bf5cbcb439..fb22953f16 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -1,9 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include /* IWYU pragma: export */ #include /* IWYU pragma: export */ #include "basic-forward.h" +#include "dlfcn-util.h" + +/* format_arg(2) propagates the format-string nature of the second argument to the return value, so that + * printf(_("Hello %s"), name) still gets checked. It survives both DLSYM_PROTOTYPE's typeof() and the + * ternary in _() below — verified on gcc and clang. */ +extern DLSYM_PROTOTYPE(dgettext) __attribute__((format_arg(2))); + +int dlopen_libintl(int log_level); typedef enum LocaleVariable { /* We don't list LC_ALL here on purpose. People should be @@ -31,7 +40,9 @@ int get_locales(char ***ret); bool locale_is_valid(const char *name); int locale_is_installed(const char *name); -#define _(String) dgettext(GETTEXT_PACKAGE, String) +/* Falls back to the untranslated string if dlopen_libintl() hasn't run or has failed, so callers don't have + * to gate every translatable message on a runtime check. */ +#define _(String) (sym_dgettext ? sym_dgettext(GETTEXT_PACKAGE, (String)) : (String)) #define N_(String) String bool is_locale_utf8(void); diff --git a/src/home/meson.build b/src/home/meson.build index 631eeb13aa..0b27001c5a 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -115,7 +115,6 @@ modules += [ 'conditions' : ['HAVE_PAM'], 'sources' : pam_systemd_home_sources, 'dependencies' : [ - libintl, libpam_cflags, threads, ], diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 5fe6dcbec2..6e24924e91 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" @@ -792,6 +790,8 @@ _public_ PAM_EXTERN int pam_sm_authenticate( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -857,6 +857,8 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -914,6 +916,8 @@ _public_ PAM_EXTERN int pam_sm_close_session( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, @@ -979,6 +983,8 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -1098,6 +1104,8 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 0eca1356c0..de1d767139 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -18,6 +18,7 @@ #include "libcrypt-util.h" #include "libfido2-util.h" #include "libmount-util.h" +#include "locale-util.h" #include "main-func.h" #include "microhttpd-util.h" #include "module-util.h" @@ -65,6 +66,7 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libblkid, HAVE_BLKID); ASSERT_DLOPEN(dlopen_libcrypt, HAVE_LIBCRYPT); ASSERT_DLOPEN(dlopen_libfido2, HAVE_LIBFIDO2); + ASSERT_OK(dlopen_libintl(LOG_DEBUG)); /* Required to be available at build time. */ ASSERT_DLOPEN(dlopen_libkmod, HAVE_KMOD); ASSERT_DLOPEN(dlopen_libmount, HAVE_LIBMOUNT); ASSERT_DLOPEN(dlopen_libpam, HAVE_PAM); From 5a1644de343f91eed7ebc9e3b32dc6d66019a687 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 11:06:21 +0000 Subject: [PATCH 09/12] home: Use log2u64() over log2() And drop the libm dependency. --- src/home/homed-manager.c | 8 ++++---- src/home/meson.build | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index fc6fe8a4b1..0fcf7b9395 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -41,6 +40,7 @@ #include "homed-manager-bus.h" #include "homed-operation.h" #include "homed-varlink.h" +#include "logarithm.h" #include "mkdir.h" #include "notify-recv.h" #include "ordered-set.h" @@ -1937,9 +1937,9 @@ static int manager_rebalance_calculate(Manager *m) { } /* Scale next rebalancing interval based on the least amount of space of any of the home - * directories. We pick a time in the range 1min … 15min, scaled by log2(min_free), so that: - * 10M → ~0.7min, 100M → ~2.7min, 1G → ~4.6min, 10G → ~6.5min, 100G ~8.4 */ - m->rebalance_interval_usec = (usec_t) CLAMP((LESS_BY(log2(min_free), 22)*15*USEC_PER_MINUTE)/26, + * directories. We pick a time in the range 1min … 15min, scaled by floor(log2(min_free)), + * so that: 10M → ~0.6min, 100M → ~2.3min, 1G → ~4.0min, 10G → ~6.3min, 100G → ~8.1min */ + m->rebalance_interval_usec = (usec_t) CLAMP((LESS_BY(log2u64(min_free), 22u)*15*USEC_PER_MINUTE)/26, 1 * USEC_PER_MINUTE, 15 * USEC_PER_MINUTE); diff --git a/src/home/meson.build b/src/home/meson.build index 0b27001c5a..64c62f1ddd 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -68,7 +68,6 @@ executables += [ 'sources' : systemd_homed_sources, 'extract' : systemd_homed_extract_sources, 'dependencies' : [ - libm, libopenssl_cflags, threads, ], From a14adac42361d1ebaf6c7c2a8a680939f2d1cd64 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 09:54:53 +0000 Subject: [PATCH 10/12] meson: drop libdl, threads, and librt dependencies Our baseline glibc is 2.34, which merged libdl, libpthread (the dependency('threads') target), and librt into libc. Empty .so/.a stubs remain for backward compatibility with old binaries, but new builds resolve dl_*, pthread_*, mq_*, timer_*, etc. directly from libc. On musl the same libraries are likewise empty stubs. Drop the libdl, threads, and librt entries from every meson.build, and remove the now-stale 'Libs.private: -lrt -pthread' from libudev.pc.in since both flags resolve to empty link-time stubs on glibc 2.34+ and musl. Verified with readelf -d that libsystemd.so, libudev.so, and systemd no longer carry DT_NEEDED entries for libdl/libpthread/librt. --- meson.build | 24 ++++-------------------- src/basic/meson.build | 3 --- src/core/meson.build | 3 --- src/coredump/meson.build | 1 - src/creds/meson.build | 1 - src/cryptenroll/meson.build | 1 - src/home/meson.build | 12 +----------- src/import/meson.build | 1 - src/journal-remote/meson.build | 1 - src/journal/meson.build | 5 ----- src/libsystemd/meson.build | 21 +++------------------ src/libudev/libudev.pc.in | 1 - src/locale/meson.build | 1 - src/login/meson.build | 5 ----- src/machine/meson.build | 2 -- src/network/meson.build | 4 ---- src/nsresourced/meson.build | 2 -- src/portable/meson.build | 2 -- src/pstore/meson.build | 1 - src/repart/meson.build | 2 -- src/resolve/meson.build | 1 - src/shared/meson.build | 5 +---- src/socket-activate/meson.build | 1 - src/socket-proxy/meson.build | 1 - src/systemctl/meson.build | 1 - src/sysupdate/meson.build | 2 -- src/test/meson.build | 31 +++++-------------------------- src/timesync/meson.build | 5 +---- src/udev/meson.build | 2 -- src/userdb/meson.build | 3 --- 30 files changed, 15 insertions(+), 130 deletions(-) diff --git a/meson.build b/meson.build index b26ef7979c..8d5ee4f579 100644 --- a/meson.build +++ b/meson.build @@ -978,10 +978,7 @@ endif ##################################################################### -threads = dependency('threads') -librt = cc.find_library('rt') libm = cc.find_library('m') -libdl = cc.find_library('dl') # Header presence check only — dgettext itself is resolved via dlopen_libintl() at runtime, so we never # link against libintl. On glibc dgettext lives in libc; on musl gettext-dev provides libintl.h alongside @@ -1758,9 +1755,7 @@ libsystemd = shared_library( link_with : [libc_wrapper_static, libbasic_static], link_whole : [libsystemd_static], - dependencies : [librt, - threads, - userspace], + dependencies : [userspace], link_depends : libsystemd_sym, install : true, install_tag: 'libsystemd', @@ -1779,14 +1774,11 @@ if static_libsystemd != 'false' install_tag: 'libsystemd', install_dir : libdir, pic : static_libsystemd_pic, - dependencies : [libdl, - libgcrypt_cflags, + dependencies : [libgcrypt_cflags, liblz4_cflags, libm, - librt, libxz_cflags, libzstd_cflags, - threads, userspace], c_args : libsystemd_c_args + (static_libsystemd_pic ? [] : ['-fno-PIC'])) @@ -1804,8 +1796,7 @@ libudev = shared_library( '-Wl,--version-script=' + libudev_sym_path], link_with : [libsystemd_static], link_whole : libudev_basic, - dependencies : [threads, - userspace], + dependencies : [userspace], link_depends : libudev_sym, install : true, install_tag: 'libudev', @@ -1922,10 +1913,6 @@ nss_template = { libshared_static, libbasic_static, ], - 'dependencies' : [ - librt, - threads, - ], 'install' : true, 'install_tag' : 'nss', 'install_dir' : libdir, @@ -1938,10 +1925,7 @@ pam_template = { libsystemd_static, libshared_static, ], - 'dependencies' : [ - libpam_cflags, - threads, - ], + 'dependencies' : libpam_cflags, 'install' : true, 'install_tag' : 'pam', 'install_dir' : pamlibdir, diff --git a/src/basic/meson.build b/src/basic/meson.build index f847b175b6..9007cff178 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -212,15 +212,12 @@ libbasic_static = static_library( include_directories : basic_includes, implicit_include_directories : false, dependencies : [libbzip2_cflags, - libdl, libgcrypt_cflags, liblz4_cflags, libm, - librt, libxz_cflags, libz_cflags, libzstd_cflags, - threads, userspace], c_args : ['-fvisibility=default'], build_by_default : false) diff --git a/src/core/meson.build b/src/core/meson.build index 2bd8170c6a..8971800bcb 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -148,13 +148,10 @@ libcore_static = static_library( dependencies : [libaudit_cflags, libbpf_cflags, libcryptsetup_cflags, - libdl, libm, libmount_cflags, - librt, libseccomp_cflags, libselinux_cflags, - threads, userspace], build_by_default : false) diff --git a/src/coredump/meson.build b/src/coredump/meson.build index 355993d09e..b0753d86fa 100644 --- a/src/coredump/meson.build +++ b/src/coredump/meson.build @@ -30,7 +30,6 @@ common_dependencies = [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ] executables += [ diff --git a/src/creds/meson.build b/src/creds/meson.build index dc4a5a28ae..c18fe2ec89 100644 --- a/src/creds/meson.build +++ b/src/creds/meson.build @@ -12,7 +12,6 @@ executables += [ 'dependencies' : [ libmount_cflags, libopenssl_cflags, - threads, ], }, ] diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 8213a0e672..aa789c0a07 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -22,7 +22,6 @@ executables += [ 'sources' : systemd_cryptenroll_sources, 'dependencies' : [ libcryptsetup_cflags, - libdl, libfido2_cflags, libopenssl_cflags, libp11kit_cflags, diff --git a/src/home/meson.build b/src/home/meson.build index 64c62f1ddd..b724517ae9 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -67,10 +67,7 @@ executables += [ 'dbus' : true, 'sources' : systemd_homed_sources, 'extract' : systemd_homed_extract_sources, - 'dependencies' : [ - libopenssl_cflags, - threads, - ], + 'dependencies' : libopenssl_cflags, }, libexec_template + { 'name' : 'systemd-homework', @@ -81,7 +78,6 @@ executables += [ libfdisk_cflags, libopenssl_cflags, libp11kit_cflags, - threads, ], }, executable_template + { @@ -91,10 +87,8 @@ executables += [ 'extract' : homectl_extract, 'objects' : ['systemd-homed'], 'dependencies' : [ - libdl, libopenssl_cflags, libp11kit_cflags, - threads, ], }, test_template + { @@ -113,10 +107,6 @@ modules += [ 'name' : 'pam_systemd_home', 'conditions' : ['HAVE_PAM'], 'sources' : pam_systemd_home_sources, - 'dependencies' : [ - libpam_cflags, - threads, - ], 'version-script' : meson.current_source_dir() / 'pam_systemd_home.sym', }, ] diff --git a/src/import/meson.build b/src/import/meson.build index c2879c5d84..f133f276b4 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -17,7 +17,6 @@ executables += [ 'import-common.c', 'qcow2-util.c', ), - 'dependencies' : threads, }, libexec_template + { 'name' : 'systemd-pull', diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index f6aa71349c..22ac8703b5 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -25,7 +25,6 @@ common_deps = [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ] executables += [ diff --git a/src/journal/meson.build b/src/journal/meson.build index 1f40e9b43b..75cd7b7a30 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -72,7 +72,6 @@ executables += [ libselinux_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, libexec_template + { @@ -86,7 +85,6 @@ executables += [ 'public' : true, 'sources' : files('cat.c'), 'objects' : ['systemd-journald'], - 'dependencies' : [threads], }, executable_template + { 'name' : 'journalctl', @@ -94,11 +92,9 @@ executables += [ 'sources' : journalctl_sources, 'link_with' : journalctl_link_with, 'dependencies' : [ - libdl, liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, journal_test_template + { @@ -123,7 +119,6 @@ executables += [ liblz4_cflags, libselinux_cflags, libxz_cflags, - threads, ], }, journal_test_template + { diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 2c86a23106..b7e11c9bb2 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -150,9 +150,7 @@ libsystemd_static = static_library( c_args : libsystemd_c_args, link_with : [libc_wrapper_static, libbasic_static], - dependencies : [threads, - libm, - librt, + dependencies : [libm, userspace], build_by_default : false) @@ -203,20 +201,17 @@ simple_tests += files( libsystemd_tests += [ { 'sources' : files('sd-bus/test-bus-address.c'), - 'dependencies' : threads }, { 'sources' : files('sd-bus/test-bus-benchmark.c'), - 'dependencies' : threads, 'type' : 'manual', }, { 'sources' : files('sd-bus/test-bus-chat.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-cleanup.c'), - 'dependencies' : [threads, libseccomp_cflags], + 'dependencies' : libseccomp_cflags, }, { 'sources' : files('sd-bus/test-bus-marshal.c'), @@ -226,28 +221,22 @@ libsystemd_tests += [ libglib_cflags, libgobject_cflags, libm, - threads, ], }, { 'sources' : files('sd-bus/test-bus-objects.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-peersockaddr.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-queue-ref-cycle.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-server.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-signature.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-track.c'), @@ -255,12 +244,11 @@ libsystemd_tests += [ }, { 'sources' : files('sd-bus/test-bus-watch-bind.c'), - 'dependencies' : threads, 'timeout' : 120, }, { 'sources' : files('sd-device/test-sd-device.c'), - 'dependencies' : [threads, libmount_cflags], + 'dependencies' : libmount_cflags, }, { 'sources' : files('sd-event/test-event.c'), @@ -288,16 +276,13 @@ libsystemd_tests += [ }, { 'sources' : files('sd-resolve/test-resolve.c'), - 'dependencies' : threads, 'timeout' : 120, }, { 'sources' : files('sd-varlink/test-varlink.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-varlink/test-varlink-idl.c'), - 'dependencies' : threads, }, ] diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in index 6541bcb1ab..72d46ffc50 100644 --- a/src/libudev/libudev.pc.in +++ b/src/libudev/libudev.pc.in @@ -16,5 +16,4 @@ Name: libudev Description: Library to access udev device information Version: {{PROJECT_VERSION}} Libs: -L${libdir} -ludev -Libs.private: -lrt -pthread Cflags: -I${includedir} diff --git a/src/locale/meson.build b/src/locale/meson.build index 1b97628abb..2f99bb8d80 100644 --- a/src/locale/meson.build +++ b/src/locale/meson.build @@ -18,7 +18,6 @@ localectl_sources = files('localectl.c') # specify where the headers are. if conf.get('HAVE_XKBCOMMON') == 1 libxkbcommon_deps = [ - libdl, libxkbcommon_cflags, ] else diff --git a/src/login/meson.build b/src/login/meson.build index 390960d5f6..44325ccd7c 100644 --- a/src/login/meson.build +++ b/src/login/meson.build @@ -48,9 +48,6 @@ executables += [ 'dbus' : true, 'sources' : systemd_logind_sources, 'extract' : systemd_logind_extract_sources, - 'dependencies' : [ - threads, - ], }, executable_template + { 'name' : 'loginctl', @@ -60,7 +57,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, executable_template + { @@ -79,7 +75,6 @@ executables += [ test_template + { 'sources' : files('test-login-tables.c'), 'objects' : ['systemd-logind'], - 'dependencies' : threads, }, test_template + { 'sources' : files('test-session-properties.c'), diff --git a/src/machine/meson.build b/src/machine/meson.build index 13756cb8a1..fc16e9f5c5 100644 --- a/src/machine/meson.build +++ b/src/machine/meson.build @@ -36,13 +36,11 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, test_template + { 'sources' : files('test-machine-tables.c'), 'objects' : ['systemd-machined'], - 'dependencies': threads, }, ] diff --git a/src/network/meson.build b/src/network/meson.build index 0319102492..23f6f3a4fe 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -188,7 +188,6 @@ network_fuzz_template = fuzz_template + { libsystemd_network, ], 'objects' : ['systemd-networkd'], - 'dependencies' : threads, 'include_directories' : network_includes, } @@ -204,7 +203,6 @@ executables += [ libsystemd_network, networkd_link_with, ], - 'dependencies' : threads, 'bpf_programs': [ 'sysctl-monitor', ] @@ -240,11 +238,9 @@ executables += [ }, network_test_template + { 'sources' : files('test-network-tables.c'), - 'dependencies' : threads, }, network_test_template + { 'sources' : files('test-network.c'), - 'dependencies' : threads, }, network_test_template + { 'sources' : files('test-networkd-address.c'), diff --git a/src/nsresourced/meson.build b/src/nsresourced/meson.build index 881fd911e4..1654e1766b 100644 --- a/src/nsresourced/meson.build +++ b/src/nsresourced/meson.build @@ -24,13 +24,11 @@ executables += [ 'name' : 'systemd-nsresourced', 'sources' : systemd_nsresourced_sources, 'extract' : systemd_nsresourced_extract_sources, - 'dependencies' : threads, 'bpf_programs': ['userns-restrict'], }, libexec_template + { 'name' : 'systemd-nsresourcework', 'sources' : systemd_nsresourcework_sources, - 'dependencies' : threads, 'objects' : ['systemd-nsresourced'], 'bpf_programs': ['userns-restrict'], }, diff --git a/src/portable/meson.build b/src/portable/meson.build index 3029ad4177..0bd5d1c3ce 100644 --- a/src/portable/meson.build +++ b/src/portable/meson.build @@ -32,7 +32,6 @@ executables += [ libcryptsetup_cflags, libmount_cflags, libselinux_cflags, - threads, ], }, executable_template + { @@ -40,7 +39,6 @@ executables += [ 'public' : true, 'sources' : files('portablectl.c'), 'link_with' : portabled_link_with, - 'dependencies' : threads, }, ] diff --git a/src/pstore/meson.build b/src/pstore/meson.build index d6bb925789..a94f1b1e88 100644 --- a/src/pstore/meson.build +++ b/src/pstore/meson.build @@ -12,7 +12,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, ] diff --git a/src/repart/meson.build b/src/repart/meson.build index 9b89f56f7a..bb5b63be05 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -17,7 +17,6 @@ executables += [ libfdisk_cflags, libmount_cflags, libopenssl_cflags, - threads, ], }, executable_template + { @@ -35,7 +34,6 @@ executables += [ libfdisk_cflags, libmount_cflags, libopenssl_cflags, - threads, ], }, ] diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 5802889746..3650313355 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -72,7 +72,6 @@ resolve_common_template = { libidn2_cflags, libopenssl_cflags, libm, - threads, ], } diff --git a/src/shared/meson.build b/src/shared/meson.build index ce96ce3025..55cf8364d3 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -383,8 +383,7 @@ shared_sources += [dns_type_from_name_inc, dns_type_to_name_inc] libshared_name = 'systemd-shared-@0@'.format(shared_lib_tag) -libshared_deps = [threads, - libacl_cflags, +libshared_deps = [libacl_cflags, libapparmor_cflags, libarchive_cflags, libaudit_cflags, @@ -393,7 +392,6 @@ libshared_deps = [threads, libcrypt_cflags, libcryptsetup_cflags, libcurl_cflags, - libdl, libdw_cflags, libelf_cflags, libfdisk_cflags, @@ -410,7 +408,6 @@ libshared_deps = [threads, libpcre2_cflags, libpwquality_cflags, libqrencode_cflags, - librt, libseccomp_cflags, libselinux_cflags, libxenctrl_cflags, diff --git a/src/socket-activate/meson.build b/src/socket-activate/meson.build index a4d18b58a8..628dbe79ff 100644 --- a/src/socket-activate/meson.build +++ b/src/socket-activate/meson.build @@ -5,6 +5,5 @@ executables += [ 'name' : 'systemd-socket-activate', 'public' : true, 'sources' : files('socket-activate.c'), - 'dependencies' : threads, }, ] diff --git a/src/socket-proxy/meson.build b/src/socket-proxy/meson.build index 52d63a8440..1106b445e9 100644 --- a/src/socket-proxy/meson.build +++ b/src/socket-proxy/meson.build @@ -5,6 +5,5 @@ executables += [ 'name' : 'systemd-socket-proxyd', 'public' : true, 'sources' : files('socket-proxyd.c'), - 'dependencies' : threads, }, ] diff --git a/src/systemctl/meson.build b/src/systemctl/meson.build index 2ce11c8f48..882704c9d7 100644 --- a/src/systemctl/meson.build +++ b/src/systemctl/meson.build @@ -58,7 +58,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], 'install_tag' : 'systemctl', }, diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 0cfe22f528..da554ddcc3 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -30,7 +30,6 @@ executables += [ 'dependencies' : [ libfdisk_cflags, libopenssl_cflags, - threads, ], }, libexec_template + { @@ -38,7 +37,6 @@ executables += [ 'dbus' : true, 'conditions' : ['ENABLE_SYSUPDATED'], 'sources' : files('sysupdated.c'), - 'dependencies' : threads, }, executable_template + { 'name' : 'updatectl', diff --git a/src/test/meson.build b/src/test/meson.build index c8279bc751..b581de293b 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -223,10 +223,8 @@ simple_tests += files( common_test_dependencies = [ libmount_cflags, - librt, libseccomp_cflags, libselinux_cflags, - threads, ] executables += [ @@ -303,14 +301,13 @@ executables += [ ], }, test_template + { - # only static linking apart from libdl, to make sure that the - # module is linked to all libraries that it uses. + # only static linking, to make sure that the module is linked + # to all libraries that it uses. 'sources' : files('test-dlopen.c'), 'link_with' : [ libc_wrapper_static, libbasic_static, ], - 'dependencies' : libdl, 'install' : false, 'type' : 'manual', }, @@ -359,10 +356,7 @@ executables += [ test_template + { 'sources' : files('test-libmount.c'), 'conditions' : ['HAVE_LIBMOUNT'], - 'dependencies' : [ - libmount_cflags, - threads, - ], + 'dependencies' : libmount_cflags, }, test_template + { 'sources' : files('test-loopback.c'), @@ -378,7 +372,6 @@ executables += [ }, test_template + { 'sources' : files('test-pressure.c'), - 'dependencies' : threads, }, test_template + { 'sources' : files('test-mount-util.c'), @@ -398,17 +391,13 @@ executables += [ test_template + { 'sources' : files('test-nss-hosts.c'), 'extract' : files('nss-test-util.c'), - 'dependencies' : [ - libdl, - libseccomp_cflags, - ], + 'dependencies' : libseccomp_cflags, 'conditions' : ['ENABLE_NSS'], 'timeout' : 120, }, test_template + { 'sources' : files('test-nss-users.c'), 'objects' : ['test-nss-hosts'], - 'dependencies' : libdl, 'conditions' : ['ENABLE_NSS'], }, test_template + { @@ -426,7 +415,6 @@ executables += [ }, test_template + { 'sources' : files('test-process-util.c'), - 'dependencies' : threads, }, test_template + { 'sources' : files('test-progress-bar.c'), @@ -441,7 +429,6 @@ executables += [ }, test_template + { 'sources' : files('test-qrcode-util.c'), - 'dependencies' : libdl, }, test_template + { 'sources' : files('test-random-util.c'), @@ -471,7 +458,6 @@ executables += [ }, test_template + { 'sources' : files('test-set-disable-mempool.c'), - 'dependencies' : threads, }, test_template + { 'sources' : files('test-sizeof.c'), @@ -603,7 +589,6 @@ executables += [ }, core_test_template + { 'sources' : files('test-loop-util.c'), - 'dependencies' : [threads], 'parallel' : false, }, core_test_template + { @@ -611,10 +596,7 @@ executables += [ }, core_test_template + { 'sources' : files('test-namespace.c'), - 'dependencies' : [ - threads, - libmount_cflags, - ], + 'dependencies' : libmount_cflags, }, core_test_template + { 'sources' : files('test-ns.c'), @@ -632,7 +614,6 @@ executables += [ }, core_test_template + { 'sources' : files('test-socket-bind.c'), - 'dependencies' : libdl, 'conditions' : ['BPF_FRAMEWORK'], }, core_test_template + { @@ -666,7 +647,6 @@ executables += [ libbasic_static, libsystemd, ], - 'dependencies' : threads, }, test_template + { 'sources' : files('../libudev/test-udev-device-thread.c'), @@ -675,7 +655,6 @@ executables += [ libbasic_static, libudev, ], - 'dependencies' : threads, }, test_template + { 'sources' : files('../libudev/test-libudev.c'), diff --git a/src/timesync/meson.build b/src/timesync/meson.build index b307245772..c42fcd3e70 100644 --- a/src/timesync/meson.build +++ b/src/timesync/meson.build @@ -35,10 +35,7 @@ executables += [ 'sources' : timesyncd_sources, 'extract' : timesyncd_extract_sources, 'link_with' : timesyncd_link_with, - 'dependencies' : [ - libm, - threads, - ], + 'dependencies' : libm, }, libexec_template + { 'name' : 'systemd-time-wait-sync', diff --git a/src/udev/meson.build b/src/udev/meson.build index 700a8cc8d5..7e2435d0fa 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -122,7 +122,6 @@ udev_dependencies = [ libblkid_cflags, libkmod_cflags, libmount_cflags, - threads, ] udev_plugin_template = executable_template + { @@ -138,7 +137,6 @@ udev_common_template = { 'dependencies' : [ libacl_cflags, libblkid_cflags, - threads, ], } udev_test_template = test_template + udev_common_template diff --git a/src/userdb/meson.build b/src/userdb/meson.build index a933a4907b..c25c40bc0a 100644 --- a/src/userdb/meson.build +++ b/src/userdb/meson.build @@ -5,7 +5,6 @@ executables += [ 'name' : 'systemd-userwork', 'conditions' : ['ENABLE_USERDB'], 'sources' : files('userwork.c'), - 'dependencies' : threads, }, libexec_template + { 'name' : 'systemd-userdbd', @@ -14,13 +13,11 @@ executables += [ 'userdbd-manager.c', 'userdbd.c', ), - 'dependencies' : threads, }, executable_template + { 'name' : 'userdbctl', 'conditions' : ['ENABLE_USERDB'], 'sources' : files('userdbctl.c'), - 'dependencies' : threads, }, ] From b77c7b7cfc243691c44d5104e8440cd3aeb86ace Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 14 May 2026 19:20:02 +0000 Subject: [PATCH 11/12] libc: Use dlsym() from a constructor instead of weak symbols Weak symbols still introduce a version requirement on a newer libc. Resolve each libc symbol via dlsym(RTLD_DEFAULT) from a per-shim constructor and cache the result in a file-scope static instead. This avoids the version requirement, keeps the call path free of atomics (constructors run single-threaded before main() and before any signal handler can fire), and keeps dlsym() out of contexts where it is not async-signal-safe. --- src/libc/libc-shim.h | 64 +++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/libc/libc-shim.h b/src/libc/libc-shim.h index c5cf9c8acd..022ee5ad15 100644 --- a/src/libc/libc-shim.h +++ b/src/libc/libc-shim.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include #include #include @@ -35,21 +36,38 @@ #define _SHIM_NAME_8(t, n, ...) n, _SHIM_NAME_7(__VA_ARGS__) #define _SHIM_NAME(...) _SHIM_CAT(_SHIM_NAME_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__) -/* Defines a wrapper that calls the libc symbol if available at runtime, or falls back to the - * corresponding direct syscall otherwise. The libc symbol is redeclared as a weak reference so the - * binary still loads on libc versions that don't provide it. Each parameter is passed as type, - * name pairs (flat). +/* The shim resolves the libc symbol via dlsym(RTLD_DEFAULT) at DSO-load time using a constructor + * and caches the result in a file-scope static. Constructors run single-threaded before main() and + * before any signal handler can fire, so the cached pointer needs no atomics: subsequent reads from + * any thread observe the value stored during init. Resolving eagerly also keeps dlsym() out of + * contexts where it is not async-signal-safe (signal handlers, between fork() and exec()). * - * The weak reference is bound to the libc symbol via an __asm__("label") rename so that the bare - * libc identifier never appears as a C token. This matters because override/musl headers - * sometimes #define the libc name to redirect it to the _shim variant — without the rename the - * caller would have to #undef each name before invoking the macro. # and ## operators don't - * macro-expand their operands, so the parameter "func" stays a literal token everywhere. */ + * The asm barrier after dlsym() is load-bearing: without it, when LTO determines the cache store + * is dead (because no caller of func##_shim survives DCE) the compiler is free to tail-call + * dlsym() (jmp dlsym@plt). Under glibc, dlsym reads __builtin_return_address(0) to find its + * caller's link map; with a tail call that read lands inside ld.so's call_init(), the resulting + * link map has no l_scope, and _dl_lookup_symbol_x SIGSEGVs. Filed upstream in glibc by + * https://sourceware.org/bugzilla/show_bug.cgi?id=34156. The barrier keeps us working on + * unpatched libc. + * + * Each reference to `func` in the macro body is positioned as an operand of `#` or `##` so the + * override headers (e.g. "#define openat2 openat2_shim") don't rewrite the token before pasting or + * stringification. For the same reason the resolution logic isn't extracted into a helper macro — + * passing `func` to a nested macro would expand it as a regular argument and re-trigger the + * override. + * + * Defines a wrapper that calls the libc symbol if available at runtime, or falls back to the + * corresponding direct syscall otherwise. Each parameter is passed as type, name pairs (flat). */ #define DEFINE_SYSCALL_SHIM(func, ret, ...) \ - extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ - if (func##_libc_weak) \ - return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ return syscall(__NR_##func, _SHIM_NAME(__VA_ARGS__)); \ } @@ -57,20 +75,30 @@ * by returning the positive errno value directly (posix_spawn-family convention). If the libc symbol * is missing at runtime, ENOSYS is returned. */ #define DEFINE_LIBC_SHIM(func, ret, ...) \ - extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ - if (func##_libc_weak) \ - return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ return ENOSYS; \ } /* Like DEFINE_LIBC_SHIM but for libc helpers that report errors via errno + -1 return value. If the * libc symbol is missing at runtime, errno is set to ENOSYS and -1 is returned. */ #define DEFINE_LIBC_ERRNO_SHIM(func, ret, ...) \ - extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ - if (func##_libc_weak) \ - return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ errno = ENOSYS; \ return -1; \ } From 97f81508c51cd11e01fc9b32cb3d7825f4a56001 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 14 May 2026 19:20:02 +0000 Subject: [PATCH 12/12] libc: Make sure C23 versions of strtol(), sscanf() are not used When _GNU_SOURCE is defined, glibc will always use c23 versions of strtol(), sscanf() and friends if available (introduced after glibc 2.34). Which means that any binaries built with headers from newer glibc won't load on glibc < 2.38. To work around this, redefine the appropriate constants to zero make sure the c99 versions are used instead. --- meson.build | 13 ++++++------- src/include/glibc/stdio.h | 18 ++++++++++++++++++ src/include/glibc/stdlib.h | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 src/include/glibc/stdio.h create mode 100644 src/include/glibc/stdlib.h diff --git a/meson.build b/meson.build index 8d5ee4f579..56b841ed35 100644 --- a/meson.build +++ b/meson.build @@ -1691,14 +1691,13 @@ system_includes = [ ), ] -if get_option('libc') == 'musl' - system_include_args = [ - '-isystem', meson.project_build_root() / 'src/include/musl', - '-isystem', meson.project_source_root() / 'src/include/musl', - ] + system_include_args +libc_include_dir = 'src/include' / get_option('libc') +system_include_args = [ + '-isystem', meson.project_build_root() / libc_include_dir, + '-isystem', meson.project_source_root() / libc_include_dir, +] + system_include_args - system_includes += include_directories('src/include/musl', is_system : true) -endif +system_includes += include_directories(libc_include_dir, is_system : true) basic_includes = [ include_directories( diff --git a/src/include/glibc/stdio.h b/src/include/glibc/stdio.h new file mode 100644 index 0000000000..7e45cb3ba2 --- /dev/null +++ b/src/include/glibc/stdio.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Force glibc's stdio.h to route sscanf/fscanf to the old __isoc99_* siblings (GLIBC_2.7) rather + * than the newer __isoc23_* ones (GLIBC_2.38). The only behavioural difference is "0b" prefix + * support in %i conversions, which we don't use. We include features.h first so the macro is set + * to its normal value, then override it before stdio.h's body evaluates __GLIBC_USE(C23_STRTOL). + * + * The macro was named __GLIBC_USE_C2X_STRTOL on glibc 2.38–2.39 and renamed to the C23 spelling + * in glibc 2.40; clear both so this override works across that range. */ + +#include +#undef __GLIBC_USE_C2X_STRTOL +#define __GLIBC_USE_C2X_STRTOL 0 +#undef __GLIBC_USE_C23_STRTOL +#define __GLIBC_USE_C23_STRTOL 0 + +#include_next /* IWYU pragma: export */ diff --git a/src/include/glibc/stdlib.h b/src/include/glibc/stdlib.h new file mode 100644 index 0000000000..a3ad2a3b5a --- /dev/null +++ b/src/include/glibc/stdlib.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Force glibc's stdlib.h to leave strtol/strtoul/strtoll/strtoull as their original GLIBC_2.2.5 + * symbols rather than redirect to __isoc23_* (GLIBC_2.38). The only behavioural difference is + * "0b" prefix support in base 0/2 parsing, which we don't use. + * + * The macro was named __GLIBC_USE_C2X_STRTOL on glibc 2.38–2.39 and renamed to the C23 spelling + * in glibc 2.40; clear both so this override works across that range. */ + +#include +#undef __GLIBC_USE_C2X_STRTOL +#define __GLIBC_USE_C2X_STRTOL 0 +#undef __GLIBC_USE_C23_STRTOL +#define __GLIBC_USE_C23_STRTOL 0 + +#include_next /* IWYU pragma: export */