Merge pull request #4455 from SibteProf/fix/libc-secure-copy-size-limits

fix(libc): reject oversized secure copy destinations
This commit is contained in:
Dean 2026-10-13 07:26:35 +13:00 committed by GitHub
commit b9f194a890
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 0 deletions

View File

@ -311,7 +311,9 @@ int APS5_VABI strcat_s_nid_postfix(char* dest, size_t destsz, const char* src) {
int APS5_VABI memcpy_s_nid_postfix(void* dest, size_t destsz, const void* src, size_t count) {
constexpr int GuestEinval = 22;
constexpr int GuestErange = 34;
constexpr size_t RsizeMax = SIZE_MAX >> 1;
if (!dest) return GuestEinval;
if (destsz > RsizeMax) return GuestErange;
if (!src || count > destsz) {
std::memset(dest, 0, destsz);
return src ? GuestErange : GuestEinval;
@ -330,7 +332,9 @@ int APS5_VABI memcpy_s_nid_postfix(void* dest, size_t destsz, const void* src, s
int APS5_VABI memmove_s_nid_postfix(void* dest, size_t destsz, const void* src, size_t count) {
constexpr int GuestEinval = 22;
constexpr int GuestErange = 34;
constexpr size_t RsizeMax = SIZE_MAX >> 1;
if (!dest) return GuestEinval;
if (destsz > RsizeMax) return GuestErange;
if (!src || count > destsz) {
std::memset(dest, 0, destsz);
return src ? GuestErange : GuestEinval;

View File

@ -72,6 +72,40 @@ static bool CheckMemcpyOverlap() {
return correct;
}
static void CheckSecureCopySizeLimits() {
using Copy = int (APS5_VABI*)(void*, std::size_t, const void*, std::size_t);
constexpr std::size_t RsizeMax = SIZE_MAX >> 1;
const std::array<unsigned char, 8> original = {1, 2, 3, 4, 5, 6, 7, 8};
const unsigned char source[] = {9, 10, 11, 12};
for (Copy copy : {memcpy_s_nid_postfix, memmove_s_nid_postfix}) {
for (const auto size : {RsizeMax + 1, SIZE_MAX}) {
for (const auto count : {std::size_t{1}, std::size_t{0}, RsizeMax + 1, SIZE_MAX}) {
for (const void* input : {static_cast<const void*>(source), static_cast<const void*>(nullptr)}) {
auto bytes = original;
Require(copy(bytes.data() + 2, size, input, count) != 0);
Require(bytes == original);
}
}
}
for (const auto count : {RsizeMax + 1, SIZE_MAX}) {
auto bytes = original;
Require(copy(bytes.data() + 2, 4, source, count) != 0);
const std::array<unsigned char, 8> cleared = {1, 2, 0, 0, 0, 0, 7, 8};
Require(bytes == cleared);
}
auto bytes = original;
Require(copy(bytes.data() + 2, RsizeMax, source, 1) == 0);
const std::array<unsigned char, 8> copied = {1, 2, 9, 4, 5, 6, 7, 8};
Require(bytes == copied);
Require(copy(bytes.data() + 2, 4, source, 0) == 0 && bytes == copied);
Require(copy(nullptr, SIZE_MAX, source, 1) == 22);
bytes = original;
Require(copy(bytes.data() + 2, 4, nullptr, 1) == 22);
const std::array<unsigned char, 8> cleared = {1, 2, 0, 0, 0, 0, 7, 8};
Require(bytes == cleared);
}
}
static void CheckBoundsCheckedFunctions() {
char small[4] = "zz";
Require(strcpy_s_nid_postfix(small, sizeof(small), "abc") == 0 && std::strcmp(small, "abc") == 0);
@ -344,5 +378,6 @@ int main() {
Require(strcasestr_nid_postfix("", "a") == nullptr);
const char highBytes[] = {static_cast<char>(0xff), 'A', 0};
Require(strcasestr_nid_postfix(highBytes, "a") == highBytes + 1);
CheckSecureCopySizeLimits();
return CheckMemcpyOverlap() ? 0 : 1;
}

View File

@ -337,6 +337,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceKernelAddTimerEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - `usec` is taken as the period in microseconds; re-adding an existing timer keeps its pending expiration count and restarts the period from the time of the call, where FreeBSD 12+ `filt_timertouch` clears pending expirations on `EV_ADD`; `usec == 0` fires on every wait. None of this is confirmed on a PS5 title
- [sceKernelWaitEqueue](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - a level-triggered user event (`sceKernelAddUserEvent`) is reported at most once per call, as in FreeBSD `kqueue_scan` (`sys/kern/kern_event.c`), which re-queues a reported knote without `EV_CLEAR` or `EV_DISPATCH` behind the scan marker, and kevent(2), where several triggers do not produce several kevents. Not measured on a console. Events are scanned in list order and the list is not rotated, so when `num` is smaller than the number of ready level events the later ones can starve, where FreeBSD moves a reported level knote to the tail. Several completions of one AMPR or VideoOut event within one call are returned as separate entries, where FreeBSD would aggregate them; the Orbis behaviour is unconfirmed
- [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset)
- [memcpy_s and memmove_s](../../core/libs/prx/libc/src/Strings.cpp) (libc) use the C11 K.3.7.1.1 and K.3.7.1.2 size constraints, with RSIZE_MAX taken as SIZE_MAX >> 1 as in memset_s. An oversized destination size returns ERANGE (34) without touching the destination; the console's RSIZE_MAX and exact error value are unverified. Constraint violations return an error without invoking the registered constraint handler, as before.
- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf`
- [vsnprintf_s and vsscanf_s](../../core/libs/prx/libc/src/Formatting.cpp) (libc) - `vsnprintf_s` forwards to `vsnprintf` semantics: the C11 K.3.7.1.5 runtime constraints (null buffer, `size == 0`, `%n`, null `%s`) are not checked, so `size == 0` returns the would-be length instead of a negative value, and no constraint handler is invoked. `vsscanf_s` returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Neither is confirmed on a PS5 title
- [libSceUlt ulthreads](../../core/libs/prx/libSceUlt/Export.cpp) - `_sceUltUlthreadRuntimeOptParamInitialize`, `_sceUltUlthreadRuntimeCreate` and `_sceUltUlthreadCreate` are assumed to take the same arguments as the existing exports without the underscore, including the trailing build version. `sceUltUlthreadTryJoin` is assumed to return `ULT_ERROR_BUSY` while the ulthread runs, and `sceUltUlthreadRuntimeDestroy` to return `ULT_ERROR_BUSY` while an ulthread created on the runtime is not joined. Neither error has been confirmed on PS5 hardware