mbox series

[pseudo,0/2] Fix an unbounded recursion in pseudo's own init path

Message ID 20260929-pseudo-init-recursion-fix-v1-0-34e30f4c5349@peridio.com
Headers show
Series Fix an unbounded recursion in pseudo's own init path | expand

Message

Javier Tia Sept. 29, 2026, 9:45 p.m. UTC
I hit this building with mold 2.42.1 under bitbake's fakeroot (pseudo)
task: mold links segfault, and pseudo's diagnostic log fills with
millions of "failed to save new value" lines before the stack
overflows.

mold statically links mimalloc and overrides malloc/strdup process-wide.
Under pseudo's LD_PRELOAD interception, mimalloc's own lazy process init
calls syscall() to probe /proc/sys/vm/overcommit_memory. pseudo's
syscall() wrapper reenters _libpseudo_init(), which allocates via
strdup() - landing back in mimalloc, still mid-init, which can
legitimately fail that allocation this early. pseudo_set_value() handles
a failed strdup() by logging a warning and moving on, which is fine on
its own. The problem is that pseudo_get_value()'s recovery path
unconditionally calls pseudo_init_util() again whenever a lookup comes
back empty but the environment variable is actually set - and
pseudo_init_util() itself performs exactly such a lookup at its own
tail, while its init-guard has already been cleared. If the value could
not be cached, that recovery path re-enters init, which fails the same
allocation again, and recurses without bound.

Patch 1 closes the recursion in pseudo_util.c: it moves the init-guard
reset to the end of pseudo_init_util() and gates the recovery re-init on
it, so a value that still cannot be cached surfaces through the existing
diagnostic instead of recursing.

Patch 2 removes the trigger for this specific caller: pseudo's
syscall()/prctl() wrappers reach _libpseudo_init() before real_syscall/
real_prctl are resolved, in two overlapping windows - before the
constructor runs, and from inside it, before pseudo_init_wrappers() has
run. Forwarding a pre-resolution call straight to the real libc function
(for every number/option this port does not itself rewrite) means an
allocator's own process init no longer re-enters pseudo's init through
these two wrappers. It does not cover every other generated wrapper,
which still calls pseudo_check_wrappers() on first use; an allocator
whose first touch is a different wrapped libc call would still hit the
same recursion through that path.

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). Verified end to end: a real
`gcc -fuse-ld=mold` build, run under pseudo with PSEUDO_DEBUG set
(matching what bitbake exports for every fakeroot task), links and runs,
and a subsequent `chown 0:0` on the output is correctly tracked by
pseudo.

Signed-off-by: Javier Tia <javier@peridio.com>
---
Javier Tia (2):
      pseudo_util: bound pseudo_get_value()'s recovery re-init
      ports/linux/pseudo_wrappers: forward pre-init syscall/prctl to the real function

 ports/linux/pseudo_wrappers.c | 48 +++++++++++++++++++++++++++++++++++++++++++
 pseudo_util.c                 | 11 +++++++---
 2 files changed, 56 insertions(+), 3 deletions(-)
---
base-commit: ba8887e5f1e922f866681ec7dec1a00b602a9328
change-id: 20260929-pseudo-init-recursion-fix-0009ce928d8e

Best regards,
--  
Javier Tia <javier@peridio.com>