Skip to content
Open
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
14 changes: 8 additions & 6 deletions core/libs/prx/libc/src/Strings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,13 +622,15 @@ int APS5_VABI memmove_s_nid_postfix(void* dest, size_t destsz, const void* src,
}

int APS5_VABI memset_s_nid_postfix(void* dest, size_t destsz, int value, size_t count) {
constexpr int GuestEinval = 22;
constexpr size_t RsizeMax = SIZE_MAX >> 1;
if (!dest || destsz > RsizeMax || count > RsizeMax) return GuestEinval;
const auto length = count > destsz ? destsz : count;
constexpr int GuestErange = 34;
constexpr size_t RsizeMax = SIZE_MAX >> 3;
if (!dest || destsz > RsizeMax || count > destsz) {
SonyInvokeConstraintHandler("memset_s: bad destination argument", nullptr, GuestErange);
return GuestErange;
}
auto* bytes = static_cast<volatile unsigned char*>(dest);
for (size_t index = 0; index < length; ++index) bytes[index] = static_cast<unsigned char>(value);
return count > destsz ? GuestEinval : 0;
for (size_t index = 0; index < count; ++index) bytes[index] = static_cast<unsigned char>(value);
return 0;
}

char* APS5_VABI strnstr_nid_postfix(const char* haystack, const char* needle, size_t length) {
Expand Down
21 changes: 21 additions & 0 deletions core/libs/tests/GuestBoundsChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ void CheckWcstok() {
RequireViolation(before, "wcstok_s: bad argument", 22);
}

void CheckMemset() {
unsigned char bytes[8] = {1, 2, 3, 4, 5, 6, 7, 8};
Require(memset_s_nid_postfix(bytes, sizeof(bytes), 9, 4) == 0 && bytes[0] == 9 && bytes[3] == 9 && bytes[4] == 5, "memset_s did not fill count bytes");
Require(memset_s_nid_postfix(bytes, 0, 7, 0) == 0 && bytes[0] == 9, "memset_s rejected an empty fill");
Require(memset_s_nid_postfix(bytes, tooLarge - 1, 6, 1) == 0 && bytes[0] == 6 && bytes[1] == 9, "memset_s rejected the largest size");
struct Violation {
bool null;
std::size_t size;
std::size_t count;
};
constexpr Violation violations[] = {{false, 4, 8}, {false, 0, 1}, {true, 8, 4}, {true, 0, 0}, {false, tooLarge, 4}, {false, 8, tooLarge}, {false, SIZE_MAX, 0}};
for (const auto& violation : violations) {
unsigned char sentinel[8] = {1, 2, 3, 4, 5, 6, 7, 8};
const int before = constraintCalls;
Require(memset_s_nid_postfix(violation.null ? nullptr : sentinel, violation.size, 0, violation.count) == 34, "memset_s accepted a violation");
RequireViolation(before, "memset_s: bad destination argument", 34);
Require(sentinel[0] == 1 && sentinel[7] == 8, "memset_s wrote on a violation");
}
}

void CheckWideToMultibyte() {
int status = 7;
char bytes[4] = {'x', 'x', 'x', 'x'};
Expand Down Expand Up @@ -261,6 +281,7 @@ int main(int argc, char** argv) {

CheckBsearch();
CheckWcstok();
CheckMemset();
CheckWideToMultibyte();
CheckMultibyteToWide();
CheckGets(argv[1]);
Expand Down
7 changes: 0 additions & 7 deletions core/libs/tests/GuestStrings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,6 @@ static void CheckBoundsCheckedFunctions() {
char nonOverlap[16] = {};
std::strcpy(nonOverlap + 8, "cd");
Require(strcpy_s_nid_postfix(nonOverlap, (std::size_t{1} << 61) - 1, nonOverlap + 8) == 0 && std::strcmp(nonOverlap, "cd") == 0);
Require(memset_s_nid_postfix(bytes, sizeof(bytes), 9, 8) == 22 && bytes[0] == 9 && bytes[3] == 9);
char sentinel[4] = {1, 2, 3, 4};
Require(memset_s_nid_postfix(sentinel, sizeof(sentinel), 7, 2) == 0 && sentinel[1] == 7 && sentinel[2] == 3);
Require(memset_s_nid_postfix(sentinel, (SIZE_MAX >> 1) + 1, 0, 1) == 22 && sentinel[0] == 7);
Require(memset_s_nid_postfix(sentinel, sizeof(sentinel), 0, (SIZE_MAX >> 1) + 1) == 22 && sentinel[0] == 7 && sentinel[3] == 4);
Require(memset_s_nid_postfix(sentinel, SIZE_MAX >> 1, 5, 1) == 0 && sentinel[0] == 5 && sentinel[1] == 7);
Require(memset_s_nid_postfix(nullptr, sizeof(sentinel), 0, 1) == 22);
Require(strnstr_nid_postfix("haystack", "st", 4) == nullptr);
const char haystack[] = "haystack";
Require(strnstr_nid_postfix(haystack, "st", 6) == haystack + 3);
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 @@ -99,6 +99,7 @@
- [strerror, strerror_r and perror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - follow the `libc.prx` that PPSA05916, PPSA07923 and PPSA13934 ship, run natively over -2000 to 2000, `INT_MAX` and `INT_MIN`: 0 gives "No error", 1 to 94 the FreeBSD texts, 107, 108, 152, 1040, 1050, 1051, 1062 and 1074 their own texts, and any other number "error_code=0x%08x"; `perror` prints the same texts and errno is left alone. Firmware 13.60's `libSceLibcInternal` and the PS4 `libc.prx` of CUSA04865 and CUSA12435 agreed at the numbers measured there (0 to 96, 97, 100, 1000, -1, `INT_MAX`, `INT_MIN`); the eight extra names were not run on a console. `strerror_r` follows PPSA31872's `libc.prx`, read statically: under 23 bytes it returns `ERANGE` (34) without writing, otherwise it `strncpy`s length - 1 bytes, terminates the last byte and returns 0. Firmware 13.60 agreed at 0, 1, 5, 16 and 256 bytes. A null buffer of 23 bytes or more throws, as the console writes through it.
- [strerror_s and strerrorlen_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc) - a null destination, a capacity of 0 or one above `SIZE_MAX >> 3` calls the registered guest-ABI constraint handler with "strerror_s: bad argument", `EINVAL` and a null pointer and leaves the destination untouched, as the `libc.prx` PPSA07923 and PPSA05916 ship does; the system `libSceLibcInternal` is not measured for these. Otherwise they match the system `libSceLibcInternal` of PS5 firmware 13.60 for the numbers it was measured on (0 to 96, 97, 100, 1000, -1, `INT_MAX`, `INT_MIN`): the `strerror` texts (the eight extended names of `strerror` were taken from the titles' `libc.prx`, not run on firmware 13.60), a short buffer truncated with "..." when it holds more than 3 bytes and a return of 0 either way, and `errno` left alone
- [getw and putw](../../../core/libs/prx/libc/src/BinaryWordIo.cpp) (libc) follow FreeBSD 11.4 [getw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/getw.c) and [putw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/putw.c): one 32-bit little-endian word, `getw` returns -1 for EOF or an error, and `putw` returns 0 on success or -1 on error while holding the stream lock. They use the existing host-backed streams, so buffering and error timing follow the host CRT. Null streams throw and closed streams return -1 with `EBADF`, as existing byte I/O does. Console behavior is unverified. A payload process on a PS5 (firmware 13.60) gave the same results: two words written little-endian, -1 with end of file after them and for a 2-byte file, and -1 with the error flag for `putw` on a read-only stream
- [memset_s](../../../core/libs/prx/libc/src/Strings.cpp) (libc) fills only when `dest` is not null, `destsz` is at most `SIZE_MAX >> 3` and `count` is at most `destsz`, as the `libc.prx` PPSA07923 and PPSA05916 ship does. Any other call writes nothing, returns `ERANGE` and calls the constraint handler with "memset_s: bad destination argument", `ERANGE` and a null pointer, where C11 K.3.7.4.1 fills `min(count, destsz)` bytes of a valid destination; the system `libSceLibcInternal` is not measured for this
- [vsnwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - `va_list` counterpart of `snwprintf_s` (C11 K.3.9.1.3): truncates to the buffer and returns the full length, with the same constraints and argument checks
- [%s in the wide printf family](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) decodes the narrow argument as the `libc.prx` that titles ship does: UTF-8 of at most 3 bytes, with overlong forms and encoded surrogates accepted and a decoded NUL ending the string. A byte that cannot begin a character, a 4-byte lead or an incomplete character makes `swprintf` and `vswprintf` return -1 with `errno` `EILSEQ` (86), keeping the text written before that conversion; a precision stops decoding after that many characters, but always decodes the first, and a field that decodes to no character is not padded to its width. A `%c` takes the low byte of its argument and fails when that byte is 0x80 or above, setting `EILSEQ` only for 0x80-0xBF and 0xF0-0xFF and not for `EOF`, as `btowc` does. Checked by running PPSA07923's copy side by side, not measured on a console. `wprintf` and `vfwprintf` also fail with `EILSEQ` without writing, and the secure forms fail as before; their console output on such a string is unverified. Streamed and secure `%c` error output is unverified.
- [strndup and strdup](../../../core/libs/prx/libc/src/AllocatingStrings.cpp) (libc) return null with `errno` set to `EINVAL` (22) when `source` is null, preventing a host segfault on invalid guest input. POSIX.1-2008 and FreeBSD 11.4 libc leave `strndup(NULL)` and `strdup(NULL)` undefined (faulting). The `libc.prx` that titles ship has no null check either (read from disassembly of PPSA13934's copy, not measured on a console): `strdup` (0x6b440) calls `strlen` and `strndup` (0x6b650) calls `strnlen` on the source before allocating, so both fault on a null source there, except `strndup(NULL, 0)`, whose `strnlen` reads nothing and which returns an empty copy; here it returns null with `EINVAL`.
Expand Down
Loading