Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/libs/prx/libc/src/Formatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion core/libs/tests/Formatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "prx/libc/include/GuestHeap.hpp"
#include <algorithm>
#include <array>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <cstring>
Expand Down Expand Up @@ -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<std::size_t>(INT_MAX) + 2, static_cast<std::size_t>(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<std::size_t>(INT_MAX), static_cast<std::size_t>(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",
Expand Down Expand Up @@ -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; }
2 changes: 2 additions & 0 deletions docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading