Skip to content

fix(libc): open closed standard descriptors on /dev/null when libc loads - #6107

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
YossiYad:fix/libc-closed-standard-descriptors
Oct 18, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
YossiYad:fix/libc-closed-standard-descriptors

Conversation

@YossiYad

Copy link
Copy Markdown
Contributor

What

A converted title started with its standard input closed (a launcher or a shell that closes it, ./app.elf <&-) gets one of AnyPS5's own descriptors as its standard input. The first descriptor the libraries open takes the lowest free number, and on Linux that is the write watch's userfaultfd: /proc/<pid>/fd/0 reads anon_inode:[userfaultfd] about 100 ms in. Yamagi Quake II (PPSA99007) calls fcntl on descriptor 0 at start-up and stops on

terminate called after throwing an instance of 'std::runtime_error'
  what():  fcntl: descriptor 0 is neither a file, a directory, a pipe nor a character device

The same happens to descriptors 1 and 2: a guest write to a closed stdout would go to whatever AnyPS5 opened in its place.

  • libc: a static initializer in StandardStreams.cpp opens /dev/null on each of descriptors 0, 1 and 2 that is closed when libc loads, read-only for 0 and write-only for 1 and 2, the way glibc's check_fds.c does for set-user-ID programs. libc is loaded and initialized before the libraries that depend on it, so this runs before any of them opens a descriptor. Descriptors that are open are not touched. Linux and macOS only.
  • New guest_closed_standard_descriptors: the test runs itself again with stdin, stdout, or all three closed, and the child checks that each closed descriptor is /dev/null with the expected access mode by the time main runs, and that the guest fcntl on descriptor 0 succeeds. If a libc initializer opened a descriptor before this one, it would take descriptor 0 and the check would fail.
  • Debt entry: the initializer order inside libc that this relies on, what the console gives a title as its standard descriptors (not measured), and that Windows is not covered.

Title. Yamagi Quake II (PPSA99007, catalog release) with ./app.elf <&-: on main it aborts on the fcntl throw above in each of 3 runs. With this PR it no longer aborts and runs the same as when started with </dev/null (in both cases it then leaves on its own after start-up and tears down cleanly with status 0).

Tested

Linux (Bazzite 44, GCC 16.2.1, Ninja, Release, -DBUILD_TESTING=ON), RX 7700 XT (RADV), based on main 922abe6a4:

  • Full build and ctest: 1187 of 1211 pass and 21 are skipped (display tests). agc_driver_tessellation and application_heap_lazy segfault, and agc_host_import_sparse fails, on unchanged main here too (test(agc): drop a shared range's backing when the range goes away #5759 fixes the last one).
  • guest_closed_standard_descriptors passes 5 of 5 (--repeat until-fail:5). With the previous StandardStreams.cpp it fails on its first case (child exited with 74: descriptor 0 is not /dev/null).
  • Title run (the fix on main 4808d9c20, hidden display, no controller): as above.
  • tools/check_conventions.py --base upstream/main passes. tools/pr_overlap.py reports feat(libc): implement wide and unlocked stream I/O #5816 in docs/dev/debt/libc.md only (a neighbouring line). Windows and macOS were not built here.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw; no silent stubs added
  • No notes, investigation .md files or images added to the repository
  • No new third-party code
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA99007 started without a standard input
  • GPUs tested: AMD RX 7700 XT (RADV)
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 4972 core/libs/prx/libkernel/CMakeLists.txt
PR 5021 core/libs/prx/libc/src/StandardStreams.cpp
core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libc.md
PR 5303 docs/dev/debt/libc.md
PR 6044 core/libs/prx/libkernel/CMakeLists.txt
PR 6222 core/libs/prx/libc/src/StandardStreams.cpp
core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libc.md
PR 6226 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libc.md
PR 6244 core/libs/prx/libkernel/CMakeLists.txt
PR 6334 docs/dev/debt/libc.md
PR 6483 docs/dev/debt/libc.md
PR 6565 docs/dev/debt/libc.md
PR 6658 core/libs/prx/libkernel/CMakeLists.txt
PR 6908 core/libs/prx/libkernel/CMakeLists.txt
PR 6936 core/libs/prx/libkernel/CMakeLists.txt
PR 6977 core/libs/prx/libkernel/CMakeLists.txt
PR 7192 core/libs/prx/libkernel/CMakeLists.txt
PR 7201 core/libs/prx/libc/src/StandardStreams.cpp
core/libs/prx/libkernel/CMakeLists.txt
PR 7299 core/libs/prx/libkernel/CMakeLists.txt
PR 7447 docs/dev/debt/libc.md
PR 7464 core/libs/prx/libkernel/CMakeLists.txt

This was referenced Oct 15, 2026
@YossiYad
YossiYad force-pushed the fix/libc-closed-standard-descriptors branch from c4c90bd to a7ef31e Compare October 15, 2026 15:23
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 15, 2026
@YossiYad

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (head a7ef31e). The only conflict was in docs/dev/debt/libc.md, where main had expanded the neighbouring *_unlocked and fgetws lines; I kept those as they are on main and added the "Closed standard descriptors" line after them. The fix and test commits are unchanged.

Checks are green and it merges cleanly. Could you take a look when you have a moment, and merge if it looks right?

@screamm

screamm commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Windows check of head a7ef31e10 on main 44df21632 (Windows 11 Pro 26220, RTX 4070 596.21, MinGW GCC 15.2). This PR is Linux and macOS code, so this is a no-regression run only.

  • guest_closed_standard_descriptors does not exist on Windows (ctest -N lists no such test; it is registered under if(UNIX) and uses fork/execv), so no 3x run or negative control is possible. The initializer in StandardStreams.cpp is under #ifndef _WIN32, so the Windows code path is not covered; the debt line already says that.
  • PPSA99007 (Yamagi Quake II 0.2.0-alpha.1), relinked, 120 s per run with the display:
    • stdin closed with Git Bash ./app.exe 0<&-: main 13934 frames, with this PR 13941 frames.
    • cmd /c "app.exe < NUL": main 13562 frames, with this PR 13450 frames.
    • All four reach the 120 s limit (killed by the harness) with no stop line; the log holds the same three start-up lines before and after. main on Windows does not hit the fcntl: descriptor 0 ... throw from the description with stdin closed, so there is nothing for the PR to fix there.
  • Not checked: a launch with no standard handles at all (detached process), the Windows case your debt entry mentions; the full build and suite on this head.

Written with Claude Code assistance (validation by a subagent, reviewed in this session).

@adriadam10 adriadam10 self-assigned this Oct 16, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the initializer in StandardStreams.cpp, the new guest_closed_standard_descriptors (passes; it fails without the fix) and the debt entry. Build and the libc/libkernel ctest scope are green on the PR merged onto main; merging.

@YossiYad

Copy link
Copy Markdown
Contributor Author

@adriadam10 thanks for checking it. It's still open; it merges cleanly onto current main and all checks are green, so it's ready whenever you have a moment to merge.

@adriadam10
adriadam10 merged commit e1dae23 into boykopovar:main Oct 18, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants