Skip to content

Fix GH-16414: zend_test.observer.observe_function_names may segfault #16438

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
wants to merge 3 commits into from

Conversation

cmb69
Copy link
Member

@cmb69 cmb69 commented Oct 14, 2024

// Call during runtime, but only if you have used zend_observer_fcall_register.
// You must not have more than one begin and one end handler active at the same time. Remove the old one first, if there is an existing one.

How would poor ext/zend_test know whether zend_observer_fcall_register() had been called? Can that function call be observed? ;)

Kidding aside, I don't think this patch is the proper fix (since the issue is related to ext/zend_test, and not really to observers). Instead, it should be possible to check whether an fcall has been registered; unfortunately, there appears to be no API for that, and zend_observers_fcall_list is static.

@bwoebi
Copy link
Member

bwoebi commented Oct 14, 2024

@cmb69 No, the proper fix would be simply checking for if (ZT_G(observer_enabled)) in the ini-update handler in zend_test. It says if you (i.e. the zend_test extension) have called zend_observer_fcall_register(), then you may use that API.

@cmb69 cmb69 changed the title Fix GH-16414: Segmentation fault in Zend/zend_observer.c Fix GH-16414: zend_test.observer.observe_function_names may segfault Oct 14, 2024
Copy link
Member

@bwoebi bwoebi left a comment

Choose a reason for hiding this comment

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

Yup :-)

@cmb69
Copy link
Member Author

cmb69 commented Oct 14, 2024

The new test segfaults on aarch64. :/

@nielsdos
Copy link
Member

nielsdos commented Oct 14, 2024

@cmb69 If I had to make a guess, it's because it's a ZTS build: there the modules globals get allocated probably after the ini MH gets executed. EDIT: nvm, does not make sense in this context

@bwoebi
Copy link
Member

bwoebi commented Oct 14, 2024

@cmb69 the aarch runner runs with the observer already enabled. I suppose it's missing handling for that case when the function wasn't called yet...

@cmb69
Copy link
Member Author

cmb69 commented Oct 15, 2024

I suppose it's missing handling for that case when the function wasn't called yet...

It seems we're back at square one. :)

@cmb69 cmb69 changed the base branch from PHP-8.2 to PHP-8.4 October 16, 2024 18:10
cmb69 added 3 commits October 16, 2024 20:31
Unless `zend_test.observer.enabled` is on, we must not add observer
handlers, so we let the INI modify handler fail early.

Proper fix suggested by @bwoebi.
We also need to ensure that the functions to observe have already been
called, so that their begin and end handlers are properly initialized.
Otherwise we will not observe the function execution, but a segfault.
@cmb69
Copy link
Member Author

cmb69 commented Oct 16, 2024

Happily hacking along. I think this is okay, but would not work for PHP-8.2 and 8.3 due to ZEND_OBSERVER_DATA only being public as of PHP 8.4.0. To cater to the older branches, more hacks would be necessary, or to employ a clean solution, namely to track all functions which have been already observed in a HashTable in zend_test. I don't think that's worth it, and it's probably okay to only fix this issue for PHP-8.4 given that respective bug reports can only come from fuzz testing (users are not supposed to use zend_test).

@cmb69 cmb69 marked this pull request as ready for review October 16, 2024 19:18
@bwoebi
Copy link
Member

bwoebi commented Oct 20, 2024

@cmb69 Yes, tracking via hashtable is the right way to do. But I agree, for the purposes of zend_test, this is fine.

@cmb69 cmb69 closed this in 909cecb Oct 20, 2024
@cmb69 cmb69 deleted the cmb/gh16414 branch October 20, 2024 10:12
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
if ((func = zend_hash_find_ptr(EG(function_table), funcname)) && ZEND_OBSERVER_DATA(func) != NULL) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this. ZEND_OBSERVER_DATA() expands to &RUN_TIME_CACHE(op_array)[handle], which cannot be NULL (except for RUN_TIME_CACHE(op_array) == NULL && handle == 0, which is undefined behavior). Removing these two conditions still makes the test pass. Should this have dereferenced the pointer?

Copy link
Member

Choose a reason for hiding this comment

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

Actually, it probably must handle ZEND_OBSERVER_NOT_OBSERVED and ZEND_OBSERVER_NONE_OBSERVED. But I forgot about the details of this api.

Copy link
Member

@bwoebi bwoebi Oct 22, 2024

Choose a reason for hiding this comment

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

@iluuu1994 The value is NULL if the observer has not been initialized yet. It's non-NULL once the init handler ran, assigning a specific handler or one of these two constants. Here we only care about initialized.
But it seems to be missing a dereference, yes. (In PHP 8.3 and before it evaluated to the first value. Hence I missed this.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll have a look as soon as possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

@iluuu1994, this PR targets PHP-8.4; the definition of ZEND_OBSERVER_DATA() has changed there:

#define ZEND_OBSERVER_DATA(function) \
((zend_observer_fcall_begin_handler *)&ZEND_OP_ARRAY_EXTENSION((&(function)->common), ZEND_OBSERVER_HANDLE(function)))

That part is needed if zend_test.observer.enabled=1 upfront (default for our Circle CI job).

@bwoebi, when running the test with zend_test.observer.enabled=1, ZEND_OBSERVER_DATA(func) is NULL for me.

Copy link
Member

@iluuu1994 iluuu1994 Oct 22, 2024

Choose a reason for hiding this comment

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

It's because RUN_TIME_CACHE(&func->common) is NULL, so doing pointer arithmetics on it is UB. In that case, I think we should check whether RUN_TIME_CACHE(&func->common) is set. Note that you're lucky here, if observer had a higher handle, this check would break.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, thank you! I'll have a closer look again, and might actually go with the hash table.

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.

zend_test.observer.observe_function_names may segfault
4 participants