From 17e03d3c0af4338bff1fb71328f80ec5e99f8a27 Mon Sep 17 00:00:00 2001 From: fablevoid <340827264+fable-void@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:49:43 +0000 Subject: [PATCH] fix(libc): fail fgets with a size of 0 or less like FreeBSD The guest fgets handed the size straight to the host. With a size of 0 glibc and the Windows runtime return NULL without setting errno or the stream error indicator, and a negative size goes to the Windows invalid-parameter path. FreeBSD's fgets checks n <= 0 before reading: it sets __SERR on the stream, sets errno to EINVAL and returns NULL, leaving the buffer and the file position untouched. fgets_nid_postfix now does the same before calling the host. The FileStream flag it uses was only set for encoding errors so far and is renamed from SetEncodingError/encodingError to SetError/guestError; it already stays set until clearerr like __SERR. guest_standard_streams checks sizes 0, -1 and INT_MIN (NULL, errno 22, error indicator set and cleared by clearerr, buffer and position untouched) and that a following read still returns the line; it fails without the change. Fixes #3084 AI-assisted: yes Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/FileStream.hpp | 14 +++++------ core/libs/prx/libc/src/StandardStreams.cpp | 7 +++++- core/libs/tests/GuestStandardStreams.cpp | 27 ++++++++++++++++++++-- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index 7a1b467be..9fff6280e 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -42,7 +42,7 @@ class FileStream { std::byte _reserved[256 - sizeof(GuestFilePrefix)]{}; std::FILE* _handle; bool _dynamic; - bool encodingError = false; + bool guestError = false; public: explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) { @@ -72,7 +72,7 @@ public: bool Reopen(const std::filesystem::path& filename, const char* mode) { auto* previous = GetHandle(); _guest = {}; - encodingError = false; + guestError = false; #ifdef _WIN32 std::wstring wideMode; for (const char character : std::string_view(mode)) wideMode.push_back(static_cast(character)); @@ -94,7 +94,7 @@ public: _guest.readRemaining = 0; _guest.writeRemaining = 0; _guest.flags = static_cast((_guest.flags & ~0x60) | - (std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) || encodingError ? 0x40 : 0)); + (std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) || guestError ? 0x40 : 0)); } void SyncUnlockedStatus() { @@ -108,17 +108,17 @@ public: const bool error = ::ferror_unlocked(GetHandle()) != 0; #endif _guest.flags = static_cast((_guest.flags & ~0x60) | - (endOfFile ? 0x20 : 0) | (error || encodingError ? 0x40 : 0)); + (endOfFile ? 0x20 : 0) | (error || guestError ? 0x40 : 0)); } - void SetEncodingError() { - encodingError = true; + void SetError() { + guestError = true; SyncStatus(); } void ClearError() { std::clearerr(GetHandle()); - encodingError = false; + guestError = false; SyncStatus(); } diff --git a/core/libs/prx/libc/src/StandardStreams.cpp b/core/libs/prx/libc/src/StandardStreams.cpp index 1d51cdb37..69e24de20 100644 --- a/core/libs/prx/libc/src/StandardStreams.cpp +++ b/core/libs/prx/libc/src/StandardStreams.cpp @@ -27,7 +27,7 @@ std::int32_t APS5_VABI ungetwc_nid_postfix(std::int32_t value, FileStream* strea if (value == -1) return -1; if (value < 0 || value > 255) { errno = 86; - stream->SetEncodingError(); + stream->SetError(); return -1; } const int result = std::ungetc(value, GetNativeStream(stream)); @@ -69,6 +69,11 @@ int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream) { return result; } char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream) { + if (size <= 0) { + stream->SetError(); + errno = 22; + return nullptr; + } auto* result = std::fgets(buffer, size, GetNativeStream(stream)); stream->SyncStatus(); return result; diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index cb428b927..c90016ce2 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -119,7 +120,7 @@ static void CheckUnlockedStreams() { Require(std::ungetc('Q', stream.GetHandle()) == 'Q'); Require(getc_unlocked_nid_postfix(&stream) == 'Q' && !(guest.flags & 0x20)); std::rewind(stream.GetHandle()); - stream.SetEncodingError(); + stream.SetError(); Require(putc_unlocked_nid_postfix('R', &stream) == 'R' && (guest.flags & 0x40)); clearerr_nid_postfix(&stream); Require(!(guest.flags & 0x40)); @@ -176,6 +177,27 @@ static void CheckUnlockedStreamLock(bool write) { stream.Close(); } +static void CheckFgetsSize() { + FileStream stream(std::tmpfile()); + Require(fwrite_nid_postfix("line\n", 1, 5, &stream) == 5); + rewind_nid_postfix(&stream); + for (const int size : {0, -1, std::numeric_limits::min()}) { + char buffer[8]; + std::memset(buffer, '!', sizeof(buffer)); + *__error_nid_postfix() = 0; + Require(fgets_nid_postfix(buffer, size, &stream) == nullptr); + Require(*__error_nid_postfix() == 22 && ferror_nid_postfix(&stream) != 0 && feof_nid_postfix(&stream) == 0); + Require(buffer[0] == '!' && ftello_nid_postfix(&stream) == 0); + Require(ferror_nid_postfix(&stream) != 0); + clearerr_nid_postfix(&stream); + Require(ferror_nid_postfix(&stream) == 0); + } + char line[8]{}; + Require(fgets_nid_postfix(line, sizeof(line), &stream) == line && std::strcmp(line, "line\n") == 0); + Require(ferror_nid_postfix(&stream) == 0); + stream.Close(); +} + static void CheckRewind() { FileStream stream(std::tmpfile()); Require(fwrite_nid_postfix("ab", 1, 2, &stream) == 2); @@ -186,7 +208,7 @@ static void CheckRewind() { rewind_nid_postfix(&stream); Require(fgetc_nid_postfix(&stream) == 'a' && fgetc_nid_postfix(&stream) == 'b' && fgetc_nid_postfix(&stream) == EOF); Require(feof_nid_postfix(&stream) != 0); - stream.SetEncodingError(); + stream.SetError(); Require(ferror_nid_postfix(&stream) != 0); rewind_nid_postfix(&stream); Require(feof_nid_postfix(&stream) == 0 && ferror_nid_postfix(&stream) == 0 && fgetc_nid_postfix(&stream) == 'a'); @@ -458,6 +480,7 @@ int main() { Require(fgetc_nid_postfix(&locked) == 'A' && fgetc_nid_postfix(&locked) == 'B'); locked.Close(); CheckRewind(); + CheckFgetsSize(); CheckUnlockedStreams(); CheckUnlockedStreamLock(false); CheckUnlockedStreamLock(true);