diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 07fc5468a8a..2d8db357d33 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -361,6 +361,13 @@ int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) { return result; } +static bool SizeOverflowsInt(char* buffer, size_t size) { + if (size == 0 || size - 1 <= static_cast(INT_MAX)) return false; + errno = 84; + if (buffer != nullptr) *buffer = '\0'; + return true; +} + int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); @@ -402,6 +409,7 @@ int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { } int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) { + if (SizeOverflowsInt(buffer, size)) return -1; __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); const int result = LibcDetail::FormatWindows(buffer, size, format, args); @@ -438,6 +446,7 @@ int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { } int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) { + if (SizeOverflowsInt(buffer, size)) return -1; std::va_list args; va_start(args, format); const int result = RequiresGuestFormatting(format) ? LibcDetail::FormatWindows(buffer, size, format, args) : std::vsnprintf(buffer, size, format, args); @@ -540,6 +549,7 @@ int APS5_VABI vsprintf_nid_postfix(char* str, const char* format, VaList* args) } int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c) { + if (SizeOverflowsInt(str, size)) return -1; #ifdef _WIN32 return LibcDetail::FormatWindows(str, size, format, c); #else diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index f22d7880dd8..937f348f175 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/GuestHeap.hpp" #include #include +#include #include #include #include @@ -84,6 +85,34 @@ static int APS5_VABI PrintList(const char* format, ...) { return result; } +static void CheckSizeLimits() { + using Formatter = int (APS5_VABI*)(char*, size_t, const char*, ...); + for (const Formatter format : {&snprintf_nid_postfix, &FormatList}) { + char output[8]; + for (const std::size_t oversized : {static_cast(INT_MAX) + 2, static_cast(SIZE_MAX)}) { + for (const bool wide : {false, true}) { + std::memset(output, 'Z', sizeof(output)); + int consumed = -1; + *__error_nid_postfix() = 0; + const int result = wide ? format(output, oversized, "%ls%n", u"x", &consumed) + : format(output, oversized, "x%n", &consumed); + Require(result == -1 && *__error_nid_postfix() == 84 && output[0] == 0 && consumed == -1); + Require(std::all_of(output + 1, output + sizeof(output), [](char value) { return value == 'Z'; })); + } + } + for (const std::size_t capacity : {std::size_t{0}, std::size_t{1}, static_cast(INT_MAX), static_cast(INT_MAX) + 1}) { + std::memset(output, 'Z', sizeof(output)); + *__error_nid_postfix() = 35; + Require(format(output, capacity, "x") == 1 && *__error_nid_postfix() == 35); + Require(output[0] == (capacity == 0 ? 'Z' : capacity == 1 ? '\0' : 'x')); + const std::size_t written = capacity < 2 ? capacity : 2; + if (capacity > 1) Require(output[1] == 0); + Require(std::all_of(output + written, output + sizeof(output), [](char value) { return value == 'Z'; })); + } + Require(format(nullptr, 0, "x") == 1); + } +} + static bool CheckWidePrecision() { const char16_t input[] = u"A\u00e9\u20ac\U0001f600Z"; const char* expected[] = {"", "A", "A", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9\xe2\x82\xac", @@ -368,4 +397,4 @@ static void CheckAllocatingFormat() { free_nid_postfix(allocated); } -int main() { RunChecks(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } +int main() { CheckSizeLimits(); RunChecks(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 581f589299e..5560872f1a7 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -52,6 +52,8 @@ - [__umodti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead - [__modti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead +- [snprintf and vsnprintf](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) reject sizes above `INT_MAX + 1` with `EOVERFLOW` (84), a terminating NUL and return value -1, following FreeBSD 11.4 [snprintf.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/snprintf.c) and [vsnprintf.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/vsnprintf.c). This limit is unverified on the console. A null buffer at an oversized nonzero size returns the error without writing, where FreeBSD dereferences it. + ### Functional - [_FSinh and _FCosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) finite results are not verified bit for bit against the console: the scaled product uses the host's binary64 hyperbolic function before rounding to binary32, rather than the float rational and `_FExp` sequence in PRAGMATA's bundled `libc.prx`. NaN preference and payloads, signed zeros, infinite operands and the range-error thresholds follow that binary's instructions; the system `libSceLibcInternal` implementation and nondefault flush-to-zero modes are unverified.