From 6f778e467d2ce40e65176e28f5ed661ffdb6f268 Mon Sep 17 00:00:00 2001 From: StoneDC <19905433+StoneDC@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:38:49 +0800 Subject: [PATCH] test(libc): compare terminated strings at protected boundaries --- core/libs/prx/libc/CMakeLists.txt | 10 +++ core/libs/tests/GuestStrcmpBoundary.cpp | 99 +++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 core/libs/tests/GuestStrcmpBoundary.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 176bc416d1e..f2e581666b0 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -1295,6 +1295,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_strcmp_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrcmpBoundary.cpp) + target_include_directories(guest_strcmp_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_strcmp_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_strcmp_boundary_tests) + add_test(NAME guest_strcmp_boundary COMMAND guest_strcmp_boundary_tests) + set_tests_properties(guest_strcmp_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_strcmp_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_heap_report_for_game_tests ${LIBS_INCLUDE_DIR}/tests/GuestHeapReportForGame.cpp) target_include_directories(guest_heap_report_for_game_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_heap_report_for_game_tests PRIVATE libc) diff --git a/core/libs/tests/GuestStrcmpBoundary.cpp b/core/libs/tests/GuestStrcmpBoundary.cpp new file mode 100644 index 00000000000..876031921ae --- /dev/null +++ b/core/libs/tests/GuestStrcmpBoundary.cpp @@ -0,0 +1,99 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestStrcmpBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +static int Sign(int value) { + return (value > 0) - (value < 0); +} + +int main() { + Buffer first, second; + for (std::size_t count = 0; count <= 128; ++count) { + auto* left = first.At(count + 1); + auto* right = second.At(count + 1); + for (std::size_t index = 0; index < count; ++index) left[index] = static_cast(1 + (index * 73 + count) % 255); + left[count] = 0; + const std::vector saved(left, left + count + 1); + for (std::size_t position = 0; position <= count; ++position) { + for (unsigned char value : {0, 1, 127, 128, 255}) { + if (position == count && value != 0) continue; + std::copy(saved.begin(), saved.end(), right); + right[position] = value; + int expected = 0; + for (std::size_t index = 0; index <= count; ++index) { + if (left[index] != right[index]) { expected = static_cast(left[index]) - right[index]; break; } + if (left[index] == 0) break; + } + const std::vector other(right, right + count + 1); + Require(Sign(strcmp_nid_postfix(reinterpret_cast(left), reinterpret_cast(right))) == Sign(expected)); + Require(std::equal(saved.begin(), saved.end(), left)); + Require(std::equal(other.begin(), other.end(), right)); + } + } + } +}