Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -685,13 +685,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 @@ -116,6 +116,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 @@ -240,6 +260,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 @@ -134,13 +134,6 @@ static void CheckBoundsCheckedFunctions() {
char nonOverlap[16] = {};
std::strcpy(nonOverlap + 8, "cd");
Require(strcpy_s_nid_postfix(nonOverlap, SIZE_MAX >> 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 @@ -73,6 +73,7 @@
- [strerror, strerror_r and perror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - match the system `libSceLibcInternal` of PS5 firmware 13.60 and the `libc.prx` that CUSA04865 and CUSA12435 ship, loaded on a PS4 Pro (firmware 10.71): 0 gives "No error", 1 to 94 the FreeBSD texts and any other number "error_code=0x%08x"; `perror` prints the same texts; errno is left alone. `strerror_r` follows the `libc.prx` that PPSA31872 ships, read statically: a buffer under 23 bytes returns `ERANGE` (34) without writing, otherwise it `strncpy`s length - 1 bytes, terminates the last byte and returns 0, so a long text is cut. Firmware 13.60 agreed at 0, 1, 5, 16 and 256 bytes; 17 to 255 bytes and the cut were not run on a console. 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 `RSIZE_MAX` (`SIZE_MAX >> 1` here) calls the registered guest-ABI constraint handler with `EINVAL` and leaves the destination untouched, following [C11 K.3.7.4.2](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf); what the console does then, its `RSIZE_MAX` and its default handler are not measured. Otherwise they match the system `libSceLibcInternal` of PS5 firmware 13.60: the `strerror` texts, 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
- [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`.
- [snprintf_s and vsnprintf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - follow C11 K.3.5.3.5 and K.3.5.3.12 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`, as `snwprintf_s` does: a null buffer or format, `size` of 0 or above `RSIZE_MAX`, any `%n` and a null `%s`, `%ls` or `%S` argument return a negative value and set `buffer[0]` to zero when the buffer and size allow it; otherwise the output is truncated to the buffer and the full length is returned. A violation only returns: the constraint handler is not invoked, and the console's error value is unverified. Not confirmed on a PS5 title
Expand Down
Loading