diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 575971f71..951173fe7 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -45,6 +45,15 @@ if(WIN32) endif() if(BUILD_TESTING) + add_test_executable(guest_scan_count_tests ${LIBS_INCLUDE_DIR}/tests/GuestScanCount.cpp) + target_include_directories(guest_scan_count_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_scan_count_tests PRIVATE libc) + configure_windows_unwind(guest_scan_count_tests) + add_test(NAME guest_scan_count COMMAND guest_scan_count_tests) + if(WIN32) + set_tests_properties(guest_scan_count PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_access_tests ${LIBS_INCLUDE_DIR}/tests/GuestAccess.cpp) target_include_directories(guest_access_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_access_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 0cc9b6e63..ffff79f04 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -76,7 +76,17 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n } ++cursor; if (conversion == 'n') { - if (!suppress) *static_cast(nextPointer()) = static_cast(input - buffer); + if (!suppress) { + void* target = nextPointer(); + const auto count = input - buffer; + if (length == "hh") *static_cast(target) = static_cast(count); + else if (length == "h") *static_cast(target) = static_cast(count); + else if (length.empty()) *static_cast(target) = static_cast(count); + else if (length == "l" || length == "ll" || length == "j" || length == "z" || length == "t") { + const std::int64_t value = count; + std::memcpy(target, &value, sizeof(value)); + } else throw std::invalid_argument("Invalid scan count length"); + } continue; } if (conversion != 'c' && conversion != '[') { diff --git a/core/libs/tests/GuestScanCount.cpp b/core/libs/tests/GuestScanCount.cpp new file mode 100644 index 000000000..f77e73b62 --- /dev/null +++ b/core/libs/tests/GuestScanCount.cpp @@ -0,0 +1,70 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sscanf_s_nid_postfix(const char*, const char*, ...); + +static int failures = 0; + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + ++failures; + } +} + +template static void CheckCount(const char* modifier, int count) { + struct alignas(8) Destination { + TValue value; + std::array guard; + } destination{static_cast(-1), {}}; + destination.guard.fill(0xa5); + const auto guard = destination.guard; + const std::string input(static_cast(count), 'x'); + const std::string format = input + "%" + modifier + "n"; + const int assigned = sscanf_s_nid_postfix(input.c_str(), format.c_str(), &destination.value); + if (destination.value != count || destination.guard != guard || assigned != 0) { + std::fprintf(stderr, "%%%sn after %d bytes: count=%lld, guard=%s, assignments=%d\n", + modifier, count, static_cast(destination.value), + destination.guard == guard ? "unchanged" : "overwritten", assigned); + ++failures; + } +} + +int main() { + for (const int count : {0, 3, 127}) { + CheckCount("hh", count); + CheckCount("h", count); + CheckCount("", count); + CheckCount("l", count); + CheckCount("ll", count); + CheckCount("j", count); + CheckCount("z", count); + CheckCount("t", count); + } + CheckCount("h", 257); + CheckCount("l", 257); + + int number = -1; + std::int64_t before = -1; + std::int64_t after = -1; + char word[4] = {}; + Require(sscanf_s_nid_postfix(" 12 abc!", "%ln%d %3[a-z]%jn!", &before, &number, + word, 4u, &after) == 2, "Count conversions must not increase assignments"); + Require(before == 0 && number == 12 && std::strcmp(word, "abc") == 0 && after == 7, + "Counts must include leading whitespace and earlier conversions"); + + after = -1; + Require(sscanf_s_nid_postfix("123x", "%*d%tn", &after) == 0 && after == 3, + "Count must include suppressed input without consuming a capacity argument"); + after = -1; + Require(sscanf_s_nid_postfix("x", "%d%lln", &number, &after) == 0 && after == -1, + "Matching failure must leave a later count untouched"); + Require(sscanf_s_nid_postfix("", "%d%lln", &number, &after) == EOF && after == -1, + "Input failure must leave a later count untouched"); + return failures == 0 ? 0 : 1; +}