[Bug dynamic-link/15686] Shared-object static constructors called with a lock held

temap at mail dot ru via Glibc-bugs <[email protected]> Sun, 12 Jul 2026 12:08:11 +0000
Newsgroups gmane.comp.lib.glibc.bugs
Message-ID <[email protected]/bugzilla/>
https://sourceware.org/bugzilla/show_bug.cgi?id=3D15686

--- Comment #8 from Artem Proskurnev <temap at mail dot ru> ---
I received a message from [email protected] and ran all available tests
again.

-- Method --


Two independent glibc builds from source, identical configuration, differing
only in whether the patch is applied. Full make check run on each.
Results compared by test name.


-- Source revisions --


NEW: 96623a9cf9 =E2=80=9Celf: Release dl_load_lock before running dlopen co=
nstructors
(BZ 15686)=E2=80=9D patch applied
OLD: 5396eb7045 =E2=80=9Clocaledata: Use libc-alpha ML as the canonical con=
tact=E2=80=9D
(parent of the patch commit) patch not applied

The patch commit adds tst-create2 as a regression test. For OLD, the regres=
sion
test source files (sysdeps/pthread/tst-create2.c and
sysdeps/pthread/tst-create2mod.c) and their Makefile entries were
forward-ported onto the unpatched base, so that the same test is
present in both builds. No other changes were made to the OLD tree; in
particular, elf/dl-open.c is unchanged from upstream master and still
contains the bug.


-- Build configuration --


Both trees configured identically, out-of-tree:

```
../src/configure \
  --prefix=3D/usr \
  --disable-werror \
  --disable-profile \
  --enable-kernel=3D3.2 \
  --without-selinux \
  --enable-bind-now
```

Toolchain: GCC 14.3.0 (ROSA), binutils from host, x86_64-linux-gnu.

Build steps for each side:
```
mkdir build && cd build
../src/configure <flags as above>
make -j8
make -j8 check
```

No mocking, no chroots, no container isolation. Both builds ran on
the same bare-metal x86_64 host (ROSA Linux, kernel 6.12).


-- Results --


Aggregate counts from `tests.sum`:

Status      | NEW  | OLD
-------------------------
PASS        | 6874 | 6873
FAIL        |    7 |    8
XFAIL       |   16 |   16
XPASS       |    0 |    0
UNSUPPORTED |  476 |  476
ERROR       |    0 |    0


-- Per-test diff (the entire behavioral difference) --


Test               | NEW  | OLD
-------------------------------
`nptl/tst-create2` | PASS | **FAIL** (deadlock)

This is the only test whose outcome differs between the two runs.

On unpatched glibc, `tst-create2` reproduces the deadlock described
in BZ 15686: the main thread is in `dlopen()` running a constructor
that calls `pthread_join` on a worker thread; the worker thread blocks
forever in `__cxa_thread_atexit_impl` trying to acquire `dl_load_lock`,
which the main thread still holds. The test times out and is reported
as FAIL.

With the patch applied, `dlopen()` releases `dl_load_lock` before
invoking constructors, the worker thread completes its TLS destructor
registration, the constructor returns, and the test exits cleanly.


-- FAILs common to both runs (not caused by the patch) --


These 7 FAILs appear identically in NEW and OLD and are unrelated to
the patch - they require root, specific hardware, or kernel
capabilities that the unprivileged run environment does not provide:

```
FAIL: misc/test-errno-linux
FAIL: misc/tst-mlock2
FAIL: misc/tst-ntp_gettime
FAIL: misc/tst-ntp_gettimex
FAIL: misc/tst-pkey
FAIL: misc/tst-process_mrelease
FAIL: time/tst-adjtime
```

No regressions were introduced by the patch.


-- Conclusion --


- The patch resolves the deadlock class tracked by BZ 15686, demonstrated
  by `tst-create2` transitioning from FAIL (timeout on deadlock) on
  unpatched glibc to PASS on patched glibc.
