From e5a8ce5ecbab87f7f19deaf36cd8b13005398e07 Mon Sep 17 00:00:00 2001 From: Asaf Niv Date: Tue, 30 Dec 2025 22:46:23 +0200 Subject: [PATCH] unittests: add tests for stack mapping --- unittests/FEXLinuxTests/tests/CMakeLists.txt | 6 +++ .../tests/smc/smc-exec-stack.cpp | 38 ++++++++++++++ .../tests/smc/smc-missing-gnustack.cpp | 49 +++++++++++++++++++ .../tests/smc/smc-unexec-stack.cpp | 30 ++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 unittests/FEXLinuxTests/tests/smc/smc-exec-stack.cpp create mode 100644 unittests/FEXLinuxTests/tests/smc/smc-missing-gnustack.cpp create mode 100644 unittests/FEXLinuxTests/tests/smc/smc-unexec-stack.cpp diff --git a/unittests/FEXLinuxTests/tests/CMakeLists.txt b/unittests/FEXLinuxTests/tests/CMakeLists.txt index fb390226b..95c425d67 100644 --- a/unittests/FEXLinuxTests/tests/CMakeLists.txt +++ b/unittests/FEXLinuxTests/tests/CMakeLists.txt @@ -46,3 +46,9 @@ target_link_libraries(smc-shared-2.${BITNESS} PRIVATE rt pthread) target_link_libraries(thunk_testlib.${BITNESS} PRIVATE ${CMAKE_DL_LIBS}) target_link_libraries(timer-sigev-thread.${BITNESS} PRIVATE rt pthread) + +target_link_libraries(smc-unexec-stack.${BITNESS} PRIVATE -Wl,-z,noexecstack) + +target_link_options(smc-exec-stack.${BITNESS} PRIVATE -Wl,-z,execstack) + +target_link_options(smc-missing-gnustack.${BITNESS} PRIVATE -fuse-ld=lld -Wl,-z,nognustack) diff --git a/unittests/FEXLinuxTests/tests/smc/smc-exec-stack.cpp b/unittests/FEXLinuxTests/tests/smc/smc-exec-stack.cpp new file mode 100644 index 000000000..38f4277c0 --- /dev/null +++ b/unittests/FEXLinuxTests/tests/smc/smc-exec-stack.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +bool got_signal = false; + +static void sigsegv_handler(int signal, siginfo_t *siginfo, void* context) { + REQUIRE(siginfo->si_code == SEGV_ACCERR); + got_signal = true; + size_t page_size = sysconf(_SC_PAGESIZE); + void *fault_addr = (void *)((uintptr_t)(siginfo->si_addr) & ~(page_size - 1)); + REQUIRE(mprotect(fault_addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0); +} + +TEST_CASE("smc-exec-stack: PT_GNU_STACK == RWX") { + // Register signal handler + struct sigaction act {}; + act.sa_sigaction = sigsegv_handler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &act, nullptr); + + // Try executing from stack + uint8_t stack_code = 0xC3; // ret + ((void (*)())(&stack_code))(); + REQUIRE(got_signal == false); + + // Executing from other memory should fail + size_t page_size = sysconf(_SC_PAGESIZE); + uint8_t *mem_code = static_cast(mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + REQUIRE(mem_code != nullptr); + *mem_code = 0xC3; // ret + ((void (*)())(mem_code))(); + REQUIRE(got_signal == true); + + munmap(mem_code, page_size); +} diff --git a/unittests/FEXLinuxTests/tests/smc/smc-missing-gnustack.cpp b/unittests/FEXLinuxTests/tests/smc/smc-missing-gnustack.cpp new file mode 100644 index 000000000..efc46a08d --- /dev/null +++ b/unittests/FEXLinuxTests/tests/smc/smc-missing-gnustack.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +bool got_signal = false; + +static void sigsegv_handler(int signal, siginfo_t *siginfo, void* context) { + REQUIRE(siginfo->si_code == SEGV_ACCERR); + got_signal = true; + size_t page_size = sysconf(_SC_PAGESIZE); + void *fault_addr = (void *)((uintptr_t)(siginfo->si_addr) & ~(page_size - 1)); + REQUIRE(mprotect(fault_addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0); +} + +TEST_CASE("smc-missing-gnustack: PT_GNU_STACK missing") { + // Register signal handler + struct sigaction act {}; + act.sa_sigaction = sigsegv_handler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &act, nullptr); + + // Try executing from stack + uint8_t stack_code = 0xC3; // ret + ((void (*)())(&stack_code))(); + +#ifdef __i386__ + REQUIRE(got_signal == false); +#else + REQUIRE(got_signal == true); + got_signal = false; +#endif + + // Executing from other memory should fail on 64 bit but work on 32 bit + size_t page_size = sysconf(_SC_PAGESIZE); + uint8_t *mem_code = static_cast(mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + REQUIRE(mem_code != nullptr); + *mem_code = 0xC3; // ret + ((void (*)())(mem_code))(); + +#ifdef __i386__ + REQUIRE(got_signal == false); +#else + REQUIRE(got_signal == true); +#endif + + munmap(mem_code, page_size); +} diff --git a/unittests/FEXLinuxTests/tests/smc/smc-unexec-stack.cpp b/unittests/FEXLinuxTests/tests/smc/smc-unexec-stack.cpp new file mode 100644 index 000000000..cbe773b2d --- /dev/null +++ b/unittests/FEXLinuxTests/tests/smc/smc-unexec-stack.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +bool got_signal = false; +void *tramp = nullptr; + +static void sigsegv_handler(int signal, siginfo_t *siginfo, void* context) { + REQUIRE(siginfo->si_code == SEGV_ACCERR); + got_signal = true; + size_t page_size = sysconf(_SC_PAGESIZE); + void *fault_addr = (void *)((uintptr_t)(siginfo->si_addr) & ~(page_size - 1)); + REQUIRE(mprotect(fault_addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0); +} + +TEST_CASE("smc-unexec-stack: PT_GNU_STACK == RW") { + // Register signal handler + struct sigaction act {}; + act.sa_sigaction = sigsegv_handler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &act, nullptr); + + // Try executing from stack + uint8_t stack_code = 0xC3; // ret instruction + tramp = (void *)((uintptr_t)&stack_code & ~(sysconf(_SC_PAGESIZE) - 1)); + ((void (*)())(&stack_code))(); + + REQUIRE(got_signal == true); +}