fix(libc): reject overflowing large heap block sizes
This commit is contained in:
parent
cea1ef13c3
commit
35a491bad4
|
|
@ -281,6 +281,20 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
add_test_executable(guest_heap_rounding_tests EXCLUDE_FROM_ALL ${LIBS_INCLUDE_DIR}/tests/GuestHeapRounding.cpp)
|
||||
target_include_directories(guest_heap_rounding_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_heap_rounding_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_heap_rounding_tests)
|
||||
set_target_properties(guest_heap_rounding_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(guest_heap_rounding_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
|
||||
add_test(NAME guest_heap_rounding_overflow COMMAND guest_heap_rounding_tests)
|
||||
set_tests_properties(guest_heap_rounding_overflow PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_locale_tests EXCLUDE_FROM_ALL ${LIBS_INCLUDE_DIR}/tests/GuestLocale.cpp)
|
||||
target_include_directories(guest_locale_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
set_target_properties(guest_locale_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ public:
|
|||
}
|
||||
|
||||
void* Allocate(std::size_t total, std::size_t& blockBytes) {
|
||||
if (total > MaximumSmallBytes &&
|
||||
total > std::numeric_limits<std::size_t>::max() - (LargeGranuleBytes - 1))
|
||||
throw std::length_error("guest heap allocation overflow");
|
||||
if (total > MaximumSmallBytes) {
|
||||
blockBytes = alignUp(total, LargeGranuleBytes);
|
||||
if (void* cached = _large.Take(blockBytes)) return cached;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <windows.h>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
void* APS5_VABI malloc_nid_postfix(std::size_t);
|
||||
void APS5_VABI free_nid_postfix(void*);
|
||||
void* APS5_VABI realloc_nid_postfix(void*, std::size_t);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t HeaderBytes = 2 * sizeof(void*);
|
||||
constexpr std::size_t MaximumSmallBlockBytes = 64u * 1024u;
|
||||
|
||||
void registerDefaultHeap() {
|
||||
std::array<void*, 10> api{};
|
||||
ApplicationHeapRegister_nid_no_patch(api.data());
|
||||
}
|
||||
|
||||
bool checkThreshold(std::size_t bytes, unsigned char value) {
|
||||
auto* pointer = static_cast<unsigned char*>(malloc_nid_postfix(bytes));
|
||||
if (pointer == nullptr || reinterpret_cast<std::uintptr_t>(pointer) % 16 != 0) return false;
|
||||
pointer[0] = value;
|
||||
pointer[bytes - 1] = static_cast<unsigned char>(value + 1);
|
||||
const bool preserved = pointer[0] == value && pointer[bytes - 1] == static_cast<unsigned char>(value + 1);
|
||||
free_nid_postfix(pointer);
|
||||
return preserved;
|
||||
}
|
||||
|
||||
DWORD runChild(const wchar_t* mode) {
|
||||
std::array<wchar_t, 32768> executable{};
|
||||
const DWORD length = GetModuleFileNameW(nullptr, executable.data(), static_cast<DWORD>(executable.size()));
|
||||
if (length == 0 || length >= executable.size()) return 0xffffffffu;
|
||||
std::wstring command = L"\"" + std::wstring(executable.data(), length) + L"\" " + mode;
|
||||
std::vector<wchar_t> commandLine(command.begin(), command.end());
|
||||
commandLine.push_back(L'\0');
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
startup.dwFlags = STARTF_USESTDHANDLES;
|
||||
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
PROCESS_INFORMATION process{};
|
||||
if (!CreateProcessW(executable.data(), commandLine.data(), nullptr, nullptr, TRUE, CREATE_NO_WINDOW,
|
||||
nullptr, nullptr, &startup, &process)) return 0xffffffffu;
|
||||
const DWORD wait = WaitForSingleObject(process.hProcess, 30000);
|
||||
if (wait != WAIT_OBJECT_0) {
|
||||
const DWORD exitCode = wait == WAIT_TIMEOUT ? 0xfffffffeu : 0xffffffffu;
|
||||
TerminateProcess(process.hProcess, exitCode);
|
||||
WaitForSingleObject(process.hProcess, INFINITE);
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
return exitCode;
|
||||
}
|
||||
DWORD exitCode = 0xffffffffu;
|
||||
if (!GetExitCodeProcess(process.hProcess, &exitCode)) exitCode = 0xffffffffu;
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
int runMallocOverflow() {
|
||||
registerDefaultHeap();
|
||||
try {
|
||||
auto* pointer = malloc_nid_postfix(std::numeric_limits<std::size_t>::max() - HeaderBytes);
|
||||
free_nid_postfix(pointer);
|
||||
return 1;
|
||||
} catch (const std::length_error& error) {
|
||||
std::fprintf(stderr, "malloc overflow rejected: %s\n", error.what());
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "malloc failed with an unexpected exception: %s\n", error.what());
|
||||
return 2;
|
||||
} catch (...) {
|
||||
std::fputs("malloc failed with a non-standard exception\n", stderr);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
int runReallocOverflow() {
|
||||
registerDefaultHeap();
|
||||
constexpr std::size_t bytes = 16;
|
||||
auto* original = static_cast<unsigned char*>(malloc_nid_postfix(bytes));
|
||||
std::memset(original, 0x5a, bytes);
|
||||
bool rejectedWithExpectedError = false;
|
||||
try {
|
||||
auto* result = realloc_nid_postfix(original, std::numeric_limits<std::size_t>::max() - HeaderBytes);
|
||||
if (result != nullptr) {
|
||||
free_nid_postfix(result);
|
||||
return 4;
|
||||
}
|
||||
} catch (const std::length_error& error) {
|
||||
rejectedWithExpectedError = true;
|
||||
std::fprintf(stderr, "realloc overflow rejected: %s\n", error.what());
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "realloc failed with an unexpected exception: %s\n", error.what());
|
||||
} catch (...) {
|
||||
std::fputs("realloc failed with a non-standard exception\n", stderr);
|
||||
}
|
||||
bool originalPreserved = true;
|
||||
for (std::size_t index = 0; index < bytes; ++index) {
|
||||
if (original[index] != 0x5a) originalPreserved = false;
|
||||
}
|
||||
auto* replacement = static_cast<unsigned char*>(nullptr);
|
||||
bool followUpAllocationThrew = false;
|
||||
try {
|
||||
replacement = static_cast<unsigned char*>(malloc_nid_postfix(bytes));
|
||||
} catch (const std::exception& error) {
|
||||
followUpAllocationThrew = true;
|
||||
std::fprintf(stderr, "follow-up allocation failed with an exception: %s\n", error.what());
|
||||
} catch (...) {
|
||||
followUpAllocationThrew = true;
|
||||
std::fputs("follow-up allocation failed with a non-standard exception\n", stderr);
|
||||
}
|
||||
unsigned result = 0;
|
||||
if (!rejectedWithExpectedError) result |= 1;
|
||||
if (!originalPreserved) result |= 2;
|
||||
if (replacement == nullptr) result |= 4;
|
||||
if (replacement == original) result |= 8;
|
||||
if (followUpAllocationThrew) result |= 16;
|
||||
if (result != 0) {
|
||||
std::fprintf(stderr, "realloc checks failed: rejected=%d preserved=%d replacement=%d distinct=%d followUpThrew=%d\n",
|
||||
rejectedWithExpectedError, originalPreserved, replacement != nullptr, replacement != original, followUpAllocationThrew);
|
||||
}
|
||||
if (result == 0) {
|
||||
free_nid_postfix(replacement);
|
||||
free_nid_postfix(original);
|
||||
}
|
||||
return static_cast<int>(result);
|
||||
}
|
||||
|
||||
int runGuardedCases() {
|
||||
bool success = true;
|
||||
for (const auto& [mode, label] : std::array<std::pair<const wchar_t*, const char*>, 2>{
|
||||
std::pair{L"overflow-malloc", "overflow malloc"},
|
||||
std::pair{L"overflow-realloc", "overflow realloc"}}) {
|
||||
const DWORD exitCode = runChild(mode);
|
||||
if (exitCode != 0) {
|
||||
std::fprintf(stderr, "%s child exited with 0x%08lx\n", label, static_cast<unsigned long>(exitCode));
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc == 2) SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
||||
if (argc == 2 && std::strcmp(argv[1], "overflow-malloc") == 0) return runMallocOverflow();
|
||||
if (argc == 2 && std::strcmp(argv[1], "overflow-realloc") == 0) return runReallocOverflow();
|
||||
if (argc != 1) return 10;
|
||||
registerDefaultHeap();
|
||||
if (!checkThreshold(MaximumSmallBlockBytes - HeaderBytes, 0x31) ||
|
||||
!checkThreshold(MaximumSmallBlockBytes - HeaderBytes + 1, 0x72)) return 11;
|
||||
return runGuardedCases();
|
||||
}
|
||||
Loading…
Reference in New Issue