Skip to content

Fix OPcache memory protection race under ZTS - #23081

Merged
arnaud-lb merged 3 commits into
php:PHP-8.4from
realFlowControl:florian/fix-opcache-protect-memory-zts-race
Aug 14, 2026
Merged

Fix OPcache memory protection race under ZTS#23081
arnaud-lb merged 3 commits into
php:PHP-8.4from
realFlowControl:florian/fix-opcache-protect-memory-zts-race

Conversation

@realFlowControl

@realFlowControl realFlowControl commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

I found this after adding tracing-JIT jobs to the parallel CI, which so far only run function JIT. The new job reproducibly crashed while compiling a trace:

AddressSanitizer: SEGV caused by a WRITE
#0 zend_jit_trace_add_code ext/opcache/jit/zend_jit_trace.c:214
#1 zend_jit_finish ext/opcache/jit/zend_jit_ir.c:16295
#2 zend_jit_trace ext/opcache/jit/zend_jit_trace.c:7244
#3 zend_jit_compile_root_trace ext/opcache/jit/zend_jit_trace.c:7454
#4 zend_jit_trace_hot_root ext/opcache/jit/zend_jit_trace.c:8127

The crash occurs because opcache.protect_memory changes shared-memory permissions process-wide, while ZTS threads manage the protection scopes independently. run-tests.php sets opcache.protect_memory=1, which is why this became visible in CI, default is opcache.protect_memory=0 so most likely no one will actually see this crash in prod.

The race is:

  1. Worker A starts compiling a hot trace.
  2. It acquires zend_shared_alloc_lock().
  3. It calls SHM_UNPROTECT(), making OPcache shared memory writable.
  4. Worker B concurrently runs OPcache request activation, which is not guarded by zend_shared_alloc_lock().
  5. Worker B calls SHM_UNPROTECT() followed by SHM_PROTECT().
  6. Because memory protection is process-wide, Worker B makes the mapping read-only for all threads.
  7. Worker A is still compiling and writes JIT trace metadata into the mapping.
  8. PHP crashes with SIGSEGV or EXC_BAD_ACCESS.

This PR makes unprotected sections nested and process-aware under ZTS:

  • Each thread tracks its own unprotect depth.
  • A process-wide counter tracks threads with an active unprotected section.
  • A dedicated mutex serializes the counter and memory-protection transitions.
  • Shared memory returns to read-only only after the last thread leaves its outermost unprotected section.

The dedicated mutex is separate from zend_shared_alloc_lock() because some callers already hold that lock when calling SHM_UNPROTECT().

We do not see this in the php-src ci in ZTS runs, because those are not running multiple threads, but everything is still single threaded.

@realFlowControl
realFlowControl marked this pull request as ready for review August 6, 2026 12:35
@realFlowControl

Copy link
Copy Markdown
Contributor Author

Also see:
#17246 (comment)
#16727 (comment)

@realFlowControl

Copy link
Copy Markdown
Contributor Author

@arnaud-lb could you have a look at this PR?

@arnaud-lb arnaud-lb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks good to me, but it's unfortunate that we have to do this.

In master we could try to switch to a thread-specific memory protection mechanism. Since it's only for CI and debugging it would be fine if the mechanism is not universally available (the goal would be simplification, so no need for an mprotect fallback). We could use PKEYs on Linux and maybe pthread_jit_write_protect_np() on MacOS if that's not reserved for W^X use-cases. This would likely accelerate CI, too.

Comment thread ext/opcache/zend_shared_alloc.c Outdated
Comment thread ext/opcache/zend_shared_alloc.c Outdated
@realFlowControl

realFlowControl commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@arnaud-lb I fixed all the nested indentions in 1943fbb

@arnaud-lb
arnaud-lb merged commit 7249e7e into php:PHP-8.4 Aug 14, 2026
18 checks passed
arnaud-lb added a commit that referenced this pull request Aug 14, 2026
* PHP-8.5:
  [ci skip] NEWS
  Fix OPcache memory protection race under ZTS (#23081)
@arnaud-lb

Copy link
Copy Markdown
Member

Thank you!

pull Bot pushed a commit to Mattlk13/php-src that referenced this pull request Aug 14, 2026
* PHP-8.4:
  [ci skip] NEWS
  Fix OPcache memory protection race under ZTS (php#23081)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants