Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ out/
.vs/
*.pyc
.cache
.idea/
19 changes: 13 additions & 6 deletions Source/Tools/FEXInterpreter/ELFCodeLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class ELFCodeLoader final : public FEX::CodeLoader {

bool ElfValid {false};
bool ExecutableStack {false};
bool ExecuteAll {false};
bool HasStackHeader {false};
uintptr_t MainElfBase {};
uintptr_t InterpeterElfBase {};
uintptr_t MainElfEntrypoint {};
Expand Down Expand Up @@ -391,6 +393,7 @@ class ELFCodeLoader final : public FEX::CodeLoader {
bool MapMemory(FEX::HLE::SyscallMmapInterface* const Handler, FEXCore::Core::InternalThreadState* Thread) {
for (const auto& Header : MainElf.phdrs) {
if (Header.p_type == PT_GNU_STACK) {
HasStackHeader = true;
if (Header.p_flags & PF_X) {
ExecutableStack = true;
}
Expand All @@ -400,11 +403,15 @@ class ELFCodeLoader final : public FEX::CodeLoader {
// Both for the main and the interpreter elf
}

if (!HasStackHeader && !Is64BitMode()) {
// 32-bit behavior
ExecutableStack = true;
ExecuteAll = true;
}

// Set the process personality here
// This needs some more investigation
// READ_IMPLIES_EXEC might be default for 32-bit elfs
// Also, what about ADDR_LIMIT_3GB & co ?
if (-1 == personality(PER_LINUX | (ExecutableStack ? READ_IMPLIES_EXEC : 0))) {
if (-1 == personality(PER_LINUX | (ExecuteAll ? READ_IMPLIES_EXEC : 0))) {
LogMan::Msg::EFmt("Setting personality failed");
return false;
}
Expand Down Expand Up @@ -459,9 +466,9 @@ class ELFCodeLoader final : public FEX::CodeLoader {
}

// Allocate with permissions the 8MB of regular stack size.
StackPointer = reinterpret_cast<uintptr_t>(
Handler->GuestMmap(Thread, reinterpret_cast<void*>(reinterpret_cast<uint64_t>(StackPointerBase) + FULL_STACK_SIZE - StackSize()),
StackSize(), PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0));
StackPointer = reinterpret_cast<uintptr_t>(Handler->GuestMmap(
Thread, reinterpret_cast<void*>(reinterpret_cast<uint64_t>(StackPointerBase) + FULL_STACK_SIZE - StackSize()), StackSize(),
PROT_READ | PROT_WRITE | (ExecutableStack ? PROT_EXEC : 0), MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0));

if (FEX::HLE::HasSyscallError(StackPointer)) {
LogMan::Msg::EFmt("Allocating stack failed");
Expand Down
7 changes: 7 additions & 0 deletions unittests/FEXLinuxTests/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,10 @@ 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)

# Must use lld because it has the nognustack option
target_link_options(smc-missing-gnustack.${BITNESS} PRIVATE -fuse-ld=lld -Wl,-z,nognustack)
49 changes: 49 additions & 0 deletions unittests/FEXLinuxTests/tests/smc/smc-exec-stack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <catch2/catch_test_macros.hpp>
#include <signal.h>
#include <ucontext.h>
#include <sys/mman.h>

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_page = (void*)((uintptr_t)(siginfo->si_addr) & ~(page_size - 1));
REQUIRE(mprotect(fault_page, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0);
}

void register_signal_handler() {
struct sigaction act {};
act.sa_sigaction = sigsegv_handler;
act.sa_flags = SA_SIGINFO;
REQUIRE(sigaction(SIGSEGV, &act, nullptr) == 0);
}

TEST_CASE("smc-exec-stack: PT_GNU_STACK == RWX") {
register_signal_handler();

// Try executing from stack
char stack[16384];
auto stack_code = (char*)(((uintptr_t)stack + 4095) & ~4095);
*stack_code = 0xC3; // ret
((void (*)())(stack_code))();
CHECK(got_signal == false);
got_signal = false;
}

TEST_CASE("smc-exec-stack: mmap other memory") {
register_signal_handler();

// Executing from other memory should fail
size_t page_size = sysconf(_SC_PAGESIZE);
uint8_t* mem_code = static_cast<uint8_t*>(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))();

CHECK(got_signal == true);
got_signal = false;

REQUIRE(munmap(mem_code, page_size) == 0);
}
57 changes: 57 additions & 0 deletions unittests/FEXLinuxTests/tests/smc/smc-missing-gnustack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <catch2/catch_test_macros.hpp>
#include <signal.h>
#include <ucontext.h>
#include <sys/mman.h>

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_page = (void*)((uintptr_t)(siginfo->si_addr) & ~(page_size - 1));
REQUIRE(mprotect(fault_page, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0);
}

void register_signal_handler() {
struct sigaction act {};
act.sa_sigaction = sigsegv_handler;
act.sa_flags = SA_SIGINFO;
REQUIRE(sigaction(SIGSEGV, &act, nullptr) == 0);
}

TEST_CASE("smc-missing-gnustack: PT_GNU_STACK missing") {
register_signal_handler();

// Try executing from stack
char stack[16384];
auto stack_code = (char*)(((uintptr_t)stack + 4095) & ~4095);
*stack_code = 0xC3; // ret
((void (*)())(stack_code))();

#ifdef __i386__
CHECK(got_signal == false);
#else
CHECK(got_signal == true);
#endif
got_signal = false;
}

TEST_CASE("smc-missing-gnustack: mmap other memory") {
register_signal_handler();
// 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<uint8_t*>(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__
CHECK(got_signal == false);
#else
CHECK(got_signal == true);
#endif
got_signal = false;

REQUIRE(munmap(mem_code, page_size) == 0);
}
33 changes: 33 additions & 0 deletions unittests/FEXLinuxTests/tests/smc/smc-unexec-stack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <catch2/catch_test_macros.hpp>
#include <signal.h>
#include <ucontext.h>
#include <sys/mman.h>

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_page = (void*)((uintptr_t)(siginfo->si_addr) & ~(page_size - 1));
REQUIRE(mprotect(fault_page, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0);
}

void register_signal_handler() {
struct sigaction act {};
act.sa_sigaction = sigsegv_handler;
act.sa_flags = SA_SIGINFO;
REQUIRE(sigaction(SIGSEGV, &act, nullptr) == 0);
}

TEST_CASE("smc-unexec-stack: PT_GNU_STACK == RW") {
register_signal_handler();

// Try executing from stack
char stack[16384];
auto stack_code = (char*)(((uintptr_t)stack + 4095) & ~4095);
*stack_code = 0xC3; // ret instruction
((void (*)())(stack_code))();

CHECK(got_signal == true);
}
Loading