Skip to content
Draft
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 configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ AC_CHECK_FUNCS(m4_normalize([
mmap
nice
nl_langinfo
pkey_mprotect
poll
pthread_jit_write_protect_np
putenv
Expand Down
138 changes: 102 additions & 36 deletions ext/opcache/jit/zend_jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,19 @@
#include "Zend/zend_observer.h"
#include "zend_smart_str.h"
#include "jit/zend_jit.h"
#if __has_include(<sys/mman.h>)
# include <sys/mman.h>
#endif

#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
Comment on lines +41 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we can't disable EXECUTE permission on x86_64. I didn't found a solution.

# endif
#endif

#include "Optimizer/zend_func_info.h"
#include "Optimizer/zend_ssa.h"
#include "Optimizer/zend_inference.h"
Expand Down Expand Up @@ -88,6 +98,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;
Expand All @@ -104,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);
Expand Down Expand Up @@ -3515,10 +3531,80 @@ 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
Comment on lines +3537 to +3541
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how pthread_jit_write_protect_np() works together with mprotect() and PKEYS.


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
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) {
Comment on lines +3596 to +3601
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need #ifdef ZTS? I assume this us for AArch64. Linux or MacOS?
May be we should prefer pthread_jit_write_protect_np()?

Copy link
Member Author

@arnaud-lb arnaud-lb Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the existing code, we avoid removing the PROT_EXEC in zend_jit_unprotect(), in ZTS, because other threads may be executing. I wanted to reproduce the same logic here: with restrictions = 0, we only remove PKEY_DISABLE_WRITE, thus allowing write without disallowing execute.

My understanding is that pthread_jit_write_protect_np() is MacOS only, while pkeys are Linux only.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the existing code, we avoid removing the PROT_EXEC in zend_jit_unprotect(), in ZTS, because other threads may be executing. I wanted to reproduce the same logic here: with restrictions = 0, we only remove PKEY_DISABLE_WRITE, thus allowing write without disallowing execute.

  1. I understood that PKEYs are thread specific (may be I'm wrong).
  2. PKEY_DISABLE_EXECUTE is zero on x86_64. So this should affect only Arrach64/Linux. I didn't test this on real hardware.

My understanding is that pthread_jit_write_protect_np() is MacOS only, while pkeys are Linux only.

I understood the same.

ZEND_UNREACHABLE();
}
return;
}
# endif

int opts = PROT_READ | PROT_WRITE;
#ifdef ZTS
#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
Expand Down Expand Up @@ -3554,6 +3640,15 @@ 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) {
ZEND_UNREACHABLE();
}
return;
}
# endif

#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
if (zend_write_protect) {
pthread_jit_write_protect_np(1);
Expand Down Expand Up @@ -3787,42 +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);

#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));
}
} else {
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
zend_jit_protect_init();

if (!reattached) {
zend_jit_unprotect();
Expand Down Expand Up @@ -3880,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)
Expand Down
50 changes: 49 additions & 1 deletion ext/opcache/zend_shared_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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) {
Expand Down
Loading