From 7327d5a3cd8f5bb31483f9076715f0fc68734469 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 12 Nov 2025 13:24:11 +0100 Subject: [PATCH 1/2] Use Memory Protection Keys to protect JIT buffer --- configure.ac | 1 + ext/opcache/jit/zend_jit.c | 97 +++++++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/configure.ac b/configure.ac index 77fc8c89cdf40..b413af0f09940 100644 --- a/configure.ac +++ b/configure.ac @@ -575,6 +575,7 @@ AC_CHECK_FUNCS(m4_normalize([ mmap nice nl_langinfo + pkey_mprotect poll pthread_jit_write_protect_np putenv diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 3ffb669e84742..352bf79a2bc43 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -33,6 +33,13 @@ #ifdef HAVE_JIT +#if defined(HAVE_PKEY_MPROTECT) && defined(PKEY_DISABLE_WRITE) +# define ZEND_JIT_USE_PKEYS +# ifndef PKEY_DISABLE_EXECUTE +# define PKEY_DISABLE_EXECUTE 0 +# endif +#endif + #include "Optimizer/zend_func_info.h" #include "Optimizer/zend_ssa.h" #include "Optimizer/zend_inference.h" @@ -88,6 +95,10 @@ static void **dasm_ptr = NULL; static size_t dasm_size = 0; +#ifdef ZEND_JIT_USE_PKEYS +static int pkey = 0; /* Memory Protection Key */ +#endif + static zend_long jit_bisect_pos = 0; static zend_vm_opcode_handler_t zend_jit_runtime_jit_handler = NULL; @@ -3519,6 +3530,21 @@ void zend_jit_unprotect(void) { #ifdef HAVE_MPROTECT if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { +# ifdef ZEND_JIT_USE_PKEYS + if (pkey) { +# ifdef ZTS + int restrictions = 0; +# else + int restrictions = PKEY_DISABLE_EXECUTE; +# endif + if (pkey_set(pkey, restrictions) != 0) { + fprintf(stderr, "pkey_set() failed [%d] %s\n", errno, strerror(errno)); + } else { + return; + } + } +# endif + int opts = PROT_READ | PROT_WRITE; #ifdef ZTS #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP @@ -3554,6 +3580,16 @@ void zend_jit_protect(void) { #ifdef HAVE_MPROTECT if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { +# ifdef ZEND_JIT_USE_PKEYS + if (pkey) { + if (pkey_set(pkey, PKEY_DISABLE_WRITE) != 0) { + fprintf(stderr, "pkey_set() failed [%d] %s\n", errno, strerror(errno)); + } else { + return; + } + } +# endif + #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(1); @@ -3774,34 +3810,45 @@ int zend_jit_check_support(void) return SUCCESS; } -void zend_jit_startup(void *buf, size_t size, bool reattached) +static void zend_jit_startup_dasm_prot(void) { - zend_jit_halt_op = zend_get_halt_op(); - zend_jit_profile_counter_rid = zend_get_op_array_extension_handle(ACCELERATOR_PRODUCT_NAME); - -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP - zend_write_protect = pthread_jit_write_protect_supported_np(); -#endif - - dasm_buf = buf; - dasm_size = size; - dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); - #ifdef HAVE_MPROTECT -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifdef ZEND_JIT_USE_PKEYS + pkey = pkey_alloc(0, PKEY_DISABLE_WRITE); + if (pkey < 0) { + pkey = 0; + } +# endif +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(1); } -#endif +# endif + if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } - } else { - if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { - fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); + return; + } + +# if ZEND_JIT_USE_PKEYS + if (pkey) { + if (pkey_mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC, pkey) != 0) { + fprintf(stderr, "pkey_mprotect() failed [%d] %s\n", errno, strerror(errno)); + pkey = 0; + } else { + /* Fallback to mprotect(PROT_READ | PROT_EXEC) */ + return; } } + +# endif + + if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { + fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); + } + #elif defined(_WIN32) if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { DWORD old; @@ -3823,6 +3870,22 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) } } #endif +} + +void zend_jit_startup(void *buf, size_t size, bool reattached) +{ + zend_jit_halt_op = zend_get_halt_op(); + zend_jit_profile_counter_rid = zend_get_op_array_extension_handle(ACCELERATOR_PRODUCT_NAME); + +#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP + zend_write_protect = pthread_jit_write_protect_supported_np(); +#endif + + dasm_buf = buf; + dasm_size = size; + dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); + + zend_jit_startup_dasm_prot(); if (!reattached) { zend_jit_unprotect(); From 5577a9de0f8c397d43f49fc71d3d140f5eeed9e5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 12 Nov 2025 14:02:53 +0100 Subject: [PATCH 2/2] Use Memory Protection Keys for opcache.protect_memory --- ext/opcache/jit/zend_jit.c | 141 ++++++++++++++++---------------- ext/opcache/zend_shared_alloc.c | 50 ++++++++++- 2 files changed, 121 insertions(+), 70 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 352bf79a2bc43..96edcd14aacaa 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -30,6 +30,9 @@ #include "Zend/zend_observer.h" #include "zend_smart_str.h" #include "jit/zend_jit.h" +#if __has_include() +# include +#endif #ifdef HAVE_JIT @@ -115,6 +118,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_runtime_jit(ZEND_O static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_runtime_jit(ZEND_OPCODE_HANDLER_ARGS); #endif +static void zend_jit_protect_init(void); + static int zend_jit_trace_op_len(const zend_op *opline); static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op *opline); static uint32_t _zend_jit_trace_get_exit_point(const zend_op *to_opline, uint32_t flags ZEND_FILE_LINE_DC); @@ -3526,6 +3531,62 @@ int zend_jit_script(zend_script *script) return FAILURE; } +static void zend_jit_protect_init(void) +{ +#ifdef HAVE_MPROTECT +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP + if (zend_write_protect) { + pthread_jit_write_protect_np(1); + } +# endif + + if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { + if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { + fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); + } + return; + } + +# ifdef ZEND_JIT_USE_PKEYS + pkey = pkey_alloc(0, PKEY_DISABLE_WRITE); + if (pkey < 0) { + zend_accel_error(ACCEL_LOG_DEBUG, "zend_jit_protect_init: pkey_alloc() failed [%d] %s", errno, strerror(errno)); + pkey = 0; + } else if (pkey_mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC, pkey) != 0) { + zend_accel_error(ACCEL_LOG_DEBUG, "zend_jit_protect_init: pkey_mprotect() failed [%d] %s", errno, strerror(errno)); + pkey = 0; + } else { + return; + } +# endif + + if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { + fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); + } + +#elif defined(_WIN32) + if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { + DWORD old; + + if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READWRITE, &old)) { + DWORD err = GetLastError(); + char *msg = php_win32_error_to_msg(err); + fprintf(stderr, "VirtualProtect() failed [%lu] %s\n", err, msg); + php_win32_error_msg_free(msg); + } + } else { + DWORD old; + + if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READ, &old)) { + DWORD err = GetLastError(); + char *msg = php_win32_error_to_msg(err); + fprintf(stderr, "VirtualProtect() failed [%lu] %s\n", err, msg); + php_win32_error_msg_free(msg); + } + } +#endif +} + void zend_jit_unprotect(void) { #ifdef HAVE_MPROTECT @@ -3538,10 +3599,9 @@ void zend_jit_unprotect(void) int restrictions = PKEY_DISABLE_EXECUTE; # endif if (pkey_set(pkey, restrictions) != 0) { - fprintf(stderr, "pkey_set() failed [%d] %s\n", errno, strerror(errno)); - } else { - return; + ZEND_UNREACHABLE(); } + return; } # endif @@ -3583,10 +3643,9 @@ void zend_jit_protect(void) # ifdef ZEND_JIT_USE_PKEYS if (pkey) { if (pkey_set(pkey, PKEY_DISABLE_WRITE) != 0) { - fprintf(stderr, "pkey_set() failed [%d] %s\n", errno, strerror(errno)); - } else { - return; + ZEND_UNREACHABLE(); } + return; } # endif @@ -3810,68 +3869,6 @@ int zend_jit_check_support(void) return SUCCESS; } -static void zend_jit_startup_dasm_prot(void) -{ -#ifdef HAVE_MPROTECT -# ifdef ZEND_JIT_USE_PKEYS - pkey = pkey_alloc(0, PKEY_DISABLE_WRITE); - if (pkey < 0) { - pkey = 0; - } -# endif -# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP - if (zend_write_protect) { - pthread_jit_write_protect_np(1); - } -# endif - - if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { - if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { - fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); - } - return; - } - -# if ZEND_JIT_USE_PKEYS - if (pkey) { - if (pkey_mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC, pkey) != 0) { - fprintf(stderr, "pkey_mprotect() failed [%d] %s\n", errno, strerror(errno)); - pkey = 0; - } else { - /* Fallback to mprotect(PROT_READ | PROT_EXEC) */ - return; - } - } - -# endif - - if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { - fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); - } - -#elif defined(_WIN32) - if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { - DWORD old; - - if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READWRITE, &old)) { - DWORD err = GetLastError(); - char *msg = php_win32_error_to_msg(err); - fprintf(stderr, "VirtualProtect() failed [%lu] %s\n", err, msg); - php_win32_error_msg_free(msg); - } - } else { - DWORD old; - - if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READ, &old)) { - DWORD err = GetLastError(); - char *msg = php_win32_error_to_msg(err); - fprintf(stderr, "VirtualProtect() failed [%lu] %s\n", err, msg); - php_win32_error_msg_free(msg); - } - } -#endif -} - void zend_jit_startup(void *buf, size_t size, bool reattached) { zend_jit_halt_op = zend_get_halt_op(); @@ -3885,7 +3882,7 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) dasm_size = size; dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); - zend_jit_startup_dasm_prot(); + zend_jit_protect_init(); if (!reattached) { zend_jit_unprotect(); @@ -3943,6 +3940,12 @@ void zend_jit_shutdown(void) dasm_buf = NULL; dasm_end = NULL; dasm_size = 0; + +#ifdef ZEND_JIT_USE_PKEYS + if (pkey) { + pkey_free(pkey); + } +#endif } static void zend_jit_reset_counters(void) diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index 80ef36b8749d9..2f9356591d61e 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -44,6 +44,10 @@ # include "sys/mman.h" #endif +#if defined(HAVE_PKEY_MPROTECT) && defined(PKEY_DISABLE_WRITE) +# define ZEND_ACCEL_USE_PKEYS +#endif + #define SEM_FILENAME_PREFIX ".ZendSem." #define S_H(s) g_shared_alloc_handler->s @@ -79,6 +83,12 @@ static const zend_shared_memory_handler_entry handler_table[] = { { NULL, NULL} }; +#ifdef ZEND_ACCEL_USE_PKEYS +static int pkey = 0; /* Memory Protection Key */ +#endif + +static void zend_accel_shared_protect_init(void); + #ifndef ZEND_WIN32 void zend_shared_alloc_create_lock(char *lockfile_path) { @@ -260,6 +270,8 @@ int zend_shared_alloc_startup(size_t requested_size, size_t reserved_size) ZSMMG(shared_segments)[i]->end = ZSMMG(shared_segments)[i]->size; } + zend_accel_shared_protect_init(); + shared_segments_array_size = ZSMMG(shared_segments_count) * S_H(segment_type_size)(); /* move shared_segments and shared_free to shared memory */ @@ -342,6 +354,13 @@ void zend_shared_alloc_shutdown(void) # ifdef ZTS tsrm_mutex_free(zts_lock); # endif + +# ifdef ZEND_ACCEL_USE_PKEYS + if (pkey) { + pkey_free(pkey); + } +# endif + #endif } @@ -628,9 +647,38 @@ const char *zend_accel_get_shared_model(void) return g_shared_model; } +static void zend_accel_shared_protect_init(void) +{ +#ifdef ZEND_ACCEL_USE_PKEYS + pkey = pkey_alloc(0, 0); + if (pkey < 0) { + zend_accel_error(ACCEL_LOG_DEBUG, "zend_accel_shared_protect_init: pkey_alloc() failed [%d] %s", errno, strerror(errno)); + pkey = 0; + return; + } + + for (int i = 0; i < ZSMMG(shared_segments_count); i++) { + if (pkey_mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->end, PROT_READ | PROT_WRITE, pkey) != 0) { + zend_accel_error(ACCEL_LOG_DEBUG, "zend_accel_shared_protect_init: pkey_mprotect() failed [%d] %s", errno, strerror(errno)); + pkey = 0; + break; + } + } +#endif +} + void zend_accel_shared_protect(bool protected) { -#ifdef HAVE_MPROTECT +#ifdef ZEND_ACCEL_USE_PKEYS + if (pkey) { + if (pkey_set(pkey, protected ? PKEY_DISABLE_WRITE : 0) != 0) { + ZEND_UNREACHABLE(); + } + return; + } +#endif + +#if defined(HAVE_MPROTECT) int i; if (!smm_shared_globals) {