@@ -59,6 +59,31 @@ syscall(long number, ...) {
long rc = -1;
va_list ap;
+ /* Reached before real_syscall is resolved: the caller may be a malloc
+ * implementation initializing itself, and pseudo_check_wrappers()
+ * would run _libpseudo_init() -> pseudo_init_util(), which allocates.
+ * Forward the numbers pseudo never rewrites without running that path.
+ *
+ * The !real_syscall half of the guard covers a narrower window than
+ * !_libpseudo_initted alone would: _libpseudo_init() (below) sets
+ * _libpseudo_initted before it resolves real_syscall via
+ * pseudo_init_wrappers(), so an allocator whose first touch happens
+ * from inside the constructor itself - rather than before it runs -
+ * would otherwise still fall through to pseudo_enosys() below. */
+ if ((!_libpseudo_initted || !real_syscall) && number != SYS_openat2
+#ifdef SYS_renameat2
+ && number != SYS_renameat2
+#endif
+#ifdef SYS_seccomp
+ && number != SYS_seccomp
+#endif
+ ) {
+ if (!real_syscall)
+ real_syscall = (long (*)(long, ...)) dlsym(RTLD_NEXT, "syscall");
+ if (real_syscall)
+ goto call_syscall;
+ }
+
if (!pseudo_check_wrappers() || !real_syscall) {
/* rc was initialized to the "failure" value */
pseudo_enosys("syscall");
@@ -147,6 +172,20 @@ prctl(int option, ...) {
int rc = -1;
va_list ap;
+ /* See syscall() above: covers both the pre-constructor window and the
+ * mid-constructor one, for every option except the one prctl() itself
+ * still special-cases below. */
+ if ((!_libpseudo_initted || !real_prctl)
+#ifdef PR_SET_SECCOMP
+ && option != PR_SET_SECCOMP
+#endif
+ ) {
+ if (!real_prctl)
+ real_prctl = (int (*)(int, ...)) dlsym(RTLD_NEXT, "prctl");
+ if (real_prctl)
+ goto call_prctl;
+ }
+
if (!pseudo_check_wrappers() || !real_prctl) {
/* rc was initialized to the "failure" value */
pseudo_enosys("prctl");
@@ -167,6 +206,15 @@ prctl(int option, ...) {
}
#endif
+call_prctl:
+ /* On Debian 11 - gcc (Debian 10.2.1-6) this results in:
+ * error: a label can only be part of a statement and a declaration
+ * is not a statement
+ *
+ * adding a ; here resolves this
+ */
+ ;
+
/* gcc magic to attempt to just pass these args to prctl. we have to
* guess about the number of args; the docs discuss calling conventions
* up to 5, so let's try that?
pseudo's syscall() and prctl() wrappers both call pseudo_check_wrappers(), which runs _libpseudo_init() when _libpseudo_initted is still 0. That is correct for a wrapped call pseudo actually needs to look at, but init itself allocates (pseudo_init_util()'s pseudo_set_value() calls strdup()), and an allocator that statically overrides the process's global malloc/strdup and uses syscall()/prctl() during its own not-yet-complete process init re-enters pseudo's own init on the same thread, before that allocator has anywhere safe to return an allocation from. Observed with mold 2.42.1's bundled mimalloc, which does exactly this under pseudo's LD_PRELOAD interception: mi_process_init() calls syscall(SYS_open, ...) to probe /proc/sys/vm/overcommit_memory. Forward a pre-init syscall()/prctl() straight to the real libc function via dlsym(RTLD_NEXT, ...) instead, for every number/option this port does not itself rewrite (openat2, renameat2, seccomp stay on the existing path so their behavior is unchanged; PR_SET_SECCOMP is guarded with #ifdef the same way the syscall() side already guards SYS_renameat2 and SYS_seccomp, since this is now the first unconditional reference to it - previously it only appeared inside the existing `#ifdef SECCOMP_SET_MODE_FILTER` block below). The guard is `!_libpseudo_initted || !real_syscall` rather than just the first half: _libpseudo_init() sets _libpseudo_initted before it resolves real_syscall via pseudo_init_wrappers(), so an allocator whose first touch happens from inside the constructor itself, rather than before it runs, would otherwise still reach pseudo_enosys() and get ENOSYS instead of a real result. The resolved pointer is stored directly into real_syscall/ real_prctl, the same globals pseudo_init_wrappers() would otherwise populate, and pseudo_init_one_wrapper() already skips re-resolving a wrapper whose real pointer is non-NULL, so this path and the normal init path cannot race to different results. This removes the re-entrant call into _libpseudo_init() for the caller that triggers it before pseudo has any state of its own to protect, without touching behavior once pseudo is initialized. It covers only syscall()/prctl(); every other generated wrapper still calls pseudo_check_wrappers() on first use (templates/wrapfuncs.c), so an allocator whose first touch is a different wrapped libc call (open(), readlink(), etc.) would still re-enter init through that path. On glibc before 2.34, dlsym() itself can allocate on a thread's first dynamic-linker call (the lazy dlerror() result struct), which would re-enter the same allocator this patch is trying to protect; this is not fixed here. Verified against pseudo's own test suite (no change to the 18/45 tests that pass in this sandbox, which lacks chroot/openat2/renameat2 support - confirmed identical to pristine master) and against a real `gcc -fuse-ld=mold` build, plus a subsequent `chown 0:0` on the output, run under pseudo with PSEUDO_DEBUG set, matching what bitbake exports for every fakeroot task. Signed-off-by: Javier Tia <javier@peridio.com> --- ports/linux/pseudo_wrappers.c | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+)