Skip to content

fix(libkernel): return EBADF from fstat on a descriptor that is not open on Windows - #6897

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
tv7:fix/fstat-closed-descriptor
Oct 19, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
tv7:fix/fstat-closed-descriptor

Conversation

@tv7

@tv7 tv7 commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

What

On Windows, sceKernelFstat and fstat pass the descriptor to the UCRT's _fstat64. For a descriptor that is not open, the UCRT raises its invalid-parameter handler. The crash report installs that handler as fatal, so the calling thread dies with FATAL: UCRT invalid parameter, where the console returns EBADF.

DoFstat now looks the descriptor up with WindowsDescriptorHandle_nid_no_patch (which already silences the handler for that lookup) and fails with EBADF when it is not open, before calling _fstat64. sceKernelFstat then returns SCE_KERNEL_ERROR_EBADF (0x80020009) and fstat returns -1 with errno 9, like the other descriptor calls the test already checks (fchmod, futimes, fsync, fdatasync).

Dying Light 2 (PPSA02262) hits this on a loading thread right after its sign-in step:

[SAVEDATA:native] mount3 user=268435456 dir=DATA mode=0x1 blocks=0 -> 0x809f0008 point= status=0
FATAL: UCRT invalid parameter on thread 12600
  stack return address candidates:
    ... libkernel.prx+0x20ee0   (File::FillFileStatFromDescriptor)
    ... libkernel.prx+0x29845   (sceKernelFstat)
    ... libc.prx.guest.prx+0x1a706

Tested

Windows 11, RTX 3070, GCC 15.2.0 UCRT, Release, on main 66e290f1c.

  • guest_filesystem: new checks that sceKernelFstat on a closed file descriptor, a closed socket and 0x7fffffff returns 0x80020009 without raising the invalid-parameter handler (counted with a recording handler, as guest_dup does for close). On main the handler is raised (the check fails at that line); with this change it passes.
  • ctest: all pass except two that fail the same way on main without this change: agc_driver_gpu_indirect_arguments_misaligned_writer_ahead does not compile with MinGW (alignment 65536 exceeds object file maximum 8192), and guest_kqueue exits with 0xc0000409.
  • tools/check_conventions.py passes.
  • Dying Light 2 (PPSA02262), with a local change past its PSN sign-in (see Dying Light 2 (PPSA02262) on Windows: what stops it between conversion and its own startup #1794): without this change the loading thread dies as above; with it there is no fatal report. The title still waits after sign-in for a separate reason.

Checklist

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 7202 core/libs/prx/libkernel/File/src/NativeStat.cpp L11

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 5021 core/libs/prx/libkernel/File/src/NativeStat.cpp
PR 6044 core/libs/tests/GuestFilesystem.cpp
PR 6879 core/libs/prx/libkernel/File/src/NativeStat.cpp
PR 7201 core/libs/prx/libkernel/File/src/NativeStat.cpp
PR 7332 core/libs/prx/libkernel/File/src/NativeStat.cpp

@karimibrahim07 karimibrahim07 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows check of head 6a402a145 merged onto main e3a8e4430 (clean merge). Windows 11 Pro (26300), Ryzen 5 3600, WinLibs GCC 15.2.0 r7 (UCRT), Ninja Release.

  • guest_filesystem, guest_dup, guest_fstatfs, guest_file_timestamps, guest_file_vector and the six guest_file_lock_* tests pass, --repeat until-fail:3.
  • Negative control: with main's NativeStat.cpp and the new test kept, guest_filesystem fails at line 628, Require(!g_invalidParameter). The three sceKernelFstat return-value checks just above it pass on main too: with the test's recording handler the UCRT returns and _fstat64 fails with EBADF anyway. So the handler check is what makes this test fail on main. That matches the description: only the fatal handler that the crash report installs turns this into the Dying Light 2 stop.

Code: WindowsDescriptorHandle_nid_no_patch is already how DescriptorControl.cpp checks descriptors (6 uses), so fstat now validates the same way as fcntl. The check comes after the directory-descriptor lookup, so directory descriptors are unaffected.

Overlap: the description says #6228 "edits the line above" in DoFstat. It deletes that line (the DirectoryDescriptorPath early return), so git reports a conflict between the two PRs in NativeStat.cpp. The resolution is to keep this PR's check and drop the directory line. One thing to check when that happens: after #6228, directory descriptors reach this check, so WindowsDescriptorHandle_nid_no_patch must return a valid handle for them, or fstat on a directory would become EBADF. I couldn't test the two together, because #6228 currently also conflicts with main in Stdio.cpp.

AI-assisted: yes. I ran the build, the tests and the negative control above.

@github-actions github-actions Bot added unblocks title Names a title ID on the Unblocks line needs rebase Conflicts with main, rebase needed labels Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
…pen on Windows

sceKernelFstat and fstat passed any descriptor to the UCRT's _fstat64, which
raises the invalid-parameter handler for one that is not open. The crash
report installs that handler as fatal, so the calling thread died where the
console returns EBADF. Dying Light 2 (PPSA02262) hits it on a loading thread
right after sign-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10
adriadam10 force-pushed the fix/fstat-closed-descriptor branch from 6a402a1 to 4fe32a4 Compare October 19, 2026 10:47
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@adriadam10
adriadam10 merged commit 15047c6 into boykopovar:main Oct 19, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (the only conflict was in DoFstat, where #6228 removed the directory-descriptor line; kept main's version plus the EBADF guard). Checked that the new test pins the invalid-parameter handler count and that the libs/libkernel ctest set passes on Linux; Windows is covered by @karimibrahim07's run and the queue's Windows job; merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libkernel unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants