From 3b0328da625267f950844c56c6b1e393723733d5 Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:40:35 +0100 Subject: [PATCH] core: never create executable host memory for guest pages --- include/rex/memory/utils.h | 13 +++---------- include/rex/system/flags.h | 1 - src/core/memory.cpp | 8 -------- src/core/memory_posix.cpp | 12 ++++++------ src/core/memory_win.cpp | 23 +++++++++-------------- 5 files changed, 18 insertions(+), 39 deletions(-) diff --git a/include/rex/memory/utils.h b/include/rex/memory/utils.h index 8ccb52c..1c9fc65 100644 --- a/include/rex/memory/utils.h +++ b/include/rex/memory/utils.h @@ -63,6 +63,9 @@ size_t page_size(); // This is likely 64KiB. size_t allocation_granularity(); +// Statically recompiled guest code executes from the host image, never from +// guest pages, so the execute variants only describe guest-side intent: the +// platform allocators serve them without the host execute bit (W^X). enum class PageAccess { kNoAccess = 0, kReadOnly = 1 << 0, @@ -82,16 +85,6 @@ enum class DeallocationType { kDecommit = 1 << 1, }; -// Whether the host allows the pages to be allocated or mapped with -// PageAccess::kExecuteReadWrite - if not, separate mappings backed by the same -// memory-mapped file must be used to write to executable pages. -bool IsWritableExecutableMemorySupported(); - -// Whether PageAccess::kExecuteReadWrite is a supported and preferred way of -// writing executable memory, useful for simulating how Xenia would work without -// writable executable memory on a system with it. -bool IsWritableExecutableMemoryPreferred(); - // Allocates a block of memory at the given page-aligned base address. // Fails if the memory is not available. // Specify nullptr for base_address to leave it up to the system. diff --git a/include/rex/system/flags.h b/include/rex/system/flags.h index b9e2f58..c85fd8e 100644 --- a/include/rex/system/flags.h +++ b/include/rex/system/flags.h @@ -25,7 +25,6 @@ REXCVAR_DECLARE(bool, kernel_debug_monitor); REXCVAR_DECLARE(bool, kernel_cert_monitor); REXCVAR_DECLARE(bool, ignore_thread_priorities); REXCVAR_DECLARE(bool, ignore_thread_affinities); -REXCVAR_DECLARE(bool, writable_executable_memory); REXCVAR_DECLARE(bool, protect_zero); REXCVAR_DECLARE(bool, protect_on_release); REXCVAR_DECLARE(bool, scribble_heap); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 316eb00..2ba4526 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -13,10 +13,6 @@ #include #include -REXCVAR_DEFINE_BOOL(writable_executable_memory, true, "Memory", - "Allow executable memory to be writable") - .lifecycle(rex::cvar::Lifecycle::kRequiresRestart); - #if REX_ARCH_ARM64 #include #endif @@ -26,10 +22,6 @@ REXCVAR_DEFINE_BOOL(writable_executable_memory, true, "Memory", namespace rex { namespace memory { -bool IsWritableExecutableMemoryPreferred() { - return REXCVAR_GET(writable_executable_memory); -} - // TODO(benvanik): fancy AVX versions. // https://github.com/gnuradio/volk/blob/master/kernels/volk/volk_16u_byteswap.h // https://github.com/gnuradio/volk/blob/master/kernels/volk/volk_32u_byteswap.h diff --git a/src/core/memory_posix.cpp b/src/core/memory_posix.cpp index df9fba3..34a51a9 100644 --- a/src/core/memory_posix.cpp +++ b/src/core/memory_posix.cpp @@ -118,19 +118,19 @@ uint32_t ToPosixProtectFlags(PageAccess access) { case PageAccess::kReadWrite: return PROT_READ | PROT_WRITE; case PageAccess::kExecuteReadOnly: - return PROT_READ | PROT_EXEC; + // Statically recompiled guest code executes from the host image, never + // from guest pages, so execute requests are served without PROT_EXEC. + // This keeps the process W^X: no path through this layer can create + // executable memory. + return PROT_READ; case PageAccess::kExecuteReadWrite: - return PROT_READ | PROT_WRITE | PROT_EXEC; + return PROT_READ | PROT_WRITE; default: assert_unhandled_case(access); return PROT_NONE; } } -bool IsWritableExecutableMemorySupported() { - return true; -} - // TODO(tomc): this needs to go somewhere else. we should utilize the platform namespace more. #if REX_PLATFORM_LINUX namespace { diff --git a/src/core/memory_win.cpp b/src/core/memory_win.cpp index 9a668e9..206bd5e 100644 --- a/src/core/memory_win.cpp +++ b/src/core/memory_win.cpp @@ -50,9 +50,13 @@ DWORD ToWin32ProtectFlags(PageAccess access) { case PageAccess::kReadWrite: return PAGE_READWRITE; case PageAccess::kExecuteReadOnly: - return PAGE_EXECUTE_READ; + // Statically recompiled guest code executes from the host image, never + // from guest pages, so execute requests are served without the execute + // bit. This keeps the process W^X: no path through this layer can + // create executable memory. + return PAGE_READONLY; case PageAccess::kExecuteReadWrite: - return PAGE_EXECUTE_READWRITE; + return PAGE_READWRITE; default: assert_unhandled_case(access); return PAGE_NOACCESS; @@ -81,16 +85,6 @@ PageAccess ToXeniaProtectFlags(DWORD access) { } } -bool IsWritableExecutableMemorySupported() { -#ifdef REX_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS - return true; -#else - // To test FromApp functions on desktop, undefine - // REX_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS and link to WindowsApp.lib. - return false; -#endif -} - void* AllocFixed(void* base_address, size_t length, AllocationType allocation_type, PageAccess access) { DWORD alloc_type = 0; @@ -206,10 +200,11 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, P file_access = FILE_MAP_ALL_ACCESS; break; case PageAccess::kExecuteReadOnly: - file_access = FILE_MAP_READ | FILE_MAP_EXECUTE; + // See ToWin32ProtectFlags: guest memory is never host-executable. + file_access = FILE_MAP_READ; break; case PageAccess::kExecuteReadWrite: - file_access = FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE; + file_access = FILE_MAP_ALL_ACCESS; break; case PageAccess::kNoAccess: default: