Skip to content

Observer segfault when calling user function in internal function via trampoline #16233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
YuanchengJiang opened this issue Oct 5, 2024 · 5 comments

Comments

@YuanchengJiang
Copy link

Description

The following code:

<?php
class LocalSoapClient extends SoapClient {
function __doRequest($request, $location, $action, $version, $one_way = 0): string {
}
}
$client = new LocalSoapClient(__DIR__."/classmap003.wsdl",
array('classmap'=>array('A'=>'A','B'=>'B')));
print_r($client->f());

Resulted in this output:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==1778123==ERROR: AddressSanitizer: SEGV on unknown address 0x00131fff8007 (pc 0x557390644f96 bp 0x7ffef91295f0 sp 0x7ffef91294a0 T0)
==1778123==The signal is caused by a READ memory access.
    #0 0x557390644f96 in call_end_observers /php-src/Zend/zend_observer.c:300:80
    #1 0x557390645f4b in zend_observer_fcall_end_all /php-src/Zend/zend_observer.c:325:3
    #2 0x55738f086722 in php_request_shutdown /php-src/main/main.c:1902:3
    #3 0x55739079f2b0 in do_cli /php-src/sapi/cli/php_cli.c:1106:3
    #4 0x557390796524 in main /php-src/sapi/cli/php_cli.c:1310:18
    #5 0x7f69ba023d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #6 0x7f69ba023e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #7 0x55738c603904 in _start (/php-src/sapi/cli/php+0x2403904) (BuildId: d9851980940e1525eb2e61068d0828e86059842d)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /php-src/Zend/zend_observer.c:300:80 in call_end_observers
==1778123==ABORTING

PHP Version

PHP 8.4.0-dev

Operating System

ubuntu 22.04

@YuanchengJiang
Copy link
Author

some reports require certain dependency files (e.g., classmap003.wsdl), which can be found in the repo.

@devnexen
Copy link
Member

devnexen commented Oct 5, 2024

sure but could not reproduce on my mac, get this instead php(90314,0x1f2723240) malloc: nano zone abandoned due to inability to reserve vm space., I may have more luck on linux.

@nielsdos
Copy link
Member

nielsdos commented Oct 5, 2024

The reason you couldn't reproduce this is because you need to use zend_test and use -d zend_test.observer.enabled=1 -d zend_test.observer.show_output=1 -d zend_test.observer.observe_all=1.

@YuanchengJiang
Copy link
Author

indeed. sorry i forgot to add the configs

@nielsdos
Copy link
Member

nielsdos commented Oct 5, 2024

This bug is unrelated to soap, this is an engine bug that happens when an observer calls a user function while reusing the stack frame in ZEND_CALL_TRAMPOLINE. I'm on it.

@nielsdos nielsdos changed the title Segmentation fault in Zend/zend_observer.c:300 Observer segfault when calling user function in internal function via trampoline Oct 5, 2024
nielsdos added a commit to nielsdos/php-src that referenced this issue Oct 5, 2024
…ternal function via trampoline

In the test, I have an internal `__call` function for `_ZendTestMagicCallForward` that calls the global function with name `$name` via `call_user_function`.
Note that observer writes the pointer to the previously observed frame in the last temporary of the new call frame (`*prev_observed_frame`).

The following happens:
First, we call `$test->callee`, this will be handled via a trampoline with T=2 for the two arguments. The call frame is allocated at this point. This call frame is not observed because it has `ZEND_ACC_CALL_VIA_TRAMPOLINE` set. Next we use ZEND_CALL_TRAMPOLINE to call the trampoline, this reuses the stack frame allocated earlier with T=2, but this time it is observed. The pointer to the previous frame is written outside of the call frame because `T` is too small (should be 3). We are now in the internal function `_ZendTestMagicCallForward::__call` where we call the global function `callee`. This will push a new call frame which will overlap `*prev_observed_frame`. This value gets overwritten by `zend_init_func_execute_data` when `EX(opline)` is set because `*prev_observed_frame` overlaps with `EX(opline)`. From now on, `*prev_observed_frame` is corrupted. When `zend_observer_fcall_end` is called this will result in reading wrong value `*prev_observed_frame` into `current_observed_frame`. This causes issues in `zend_observer_fcall_end_all` leading to the segfault we observe.

Despite function with `ZEND_ACC_CALL_VIA_TRAMPOLINE` not being observed, the reuse of call frames makes problems when `T` is not large enough.
To fix this, we make sure to add 1 to `T` if `ZEND_OBSERVER_ENABLED` is true.
nielsdos added a commit to nielsdos/php-src that referenced this issue Oct 5, 2024
…ternal function via trampoline

In the test, I have an internal `__call` function for `_ZendTestMagicCallForward` that calls the global function with name `$name` via `call_user_function`.
Note that observer writes the pointer to the previously observed frame in the last temporary of the new call frame (`*prev_observed_frame`).

The following happens:
First, we call `$test->callee`, this will be handled via a trampoline with T=2 for the two arguments. The call frame is allocated at this point. This call frame is not observed because it has `ZEND_ACC_CALL_VIA_TRAMPOLINE` set. Next we use `ZEND_CALL_TRAMPOLINE` to call the trampoline, this reuses the stack frame allocated earlier with T=2, but this time it is observed. The pointer to the previous frame is written outside of the call frame because `T` is too small (should be 3). We are now in the internal function `_ZendTestMagicCallForward::__call` where we call the global function `callee`. This will push a new call frame which will overlap `*prev_observed_frame`. This value gets overwritten by `zend_init_func_execute_data` when `EX(opline)` is set because `*prev_observed_frame` overlaps with `EX(opline)`. From now on, `*prev_observed_frame` is corrupted. When `zend_observer_fcall_end` is called this will result in reading wrong value `*prev_observed_frame` into `current_observed_frame`. This causes issues in `zend_observer_fcall_end_all` leading to the segfault we observe.

Despite function with `ZEND_ACC_CALL_VIA_TRAMPOLINE` not being observed, the reuse of call frames makes problems when `T` is not large enough.
To fix this, we make sure to add 1 to `T` if `ZEND_OBSERVER_ENABLED` is true.
nielsdos added a commit that referenced this issue Oct 7, 2024
* PHP-8.2:
  Fixed GH-16233: Observer segfault when calling user function in internal function via trampoline
nielsdos added a commit that referenced this issue Oct 7, 2024
* PHP-8.3:
  Fixed GH-16233: Observer segfault when calling user function in internal function via trampoline
nielsdos added a commit that referenced this issue Oct 7, 2024
* PHP-8.4:
  Fixed GH-16233: Observer segfault when calling user function in internal function via trampoline
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants