Skip to content

fix(libc): fail a rename of a path that ends in a dot component with EINVAL - #5008

Closed
xXPBSXx wants to merge 2 commits into
boykopovar:mainfrom
xXPBSXx:fix/rename-dot-component-v2
Closed

xXPBSXx wants to merge 2 commits into
boykopovar:mainfrom
xXPBSXx:fix/rename-dot-component-v2

Conversation

@xXPBSXx

@xXPBSXx xXPBSXx commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

rename and sceKernelRename resolved a path such as dir/. to dir before renaming, so rename("dir/.", "other") moved the directory dir. FreeBSD 11.4's lookup (vfs_lookup.c) fails a last path component of . for the DELETE and RENAME operations with EINVAL (22), for the source and the destination alike: rename("dir/.", x), rename(x, "dir/."), rename(".", x) and rename(x, ".") all fail.

A small header, RenameDot.hpp, checks the guest string before it is resolved (resolving removes the .): a last component of . or .. (also under trailing slashes, dir/./) fails with EINVAL when its parent directory exists, ENOENT when the parent does not and ENOTDIR when it is not a directory. rename (libc) and sceKernelRename (libkernel) call it for both arguments. A last component of .. is refused the same way: vfs_lookup.c fails ISDOTDOT together with ISLASTCN for DELETE and RENAME with EINVAL. The same lookup rule applies in FreeBSD to rmdir("dir/."), unlink("dir/.") and the same calls with ..; those are not changed here (the guest still resolves the dot component away and acts on dir or its parent) and stay open. Both open points are in the new TechnicalDebt entry for libc (docs/dev/debt/libc.md).

Order of the checks: the header looks at both arguments before any host access, so rename("missing", "dir/.") answers EINVAL here. FreeBSD's kern_renameat looks the source up first (namei with DELETE) and the destination afterwards (namei with RENAME), so there the source error comes first: ENOENT for a missing source (read in release/11.4.0). This difference and the backslash separator are in the TechnicalDebt entry for libc (next commit).

No title evidence: found by calling rename with dot components in a failure-path probe. This is FreeBSD conformance; the effect on a title would be a directory moved by a call that should have failed.

Overlap (git merge-tree and the overlap bot against main b4d76ba60): the bot lists #4681 and #5009 (docs/dev/debt/libc.md L57) and #5091 and #5144 (core/libs/prx/libkernel/CMakeLists.txt L270); this PR's TechnicalDebt entry now follows the realpath entry and its test registration sits before guest_fstatfs, away from those lines, and the merge is clean. #4938 changes neighbouring code in Rename.cpp, Stdio.cpp and FilesystemError.hpp; this PR leaves FilesystemError.hpp alone and adds its own header. #4532 is merged. FinalPathComponentIsDot_nid_no_patch in General.cpp (used by remove and the stat calls) already finds a final . for / and \; this header does not reuse it because it also has to refuse .. and to tell a missing parent (ENOENT) and a parent that is not a directory (ENOTDIR) from EINVAL, and it treats / and \ the same way. #5265 (new tests registered in the same part of core/libs/prx/libkernel/CMakeLists.txt) conflicts with this PR in that file (git merge-tree of the two heads, re-checked; #5325 and #5326 are closed); whichever lands second rebases the test registration. #5377 (run sceKernelRename through the checks rename uses) rewrites the body of sceKernelRename in Stdio.cpp into one call to RenameGuestPath_nid_no_patch, the function in which this PR also refuses . and ..; the two conflict in the lines of sceKernelRename (git merge-tree of the two heads, re-checked; each is clean against main 30f961266). Taking #5377's version of sceKernelRename keeps this PR's behaviour, because the check in RenameGuestPath_nid_no_patch then covers sceKernelRename as well, and guest_rename_dot still tests that path; whichever lands second rebases.

Tested

