Skip to content

win: fix error reporting when WTF-8 conversion fails in fs paths - #6

Open
dylan-conway wants to merge 2 commits into
v1.xfrom
claude/elated-engelbart-6a9d94
Open

win: fix error reporting when WTF-8 conversion fails in fs paths#6
dylan-conway wants to merge 2 commits into
v1.xfrom
claude/elated-engelbart-6a9d94

Conversation

@dylan-conway

Copy link
Copy Markdown
Member

Summary

Two Windows fs functions mishandle failures of the internal UTF-16 to WTF-8 conversion (uv_utf16_to_wtf8()). That function reports failure by returning a negative UV error code (UV_ENOMEM when its allocation fails) and does not set the thread's Win32 last error — but the surrounding code assumed the "-1 with GetLastError()" convention.

  • uv_fs_realpath reported success with a NULL path pointer. fs__realpath() only treated -1 as failure, so a negative UV code from the conversion fell through to SET_REQ_RESULT(req, 0): the request completed with result == 0 while req->ptr was still NULL. Callers that trust the documented contract (success implies a valid path pointer) crash dereferencing it. Fixed by treating any negative return as failure and propagating the UV error code for the conversion case. Also capture GetLastError() before CloseHandle() so a succeeding close cannot clobber the failure code.
  • uv_fs_readlink (and the lstat reparse-point path) reported a stale error. fs__readlink_handle() mixed two failure conventions: most paths set the last error and return -1, but the Linux-symlink branch returned UV_ENOMEM directly and the tail returned the conversion result verbatim. All three callers report failures via GetLastError(), so a conversion failure surfaced as whatever unrelated error happened to be set, instead of UV_ENOMEM. Fixed by normalizing the helper to always set the last error and return -1 (ERROR_OUTOFMEMORY translates back to UV_ENOMEM).

Both bugs are only reachable when malloc fails inside the conversion, so they are out-of-memory-only in practice. No behavior change on any success path. Both bugs also exist in upstream libuv v1.x.

Test plan

  • Full build on Windows (MSVC/clang) with no new warnings
  • uv_run_tests_a fs_realpath passes
  • uv_run_tests_a fs_readlink, fs_symlink, fs_symlink_dir, fs_symlink_junction pass
  • uv_run_tests_a fs_stat_root, fs_lstat_windows_store_apps, fs_unlink_readonly pass (cover the other fs__readlink_handle callers)
  • Verified uv_utf16_to_wtf8() can only fail with UV_ENOMEM in these call paths (the UV_ENOBUFS path requires a caller-supplied buffer; these sites pass a NULL target so the buffer is sized exactly by uv_utf16_length_as_wtf8())

fs__realpath_handle() returns -1 with the Win32 last error set for
GetFinalPathNameByHandleW failures, but returns the result of
uv_utf16_to_wtf8() directly from its tail. That function reports
failure with a negative UV error code (UV_ENOMEM) and does not set
the thread's last error. fs__realpath() only treated -1 as failure,
so a conversion failure fell through to SET_REQ_RESULT(req, 0):
the request reported success with req->ptr still NULL, and callers
dereferencing the path pointer would crash.

Treat any negative return as failure, propagating the UV error code
for the conversion case. Also capture GetLastError() before
CloseHandle() so the close cannot clobber the failure code.
fs__readlink_handle() mixed two failure conventions: most paths set
the Win32 last error and return -1, but the LX symlink branch
returned UV_ENOMEM directly and the tail returned uv_utf16_to_wtf8()'s
result verbatim, which on failure is a negative UV error code with
the thread's last error untouched. All three callers report failures
with GetLastError(), so a conversion failure surfaced as whatever
stale error happened to be set instead of UV_ENOMEM.

Normalize the helper to always set the last error and return -1 on
failure, matching its other failure paths. ERROR_OUTOFMEMORY
translates back to UV_ENOMEM, and allocation failure is the only way
the conversion can fail here since the buffer is sized by
uv_utf16_length_as_wtf8() over the same input.

Same family of bug as the earlier uv_fs_realpath fix.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ff0f830-dfca-4f8a-825d-93ecf52fd0d8

📥 Commits

Reviewing files that changed from the base of the PR and between 439a54b and 2baf2df.

📒 Files selected for processing (1)
  • src/win/fs.c

Walkthrough

In src/win/fs.c, error propagation in the readlink and realpath paths is corrected. Allocation and UTF-16 conversion failures in readlink now call SetLastError(ERROR_OUTOFMEMORY) and return -1. fs__realpath captures the converter result and Win32 error before closing the handle, then routes r == -1 to Win32 error handling and r < 0 to SET_REQ_UV_ERROR.

Changes

Win32 fs error propagation

Layer / File(s) Summary
readlink: malloc and conversion error mapping
src/win/fs.c
Malloc failure on the Linux symlink path and UTF-16→WTF-8 conversion failure now both invoke SetLastError(ERROR_OUTOFMEMORY) and return -1 instead of returning UV error codes directly.
fs__realpath: split Win32 vs UV error outcomes
src/win/fs.c
Introduces DWORD error and ssize_t r locals; captures GetLastError() before closing the handle; dispatches r == -1 to Win32 error handling and r < 0 to SET_REQ_UV_ERROR(req, r, ERROR_OUTOFMEMORY).
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the Windows fs error-reporting fix around WTF-8 conversion failures.
Description check ✅ Passed The description matches the changes and explains the real Windows fs failure cases being fixed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant