fix(libc): format guest wide strings in swprintf as 16-bit units

The padded, exact-fit and invalid-conversion swprintf checks come from #1512.

Co-Authored-By: Noface07 <yuvrajsoni7@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULF8vCbJFyzxVsW3SfiWC5
This commit is contained in:
turkishcuber 2026-10-08 13:57:37 +03:00 committed by Adrià Franch
parent f3999054bd
commit a3186c59ef
5 changed files with 34 additions and 116 deletions

View File

@ -1,99 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSWIDEFORMATTING_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSWIDEFORMATTING_HPP
#include "WindowsFormatting.hpp"
#include "General.hpp"
#include <cerrno>
#include <cwchar>
#include <deque>
namespace LibcDetail {
inline int FormatWideWindows(wchar_t* output, size_t capacity, const wchar_t* format, const void* source) {
if (!capacity) return -1;
if (!output || !format || !source) { errno = 22; return -1; }
static_assert(sizeof(wchar_t) == 2);
FormatArguments args(source);
std::wstring translated;
std::wstring result;
std::vector<std::uint64_t> slots;
std::deque<long double> extended;
auto append = [&slots](auto value) {
static_assert(sizeof(value) <= 8);
std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(value));
slots.push_back(bits);
};
while (*format) {
const wchar_t value = *format++;
if (value != L'%') { result += value; continue; }
if (*format == L'%') { result += *format++; continue; }
translated = L"%";
slots.clear();
extended.clear();
bool nullCharacter = false;
while (*format && std::wcschr(L"-+ #0", *format)) translated += *format++;
if (*format == L'*') { translated += *format++; append(args.Next<int>()); }
else while (*format >= L'0' && *format <= L'9') translated += *format++;
if (*format == L'.') {
translated += *format++;
if (*format == L'*') { translated += *format++; append(args.Next<int>()); }
else while (*format >= L'0' && *format <= L'9') translated += *format++;
}
std::wstring length;
if (*format && std::wcschr(L"hljztL", *format)) {
length += *format++;
if ((length == L"h" && *format == L'h') || (length == L"l" && *format == L'l')) length += *format++;
}
const wchar_t conversion = *format;
if (!conversion) { errno = 22; return -1; }
if (!std::wcschr(L"diouxXaAeEfFgGcspn", conversion)) {
NotImplemented_nid_no_patch("swprintf format conversion");
return -1;
}
++format;
if (std::wcschr(L"diouxXn", conversion) && (length == L"l" || length == L"j" || length == L"z" || length == L"t"))
translated += L"ll";
else if (conversion == L's' && length.empty()) translated += L"h";
else if (conversion == L'c' && length.empty()) translated += L"l";
else translated += length;
translated += conversion;
if (conversion == L'n') {
void* pointer = args.Next<void*>();
if (length == L"hh") *static_cast<signed char*>(pointer) = static_cast<signed char>(result.size());
else if (length == L"h") *static_cast<short*>(pointer) = static_cast<short>(result.size());
else if (length.empty()) *static_cast<int*>(pointer) = static_cast<int>(result.size());
else *static_cast<std::int64_t*>(pointer) = static_cast<std::int64_t>(result.size());
continue;
}
if (conversion == L's' || conversion == L'p') append(args.Next<void*>());
else if (conversion == L'c') {
const int character = args.Next<int>();
const int converted = length.empty() ? static_cast<int>(static_cast<unsigned char>(character)) : character;
nullCharacter = converted == 0;
append(nullCharacter ? 1 : converted);
}
else if (std::wcschr(L"aAeEfFgG", conversion)) {
if (length == L"L") {
extended.push_back(args.Next<long double>());
append(&extended.back());
} else append(args.Next<double>());
} else if (length.empty() || length == L"h" || length == L"hh") append(args.Next<unsigned int>());
else append(args.Next<std::uint64_t>());
if (result.size() >= capacity) return -1;
std::vector<wchar_t> part(capacity - result.size());
const int count = std::vswprintf(part.data(), part.size(), translated.c_str(), reinterpret_cast<char*>(slots.data()));
if (count < 0 || static_cast<size_t>(count) >= part.size()) return -1;
if (nullCharacter) {
for (int i = 0; i < count; ++i) if (part[i] == 1) part[i] = 0;
}
result.append(part.data(), static_cast<size_t>(count));
}
if (result.size() >= capacity || result.size() > INT_MAX) return -1;
std::memcpy(output, result.c_str(), (result.size() + 1) * sizeof(wchar_t));
return static_cast<int>(result.size());
}
}
#endif