Windows 10 (19045), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, first run, in a combined build on main b4d76ba60 (rebased afterwards; the re-run with the current head is the bullet "Re-run on 14 October 2026" below):

  • guest_rename_dot (new) passes: rename from dir/., dir/./, dir/.. and dir/../, to dir/. and dir/.., of . and .. and onto . fail with EINVAL, a missing parent gives ENOENT, sceKernelRename returns EINVAL for dir/., dir/.. and a destination of dir/., dir\. and dir\.. are refused too (the guest resolver turns a backslash into a separator), nothing is moved by the failed calls, and an ordinary rename / sceKernelRename still works.
  • It fails without the change (rename from dir/. must fail with EINVAL (22) (errno 2)).
  • The cases with a backslash fail without the separator handling in the header (rename from dir\. must fail with EINVAL (22), the guest resolver turns the backslash into a separator (errno 2)).
  • guest_filesystem, guest_kernel_rename, guest_file_vector, guest_directory* and the path tests pass. python tools/check_conventions.py --base upstream/main passes.
  • Re-run on 14 October 2026 (Windows 10 19045, MinGW-w64 GCC 15.2.0, Release, -j4), the head 5220be761 (a0ad44f13 plus a commit that only changes docs/dev/debt/libc.md) merged onto main d1670009f: guest_rename_dot passes (0.11 s, ctest); with Rename.cpp and Stdio.cpp taken back to main and the new test kept it fails (rename from dir/. must fail with EINVAL (22) (errno 2), exit code 0xc0000409). Group run with the code head a0ad44f13 on the same main, the targets built as well: on main alone guest_filesystem, guest_file_vector, guest_memory, guest_kernel_sys, guest_signals and guest_directory_buffer pass (6 of 6); with the head and its test 7 of 7 pass, 0 failed. This replaces the earlier combined-build figures. Not run: the socket tests, Linux, a console, a title.
  • Not tested: Linux (the check runs on the guest string before any host call), a console, a title run.

Checklist

  • Based on current main; no other open PR implements the same functions (overlap described above)
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt (docs/dev/debt/libc.md)
  • No notes, investigation .md files or images added to the repository
  • New third-party code is a submodule built from source (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • AI-assisted: yes

AI-assisted (Claude Code).

🤖 Generated with Claude Code

@xXPBSXx xXPBSXx changed the title fix(libkernel): fail rename of a path ending in a dot component with EINVAL fix(libc): fail a rename of a path that ends in a dot component with … Oct 13, 2026
@xXPBSXx xXPBSXx changed the title fix(libc): fail a rename of a path that ends in a dot component with … fix(libc): fail a rename of a path that ends in a dot component with EINVAL Oct 13, 2026
@xXPBSXx
xXPBSXx force-pushed the fix/rename-dot-component-v2 branch from a60be13 to 0b741ec Compare October 13, 2026 09:34
@github-actions github-actions Bot added the docs label Oct 13, 2026
@xXPBSXx
xXPBSXx force-pushed the fix/rename-dot-component-v2 branch from 0b741ec to f4fccf5 Compare October 13, 2026 14:19
…EINVAL

rename and sceKernelRename resolved "dir/." to "dir" and renamed the directory. FreeBSD 11.4's lookup fails a last component of "." for DELETE and RENAME with EINVAL, for the source and the destination alike. Check the guest string before it is resolved: a last component of "." (also under trailing slashes) fails with EINVAL when its parent exists, ENOENT when it does not and ENOTDIR when it is not a directory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@xXPBSXx
xXPBSXx force-pushed the fix/rename-dot-component-v2 branch from f4fccf5 to a0ad44f Compare October 13, 2026 15:28
@github-actions

github-actions Bot commented Oct 13, 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
#5265 core/libs/prx/libkernel/CMakeLists.txt L1250
#5377 core/libs/prx/libkernel/File/src/Stdio.cpp L1195-1212

…he rename dot component differences

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing: no title is known to reach this. With the queue at 700+ open PRs, please hold off on new PRs that don't name a title needing the change until it's down. If a title stops on this on current main, reopen with the title ID and the log line.

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.

2 participants