Skip to content

Keep a pending dlerror across lazy binding - #12

Open
buyemune-sys wants to merge 2 commits into
pg83:mainfrom
buyemune-sys:fix/lazy-binding-keeps-dlerror
Open

buyemune-sys wants to merge 2 commits into
pg83:mainfrom
buyemune-sys:fix/lazy-binding-keeps-dlerror

Conversation

@buyemune-sys

@buyemune-sys buyemune-sys commented Oct 17, 2026 •

Copy link
Copy Markdown

The problem

The lazy PLT resolver (elfPltResolve, lib/elf_loader.cpp:2719) looks symbols up through the bridge, and the bridge goes through the public stub_dlopen("c") / stub_dlsym, which reset the thread's error first. A program that reads dlerror() after a failed dlopen, with a first call to some function through the PLT in between, gets NULL.

Example: when a C module fails to load, Tarantool calls unlink and rmdir on its temporary copy, and only then dlerror() (src/box/module_cache.c). Under solo the reason is lost:

Failed to dynamically load module 'bad': (null)                # solo main
Failed to dynamically load module 'bad': ...: file too short   # glibc

LD_DEBUG=dlfcn shows what happens after the failed dlopen: dlopen c / found unlink, dlopen c / found rmdir. Each of those clears the error.

The fix

Binding never touches dlerror under ld.so. elfPltResolve now sets a pending error aside while it resolves and puts it back afterwards. With nothing pending, this costs one thread-local check.

Test

tst/glibc_lazy_test.c (built with -z lazy) gains glibc_lazy_dlerror_kept: a failed dlopen, then a first call to getppid, then dlerror(), which is itself bound lazily.

  • on main: lazy binding lost the pending dlerror: 3;
  • with the fix: ./build test passes.

buyemune-sys and others added 2 commits October 20, 2026 15:42
Security updates replaced libapparmor1 4.0.1really4.0.1-0ubuntu0.24.04.7
with .9 and libexpat1 2.6.1-2ubuntu0.4 with ubuntu0.6, and the pool kept
only the new files, so the cold-cache Fetch x86_64 job answers 404 and
every x86-64 job behind it is skipped. Both pins move to the versions
noble-updates ships now; their SHA256s match the archive's Packages index.
The other twelve pins are still served.
The lazy binder resolves through the bridge, whose lookups go through
the public dl* entry points, and those reset the thread's error. A
program that reads dlerror after a failed dlopen, with calls in between
that happen to be first calls through the PLT, then got NULL. Tarantool
does that when a C module fails to load: unlink and rmdir of its
temporary copy run first, and the error reported became "(null)".

ld.so's binding never touches dlerror. elfPltResolve now sets a pending
error aside while it resolves and puts it back afterwards; with nothing
pending it costs one thread-local check.

The lazy-binding test library fails a dlopen and reads dlerror after a
first call to getppid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@buyemune-sys
buyemune-sys force-pushed the fix/lazy-binding-keeps-dlerror branch from 499a1cd to 179f848 Compare October 20, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant