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
42 changes: 42 additions & 0 deletions core/libs/prx/libc/src/Formatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ const char* CutAtQuadScanDirective(const char* format, std::string& head) {
#endif

std::string HostLength(const std::string& length, char conversion) {
if (std::strchr("sc[", conversion) && length != "l") return {};
#ifdef _WIN32
if (length == "l" && std::strchr("diouxXn", conversion) != nullptr) return "ll";
#else
Expand All @@ -110,7 +111,46 @@ std::string HostLength(const std::string& length, char conversion) {
return length;
}

const char* ScanLengthEnd(const char* cursor) {
if (*cursor == 'h' || *cursor == 'l') {
const char first = *cursor++;
if (*cursor == first) ++cursor;
} else if (*cursor != '\0' && std::strchr("jztL", *cursor)) ++cursor;
return cursor;
}

const char* HostScanFormat(const char* format, std::string& normalized) {
if (format == nullptr) return format;
const char* copied = format;
const char* cursor = format;
while ((cursor = std::strchr(cursor, '%')) != nullptr) {
++cursor;
if (*cursor == '%') { ++cursor; continue; }
while (*cursor && std::strchr("-+ #0'0123456789.*$", *cursor)) ++cursor;
const char* const length = cursor;
cursor = ScanLengthEnd(cursor);
const bool wide = cursor == length + 1 && *length == 'l';
if (cursor != length && !wide && *cursor && std::strchr("sc[", *cursor)) {
normalized.append(copied, length);
copied = cursor;
}
if (*cursor == '[') {
++cursor;
if (*cursor == '^') ++cursor;
if (*cursor == ']') ++cursor;
while (*cursor && *cursor != ']') ++cursor;
}
if (*cursor) ++cursor;
else break;
}
if (copied == format) return format;
normalized.append(copied);
return normalized.c_str();
}

int HostVsscanf(const char* input, const char* format, std::va_list args) {
std::string normalized;
format = HostScanFormat(format, normalized);
#if defined(__GLIBC__)
return __isoc99_vsscanf(input, format, args);
#else
Expand All @@ -128,6 +168,8 @@ int HostSscanf(const char* input, const char* format, ...) {

#ifndef _WIN32
int HostVfscanf(std::FILE* stream, const char* format, std::va_list args) {
std::string normalized;
format = HostScanFormat(format, normalized);
#if defined(__GLIBC__)
return __isoc99_vfscanf(stream, format, args);
#else
Expand Down
40 changes: 40 additions & 0 deletions core/libs/tests/FileStreams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <iterator>
#include <algorithm>
#include <barrier>
#include <cerrno>
#include <cstdarg>
Expand Down Expand Up @@ -129,6 +131,43 @@ static std::string ReadWhole(const std::string& path) {
return std::string(buffer.data(), size);
}

static int APS5_VABI ScanSafeList(FileStream* stream, const char* format, ...) {
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
const int result = vfscanf_s_nid_postfix(stream, format, reinterpret_cast<VaList*>(args));
__builtin_sysv_va_end(args);
return result;
}

static void CheckCharacterScanLengths() {
using Scanner = int (APS5_VABI*)(FileStream*, const char*, ...);
const Scanner scanners[]{fscanf_nid_postfix, ScanList, fscanf_s_nid_postfix, ScanSafeList};
for (std::size_t api = 0; api < std::size(scanners); ++api) {
#ifdef _WIN32
if (api < 2) continue;
#endif
for (const char* length : {"hh", "h", "ll", "j", "z", "t", "L"}) {
for (const char* conversion : {"s", "c", "[a-z]"}) {
auto* native = std::tmpfile();
Require(native != nullptr && std::fputs("ab", native) >= 0);
std::rewind(native);
FileStream stream(native);
std::array<char, 32> output;
output.fill('Z');
int consumed = -1;
const std::string format = std::string("%2") + length + conversion + "%n";
const auto scan = scanners[api];
const int result = api >= 2 ? scan(&stream, format.c_str(), output.data(), 32u, &consumed) : scan(&stream, format.c_str(), output.data(), &consumed);
Require(result == 1 && consumed == 2, "Stream character scan ignored-length consumption");
const bool single = conversion[0] == 'c';
Require(output[0] == 'a' && output[1] == 'b' && (single || output[2] == 0), "Stream character scan ignored length stored wide data");
Require(std::all_of(output.begin() + (single ? 2 : 3), output.end(), [](char value) { return value == 'Z'; }));
Require(std::fclose(native) == 0);
}
}
}
}

template<typename TRead>
static void CheckReadAfterWrite(const std::string& path, const char* mode, const char* expected, TRead read) {
auto* stream = fopen_nid_postfix(path.c_str(), "wb");
Expand Down Expand Up @@ -210,6 +249,7 @@ static void CheckWriteAfterRead(const std::string& path, const char* mode, const
}

static void CheckStreamWritesAfterReads(const std::string& base) {
CheckCharacterScanLengths();
const std::string path = base + ".written";
constexpr char appended[] = "0123456789XY";
#if defined(__GLIBC__)
Expand Down
38 changes: 38 additions & 0 deletions core/libs/tests/Formatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "prx/libc/include/ScanQuadLength.hpp"
#include <algorithm>
#include <array>
#include <utility>
#include <iterator>
#include <climits>
#include <cmath>
#include <cstdlib>
Expand Down Expand Up @@ -67,6 +69,41 @@ static int APS5_VABI ScanListS(const char* buffer, const char* format, ...) {
return result;
}

static void CheckCharacterScanLengths() {
const auto check = [](bool condition, const char* message = "Character scan length check failed") {
if (!condition) throw std::runtime_error(message);
};
using Scanner = int (APS5_VABI*)(const char*, const char*, ...);
const Scanner scanners[]{sscanf_nid_postfix, ScanList, sscanf_s_nid_postfix, ScanListS};
for (std::size_t api = 0; api < std::size(scanners); ++api) {
const auto scan = scanners[api];
const bool secure = api >= 2;
#ifdef _WIN32
if (!secure) continue;
#endif
for (const char* length : {"hh", "h", "ll", "j", "z", "t", "L"}) {
for (const char* conversion : {"s", "c", "[a-z]"}) {
const std::string format = std::string("%2") + length + conversion + "%n";
std::array<char, 32> output;
output.fill('Z');
int consumed = -1;
const int result = secure ? scan("ab", format.c_str(), output.data(), 32u, &consumed) : scan("ab", format.c_str(), output.data(), &consumed);
check(result == 1 && consumed == 2, "Character scan ignored-length consumption");
const bool single = conversion[0] == 'c';
check(output[0] == 'a' && output[1] == 'b' && (single || output[2] == '\0'), "Character scan ignored length stored wide data");
check(std::all_of(output.begin() + (single ? 2 : 3), output.end(), [](char value) { return value == 'Z'; }), "Character scan changed its destination guard");
}
}
for (const auto& example : {std::pair{"%lls", "%5[%lls]%n"}, std::pair{"% ab", "%% %2lls%n"}}) {
std::array<char, 32> output{};
int consumed = -1;
const int result = secure ? scan(example.first, example.second, output.data(), 32u, &consumed) : scan(example.first, example.second, output.data(), &consumed);
check(result == 1 && consumed == 4);
check(std::strcmp(output.data(), example.first[1] == ' ' ? "ab" : "%lls") == 0, "Character scan normalization changed a literal or scanset");
}
}
}

static int APS5_VABI PrintList(const char* format, ...) {
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
Expand All @@ -78,6 +115,7 @@ static int APS5_VABI PrintList(const char* format, ...) {
}

static void CheckIgnoredLengths() {
CheckCharacterScanLengths();
char output[96];
Require(snprintf_nid_postfix(output, sizeof(output), "%Ld|%Lx|%Lu|%Lo", -1985229329, 0x89abcdefu, 0x89abcdefu, 0x89abcdefu) == 43 &&
std::strcmp(output, "-1985229329|89abcdef|2309737967|21152746757") == 0);
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
- [sscanf_s and vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) return `EOF` when a reached conversion has a null destination, stopping before further assignments, following C11 K.3.5.3.7 and K.3.5.3.14. Earlier assignments are retained. The constraint handler is not invoked, and console behaviour is unverified.
- [memmem and memccpy](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `memmem` searches for needle occurrences in memory buffers and `memccpy` copies buffers until a character match, following FreeBSD and POSIX specifications; behavior is unverified on console. Neither is exported by `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), so a payload process cannot call them there.
- [strtof and wcstof](../../../core/libs/prx/libc/src/Strings.cpp) (libc) round to the nearest float, where the console's `_Stof` in `libSceLibcInternal.sprx` (0x734a0) does not always: about a third of decimal inputs within a relative 2^-51 of a point halfway between two floats, and some hexadecimal inputs with more than 24 significant bits, come out one unit apart in the last place (`1e-40` is `0x1.16c3p-133` there and `0x1.16c2p-133` here). The console's own rounding is not reproduced
- [Short narrow character fields](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - on glibc-backed scan paths, `%2c` with one available input byte can return `EOF` after storing that byte, while the bundled modules in PPSA03766 and PPSA07923 count one assignment. This is a CPU-only module comparison, not a console run.
- [strtof and wcstof](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `ERANGE` follows the `libc.prx` bundled with PPSA08427 (J03547016) and PPSA03671 (J03848509), both run side by side with this libc on a Linux host: a decimal input whose result is a nonzero subnormal sets it only when inexact, a hexadecimal one only when it lies below the smallest subnormal and rounds up to it, and underflow to zero and overflow set it. Both builds also take a `NAN(...)` payload in `strtof` from the sequence's leading digits (`nan(0x123)` is `0x7fc00123`, `-nan(5)` is `0xffc00005`); the sign is kept here, the payload is not. `libSceLibcInternal`'s `_Stof` differs: no `ERANGE` for hexadecimal input, a positive `nan`, and after a minus sign a failed parse is `-0.0` for a lone sign and `+0.0` before an `inf`/`nan` spelling, the reverse of `libc.prx`. Decimal `strtod` sets `ERANGE` for a nonzero subnormal in J03547016 but not in J03848509 or `libSceLibcInternal`, whose result is kept.

- [operator new](../../../core/libs/prx/libc/src/StdioExtras.cpp) (libc) - the `nothrow` forms return null only for `std::bad_alloc`, where the `libc.prx` bundled with PPSA21564 (build `J03093718`) catches every exception there, so a new handler that throws anything else ends the process instead of returning null. The aligned forms throw `std::bad_alloc` for an alignment of zero or one that is not a power of two, where that binary calls `abort`.
Expand Down
Loading