]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
libc: Use dlsym() from a constructor instead of weak symbols
authorDaan De Meyer <daan@amutable.com>
Thu, 14 May 2026 19:20:02 +0000 (19:20 +0000)
committerDaan De Meyer <daan@amutable.com>
Mon, 18 May 2026 21:17:38 +0000 (21:17 +0000)
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

index c5cf9c8acdab50c04b78b2adf1c790b4c5510636..022ee5ad157bd9241d5b31d6b21c40cbde1ad8ef 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <dlfcn.h>
 #include <errno.h>
 #include <sys/syscall.h>
 #include <unistd.h>
 #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__));                                \
         }
 
  * 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;                                                                           \
         }