Skip to content

fix(libc): fail an oversized guest path name with ENAMETOOLONG - #5006

Closed
xXPBSXx wants to merge 3 commits into
boykopovar:mainfrom
xXPBSXx:fix/path-name-too-long-v2
Closed

xXPBSXx wants to merge 3 commits into
boykopovar:mainfrom
xXPBSXx:fix/path-name-too-long-v2

Conversation

@xXPBSXx

@xXPBSXx xXPBSXx commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

stat, open, mkdir, unlink, rmdir, rename, access and the other calls that resolve a guest path answered ENOENT (and rename EIO) for a path name that FreeBSD refuses as too long, because the resolved host path simply does not exist. FreeBSD 11.4's namei fails a path component above NAME_MAX (255 bytes) and a path of MAXPATHLEN (1024 bytes including the terminating NUL) with ENAMETOOLONG (63).

Resolve() in General.cpp (behind ResolvePath_nid_no_patch) now checks the whole path (1024 bytes) on the guest string before resolving it, and a component above 255 bytes only when every component before it exists and is a directory (FreeBSD's namei resolves in order, so a missing directory earlier in the path still answers ENOENT); both throw a filesystem_error with filename_too_long; FilesystemError already maps that to 63 and ResolvePathError_nid_no_patch, which the kernel calls use, turns it into the errno. A name of exactly 255 bytes is not affected.

A backslash counts as a path separator for the component length, because the guest path resolver turns it into / before the host call; FreeBSD treats a backslash as an ordinary name character, so a name of 200 characters, a backslash and 100 more characters (301 bytes) is accepted here and refused by FreeBSD.

Limits, listed in the TechnicalDebt entry for libc: the host's own limit still applies below these numbers (on Windows a path whose host form is longer than MAX_PATH fails with ENOENT), and a path through a regular file (file/x, file/) is still not always ENOTDIR; both are separate from this change.

Not covered: sceNgs2ParseWaveformFile calls ResolvePath_nid_no_patch without a try block, so a long component now ends in an uncaught filesystem_error there where a missing file ended in runtime_error ("cannot open"); both end the title, it is not a regression, and it is not changed here. This and the backslash difference above are in the TechnicalDebt entry for libc (second commit).

Callers of ResolvePath_nid_no_patch / Resolve() (read again from the code on main 30f961266): the ResolvePathError_nid_no_patch form catches filesystem_error and returns the errno; the kernel and libc file calls use it (Open.cpp, Stdio.cpp, libkernel_sys/Export.cpp, Directory.cpp, FileAndHeap.cpp, RenameDot.hpp, Apr.cpp and the AvPlayer, Font and LibcInternal path code). Direct callers of the throwing form: access, remove, chdir, rename and realpath in libc (inside their try blocks, mapped by FilesystemError), dlopen (inside its try block, catch (const std::exception&)), chflags in libkernel Stdio.cpp (no try block; see below) and sceNgs2ParseWaveformFile (no try block, see above). The remaining direct callers pass fixed mount-point strings (libSceAppContent, libScePlayGo, libSceSaveData, AppMetadata) and cannot hit the new check.

chflags (added to main in 4dd97f7, after the first base of this branch) resolved its path with the throwing form and without a try block, so a long name would have ended the title once this check exists. The branch is rebased onto main 30f961266; its next commit makes chflags use ResolvePathError_nid_no_patch like chmod, and guest_path_name_length gains a chflags case (it fails without that commit with an uncaught filesystem_error).

No title evidence: found by calling the file functions with oversized names (about 40 failure-path cases, of which 15 differed from FreeBSD; the others are not touched here). This is FreeBSD conformance.

Overlap (checked with git merge-tree against the heads of #4796, #4920, #4922, #4929, #4968, #4989, #4993, #4999, #4532, #4681, #4743, #4817, #4870 and #4877): no conflict. The new test is registered next to guest_file_vector, away from the other test registrations in that file.

Tested

Windows 10 (19045), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, first run on main 0f2683cb7 (the re-run with the current head and main is the bullet "Re-run on 14 October 2026" below):

  • guest_path_name_length (new) passes: stat (first, final and only component of 256 bytes and a path of 1100 bytes), access, unlink, mkdir, rmdir, open and both arguments of rename fail with ENAMETOOLONG; a name of exactly 255 bytes does not, in the final or in a middle component; a middle component of 256 bytes, a long name followed by a trailing slash, a path of exactly 1024 bytes fail and a path of exactly 1023 bytes does not; a long name below a missing directory gives ENOENT.
  • It fails without the change to General.cpp (stat of a name above NAME_MAX (255) must fail with ENAMETOOLONG (63) (errno 2)).
  • guest_kernel_sys (its pathconf case with a missing directory before a 256-byte name must give ENOENT) and the other file, directory, path, realpath, working-directory, rename and stream tests pass with the change. 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 b425544c3 merged onto main 7a0c838ca: guest_path_name_length passes (0.51 s, ctest); with General.cpp taken back to main and the new test kept it fails (stat of a name above NAME_MAX (255) must fail with ENAMETOOLONG (63) (errno 2)). Second run, head merged onto main d1670009f, the targets of the group 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.
  • chflags (second commit): guest_path_name_length also calls chflags with a 256-byte name and requires ENAMETOOLONG; with Stdio.cpp taken back to main the test ends in an uncaught filesystem_error (exit code 0xc0000409); guest_filesystem and guest_kernel_sys pass with the change (measured on the head b425544c3).
  • Not tested: Linux (the check does not depend on the host), 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 an over-long path name with ENAMETOOLONG fix(libc): fail an oversized guest path name with ENAMETOOLONG Oct 13, 2026
@nicolasfalesy

Copy link
Copy Markdown
Contributor

Linux test (Arch, GCC 16.2.1, Release), head 3053b830d on main 4bb2c7f (found by bisecting a batch of open PRs, then confirmed with this one alone): main's guest_kernel_sys fails with Kernel sys check failed at line 97 (guest errno 63). That check is from #3611:

Require(pathconf_nid_postfix((directory + "/missing/" + std::string(256, 'n')).c_str(), 4) == -1 && Error() == 2);

FreeBSD 11's namei resolves components in order, so a missing directory earlier in the path fails with ENOENT before the 256-byte last component is reached; ENAMETOOLONG only comes from the component being looked up (and the 1024-byte whole-path check comes first, which the next check in that test covers). With the check in General.cpp testing every component up front, this case now reports 63. Checking component lengths only up to the first one that does not exist, or leaving the per-component check to the lookup, would keep both tests passing.

@xXPBSXx
xXPBSXx force-pushed the fix/path-name-too-long-v2 branch from 3053b83 to 69b0c69 Compare October 13, 2026 09:43
@xXPBSXx

xXPBSXx commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for measuring this, you are right. The up-front check in Resolve() reported ENAMETOOLONG for an over-long component before the rest of the path was looked up, but FreeBSD's namei resolves the components in order, so pathconf(dir/missing/<256 x n>) has to give ENOENT (guest_kernel_sys, line 97). I had only run a targeted group of tests and missed it.

New head: the component length is checked only when every component before it exists and is a directory (otherwise the normal lookup answers ENOENT); the whole-path check at 1024 bytes stays up front. guest_kernel_sys and guest_path_name_length (extended with the missing/<256 x n> case) pass on Windows. I am running the full guest test list on this head now and will say if anything else shows up.

AI-assisted (Claude Code).

@xXPBSXx
xXPBSXx force-pushed the fix/path-name-too-long-v2 branch from 69b0c69 to c48afd5 Compare October 13, 2026 14:20
@xXPBSXx

xXPBSXx commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up to my comment of 13 October, 09:43Z, @nicolasfalesy: I ran the full guest_* / libc / libkernel list on Windows with this change together with the other open branches of our series on main 0214069: 394 of 394 tests pass (guest_kernel_sys among them).

The current head c48afd57d also adds boundary cases to guest_path_name_length: a component of exactly 255 and 256 bytes in the middle and at the end, a long name followed by a slash, a path of exactly 1023 and 1024 bytes, and a name with a backslash (a backslash counts as a separator here, FreeBSD treats it as an ordinary character; this is written in the description). Thank you again for finding the guest_kernel_sys case.

AI-assisted (Claude Code).

xXPBSXx and others added 3 commits October 14, 2026 00:55
stat, open, mkdir, rename and the other calls that resolve a guest path answered ENOENT (or EIO for rename) for a name longer than 255 bytes or a path of 1024 bytes or more, because the host path just did not exist. FreeBSD 11.4's namei fails a component above NAME_MAX and a path of MAXPATHLEN bytes with ENAMETOOLONG. Check both limits on the guest string before it is resolved; FilesystemError already maps the error to 63.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ONG instead of throwing

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…File exception among the path name length differences

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@xXPBSXx
xXPBSXx force-pushed the fix/path-name-too-long-v2 branch from c48afd5 to b425544 Compare October 13, 2026 23:22
@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.

3 participants