- The patch introduces no regressions: across the full `make check`
  suite (~7400 outcomes), no other test changes state in either
  direction.


-- Cross-check against the Linaro tcwg_glibc_check CI run (aarch64) --


The Linaro CI bot reported
https://patchwork.sourceware.org/patch/138954,
build `tcwg_glibc_check--master-aarch64-precommit/10384`):

> "Produces 1 regression 96 fixes"
> regressions.sum: FAIL: posix/tst-affinity-static

The CI summary uses "1 regression" to mean "1 stable fail" (a test
that consistently failed across multiple CI runs of the patched
build). The underlying `notify/results.compare.txt` shows a more
detailed picture - 6 actual transitions on the patchwork run vs. the
reference `master-aarch64-build/10334` run:

Transition      | Test
---------------------------------------------
PASS -> FAIL    | `posix/tst-affinity-static`
PASS -> FAIL    | `posix/tst-affinity`
PASS -> FAIL    | `posix/tst-affinity-pid`
PASS -> FAIL    | `malloc/tst-malloc-tcache-leak`
(none) -> XPASS | `elf/tst-protected1a`
(none) -> XPASS | `elf/tst-protected1b`

All six are listed in the CI's own `sumfiles/xfails.xfail` as known-flaky on
aarch64, with explicit expiry dates:

```
flaky,expire=3D20260922 | FAIL: malloc/tst-malloc-tcache-leak
flaky,expire=3D20260910 | FAIL: posix/tst-affinity
flaky,expire=3D20260910 | FAIL: posix/tst-affinity-pid
flaky,expire=3D20260909 | XPASS: elf/tst-protected1a
flaky,expire=3D20260909 | XPASS: elf/tst-protected1b
```

`tst-affinity-static` is the only one not yet on the flaky list, but
the CI's own `00-sumfiles/tests.log.1` shows it fails for the same
reason as the flaky `tst-affinity*` siblings - the aarch64 CI guest
sees a 64-CPU topology and the test times out iterating over it:

```
FAIL: posix/tst-affinity-static
original exit status 1
info: Detected CPU set size (in bits): 64
info: Maximum test CPU: 63
```

The same `tests.log.1` records the broken container environment at
the top of the run - these explain the bulk of the 96 "fixes"
(`fixes.sum` is dominated by `resolv/*`, `nss/*`, `io/tst-fchmod-fuse`,
`posix/tst-wordexp-*`, `sunrpc/tst-udp-*` and similar tests that need
network, FUSE, or root):

```
error: ../sysdeps/unix/sysv/linux/tst-spawn-cgroup.c:152: can not create a =
new
cgroupv2 group
error: support_fuse.c:425: cannot open /dev/fuse: No such file or directory
error: tst-faccessat-setuid.c:90: Test needs to be run as root (UID 0)
```

The reference build (`master-aarch64-build/10334`) ran with the same
environmental limitations, so a test that flaked to FAIL there and
flaked to PASS on the precommit run shows up as a "fix"; the inverse
shows up as a "regression". None of these transitions are behavioral
effects of the patch - none of the affected tests exercise the
`dl_open_worker` code path that the patch modifies.

The x86_64 `make check` run reported in this document, executed under
identical conditions for both sides on the same host, shows zero
PASS->FAIL transitions attributable to the patch. This is consistent
with the CI's own classification of the aarch64 transitions as
flaky/environmental.

CI artifacts referenced:

- `notify/results.compare.txt` - full per-test diff (reference vs. patched)
- `notify/regressions.sum` - CI's regression summary (1 stable, 37 flaky)
- `notify/fixes.sum` - CI's improvements summary (96 stable fixes)
- `sumfiles/xfails.xfail` - known flaky / known failure list
- `00-sumfiles/tests.log.1`, `tests.log.0` - per-test output logs

All under
`http://54.172.246.49:9090/jobs/tcwg_glibc_check--master-aarch64-precommit/=
builds/10384/archive/artifacts/artifacts.precommit/`.

--=20
You are receiving this mail because:
You are on the CC list for the bug.=