View File

@ -19,7 +19,6 @@
#ifdef _WIN32
#include "prx/libc/include/WindowsScanning.hpp"
#include "prx/libc/include/WindowsWideFormatting.hpp"
#endif
namespace {
@ -157,21 +156,6 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n
extern "C" {
int APS5_VABI swprintf_nid_postfix(wchar_t* output, size_t capacity, const wchar_t* format, ...) {
#ifdef _WIN32
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
const int result = LibcDetail::FormatWideWindows(output, capacity, format, args);
__builtin_sysv_va_end(args);
#else
std::va_list args;
va_start(args, format);
const int result = std::vswprintf(output, capacity, format, args);
va_end(args);
#endif
return result;
}
int APS5_VABI vasprintf_nid_postfix(char** destination, const char* format, VaList* args) {
if (!destination) { errno = 22; return -1; }
*destination = nullptr;

View File

@ -214,6 +214,13 @@ int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const ch
}
}
int APS5_VABI swprintf_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...) {
APS5_VA_BEGIN(format);
const int result = vswprintf_nid_postfix(buffer, size, format, reinterpret_cast<VaList*>(args));
APS5_VA_END();
return result;
}
int APS5_VABI snwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...) {
constexpr std::size_t RsizeMax = SIZE_MAX >> 1;
int result = -1;

View File

@ -8,6 +8,7 @@
extern "C" int APS5_VABI vswprintf_nid_postfix(char16_t*, std::size_t, const char16_t*, VaList*);
extern "C" int APS5_VABI snwprintf_s_nid_postfix(char16_t*, std::size_t, const char16_t*, ...);
extern "C" int APS5_VABI swprintf_nid_postfix(char16_t*, std::size_t, const char16_t*, ...);
static int APS5_VABI Format(char16_t* buffer, std::size_t size, const char16_t* format, ...) {
#ifdef _WIN32
@ -111,9 +112,34 @@ static void CheckCount() {
Require(Format(buffer, 16, u"a%n", nullCount) < 0, "vswprintf null %n argument");
}
static void CheckSwprintf() {
char16_t buffer[8];
for (auto& unit : buffer) unit = u'x';
Require(swprintf_nid_postfix(buffer, 8, u"%d-%ls", 42, u"ab") == 5, "swprintf length");
Require(buffer == std::u16string(u"42-ab") && buffer[6] == u'x', "swprintf output");
Require(swprintf_nid_postfix(buffer, 8, u"中%ls%s", u"文", "\xc3\xa9") == 3, "swprintf non-ASCII length");
Require(buffer == std::u16string(u"中文é"), "swprintf non-ASCII output");
Require(swprintf_nid_postfix(buffer, 8, u"%s", "\xf0\x9f\x98\x80") == 2, "swprintf surrogate pair length");
Require(buffer == std::u16string(u"\U0001f600"), "swprintf surrogate pair output");
Require(swprintf_nid_postfix(buffer, 8, u"") == 0 && buffer[0] == 0, "swprintf empty format");
for (auto& unit : buffer) unit = u'x';
Require(swprintf_nid_postfix(buffer, 3, u"%ls", u"abcd") < 0, "swprintf truncation result");
Require(buffer == std::u16string(u"ab") && buffer[3] == u'x', "swprintf truncation output");
Require(swprintf_nid_postfix(buffer, 3, u"abc") < 0, "swprintf terminator does not fit");
buffer[0] = u'x';
Require(swprintf_nid_postfix(buffer, 0, u"a") < 0 && buffer[0] == u'x', "swprintf zero size");
Require(swprintf_nid_postfix(nullptr, 8, u"a") < 0, "swprintf null buffer");
Require(swprintf_nid_postfix(buffer, 5, u"%ls", u"abcd") == 4 && buffer == std::u16string(u"abcd"), "swprintf exact fit");
char16_t wide[16];
Require(swprintf_nid_postfix(wide, 16, u"%5ls|%-3c|%lc", u"wide", 'z', 0x20ac) == 11, "swprintf padded length");
Require(wide == std::u16string(u" wide|z |€"), "swprintf padded output");
Require(swprintf_nid_postfix(wide, 16, u"%q") < 0 && wide[0] == 0, "swprintf invalid conversion");
}
int main() {
CheckBounded();
CheckCount();
CheckSwprintf();
Check(u"%.2s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8");
Check(u"%.1s", "\xc3\xa9\xc3\xa8", u"\u00e9");
Check(u"%.0s", "\xc3\xa9", u"");

View File

@ -264,7 +264,7 @@ Throughout the project, every function at every stage either **does exactly what
- [libSceHttp2 asynchronous requests](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) never reach the network: `sceHttp2SendRequestAsync` accepts the request, queues a completion whose result is the library's network error and triggers the user event named by the kqueue option (equeue at offset 0, event id at 8, user data at 16, layout inferred from PPSA17221); `sceHttp2WaitAsync` returns that completion. The completion's `event_type` value is unknown and left 0. PPSA17221 completes its request object only after `sceHttp2SendRequestAsync` returns, and its handler for a synchronous failure calls `shared_from_this` before that, so a send must not fail synchronously. A kqueue option or user event that is missing, an extra option, and a wait with no completed operation throw
- [system](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) runs no command. It follows FreeBSD 11's `lib/libc/stdlib/system.c` for a sandbox without `/bin/sh`: a null command returns 1 unconditionally, as that version does, and any command returns the wait status of a child that could not exec the shell and called `_exit(127)` (`0x7f00`). Whether the console's application sandbox has no shell, and whether `vfork` succeeds there (FreeBSD returns -1 when it fails), is unverified
- [sceKernelWriteThrottlingStatus](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports no write throttling: the host SSD has no write budget, so the first qword of the 32-byte status is set to `UINT32_MAX` and the rest is zeroed. PPSA17221 reads only that qword: it multiplies it by 64 KiB and compares the product with byte thresholds up to 0x4AF00000 to pick one of five tiers. When the call fails the title assumes 0x4B000001 bytes, which selects the same top tier as `UINT32_MAX`. What the value measures on hardware (a remaining budget or a rate) and the meaning of the other three qwords are unknown
- [Wide-character conversions](../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - [`swprintf`](../../core/libs/prx/libc/src/Formatting.cpp) still passes guest strings to the host's `wchar_t` functions. The guest's `wchar_t` is 16 bits, so on Linux, where the host's is 32 bits, it misreads and miswrites guest strings. The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported
- [Wide-character conversions](../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported
- [SPIRV-Tools optimization](../../core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp) skips CCP and Simplification for modules with storage-only 8- or 16-bit types because the pinned folder produces illegal narrow constants. This also disables unrelated constant folding; the restricted full pipeline mirrors the pinned performance recipe.
- On macOS the [write watch](../../core/libs/prx/libc/src/GuestWriteWatch.cpp) write-protects watched pages and records the first write to each from a SIGBUS/SIGSEGV handler, because macOS has no counterpart to `userfaultfd` write-protect or `MEM_WRITE_WATCH`. The protection is real, so:
- a system call that writes into a protected page fails with `EFAULT` instead of faulting. `GuestArena::HostWrite` lifts the protection for the calls it brackets (the file reads); a host call that writes guest memory without it (sockets, `clock_gettime` and the like into a watched page) fails;