Repository navigation
Backport musl's fix for the popen/pclose descriptor race - #17
Open
buyemune-sys wants to merge 2 commits into
Open
buyemune-sys wants to merge 2 commits into
buyemune-sys wants to merge 2 commits into
Conversation
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.
popen gives every child a file action closing the descriptors of the
other popen streams, collected from the open file list under its lock.
musl 1.2.5's fclose closes the descriptor first and leaves the list
afterwards, so a concurrent popen can still see the dead stream. If its
own pipe2 just got that descriptor number back, its child closes its own
pipe end before the dup2, and popen fails with EBADF.
Tarantool's application threads hit this on startup: eight threads load
the metrics module at once, each runs io.popen("getconf PAGESIZE"), and
one of them gets nil and aborts the instance. Glibc guests share musl's
stdio, so this is not a bridge problem but the libc underneath it; plain
musl on Alpine fails the same way.
musl fixed it after 1.2.5 in f21a965 ("fix toctou race in popen
children's closing of other popen pipes"): fclose of a popen stream now
holds the open file list lock across the close. The commit applies
unchanged to the vendored tree.
The conformance battery runs popen from eight threads at once. Before
the fix, every run fails some of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
buyemune-sys
force-pushed
the
fix/musl-popen-race
branch
from
October 20, 2026 08:11
c83c305 to
4500125
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
popenfrom several threads at once sometimes fails with EBADF:This is musl 1.2.5 itself; plain musl on Alpine 3.24 fails the same way. It is not the bridge.
popencollects the descriptors of the other popen streams from the open file list under its lock (ext/musl/src/stdio/popen.c:38) and gives the child a close action for each of them.fclosecloses the descriptor first (ext/musl/src/stdio/fclose.c:13) and leaves the list only afterwards (:28).popencan still see the dead stream. If its ownpipe2just got that descriptor number back, the child closes its own pipe end before thedup2(popen.c:40), andposix_spawnreports EBADF.In practice: Tarantool with
app_threadsstarts eight threads that load the metrics module at once. Each runsio.popen("getconf PAGESIZE"); one of them gets nil and aborts the instance.The fix
This is musl's own fix, upstream commit f21a96538f78 ("fix toctou race in popen children's closing of other popen pipes", 2026-07-28), applied unchanged to the vendored tree.
fcloseof a popen stream now holds the open file list lock across the close.The README's vendored-versions list records the backport.
Test
The conformance battery (
concurrentPipes()) runspopen("true")from 8 threads, 150 times each, and expects no failure. On real glibc it passes in about 0.3 s.On
mainit fails on every run (8 to 15 failures out of 1200 in six runs). With the fix:./build testpasses.