Skip to content

Narrow the Windows VEH to undefined-function calls only#379

Merged
realFlowControl merged 2 commits into
developfrom
florian/narrow-windows-veh
Jun 16, 2026
Merged

Narrow the Windows VEH to undefined-function calls only#379
realFlowControl merged 2 commits into
developfrom
florian/narrow-windows-veh

Conversation

@realFlowControl

@realFlowControl realFlowControl commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #381 (base = florian/fix-future-monitor-uaf-race). Retarget to develop once #381 lands.

Follow-up to #378, which narrowed the POSIX SIGSEGV handler. This applies the same policy to the Windows vectored exception handler (VEH).

What changed

The VEH introduced in #355 caught every access violation on a parallel worker and converted it into a catchable IllegalInstruction via an unconditional zend_bailout(). Now it only does that for the one case it was built for: a worker faulting on a ZEND_INIT_FCALL to a function missing from its function table (no bootstrap file). Any other access violation returns EXCEPTION_CONTINUE_SEARCH, i.e. it is forwarded to the previously installed handler and crashes cleanly if nothing handles it — the Windows equivalent of chaining to the previous SIGSEGV handler on POSIX.

Detection is shared between both handlers via php_parallel_scheduler_recover_missing_function().

Why this is now safe

Narrowing the VEH previously unmasked a latent worker-thread teardown access violation in tests/functional/007.phpt. That was a real use-after-free race on future->monitor, fixed in #381 — so with this stacked on top, 007 passes for the right reason instead of being masked by the catch-all.

Testing

  • Linux suite green (148/148).
  • Undefined-function path still covered: init_fcall_fix_001/002/003.

When a task threw (normal exception) or crashed (IllegalInstruction), the
worker signalled the future twice: first PHP_PARALLEL_ERROR (or READY|ERROR),
then an unconditional trailing PHP_PARALLEL_READY at the end of
php_parallel_scheduler_run().

Future::value() waits on READY|FAILURE|ERROR, so the first signal already
releases the consumer. The consumer can then tear the future down (freeing
future->monitor) before the worker runs the trailing
php_parallel_monitor_set(future->monitor, PHP_PARALLEL_READY), which then
dereferences a dangling monitor inside pthread_cond_signal -> access
violation / use-after-free.

This was observed as a hard crash on Windows (0xC0000005) in
tests/functional/007.phpt, masked until now by the catch-all VEH; on Linux
the freed memory happened to stay readable.

Fix: the error paths now signal READY|ERROR exactly once and skip the
trailing READY (via a 'ready' flag). The success and kill paths are
unchanged - they have no early wake and still rely on the trailing READY.
Setting READY in the error path also ensures the future destructor (which
waits for READY) never blocks when value() is not called.
Apply the same policy the POSIX SIGSEGV handler already has (#378) to the
Windows vectored exception handler: only convert an access violation into a
catchable IllegalInstruction error when a parallel worker faulted on a call
to a function missing from its function table. Every other access violation
is forwarded to the previously installed handler via EXCEPTION_CONTINUE_SEARCH
(and crashes cleanly if nothing handles it) instead of being masked by an
unconditional zend_bailout().

The missing-function detection is shared between both handlers via
php_parallel_scheduler_recover_missing_function().
@realFlowControl
realFlowControl force-pushed the florian/narrow-windows-veh branch from 4e7ac20 to e14d8b4 Compare June 16, 2026 11:01
@realFlowControl
realFlowControl changed the base branch from develop to florian/fix-future-monitor-uaf-race June 16, 2026 11:01
Base automatically changed from florian/fix-future-monitor-uaf-race to develop June 16, 2026 11:41
@realFlowControl
realFlowControl marked this pull request as ready for review June 16, 2026 11:41
@realFlowControl
realFlowControl merged commit b654483 into develop Jun 16, 2026
86 of 87 checks passed
@realFlowControl
realFlowControl deleted the florian/narrow-windows-veh branch June 16, 2026 11:41
@realFlowControl realFlowControl added this to the v1.2.15 milestone Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant