Skip to content

Fix GH-12232: FPM: segfault dynamically loading extension without opache #12233

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 1 commit into from

Conversation

bukka
Copy link
Member

@bukka bukka commented Sep 17, 2023

The reason for the cache is that loading happens after the child is forked before the interned strings are initialiazed if opcache is not loaded. If opcache is loaded, it overwrites zend_new_interned_string_request and uses its own version which does not have this issue.

The fix makes sure that the interned string hast table is initialized before used.

@bukka bukka changed the base branch from master to PHP-8.1 September 17, 2023 14:24
@bukka bukka force-pushed the fpm_config_extension_crash branch from da22696 to da3310f Compare September 17, 2023 14:24
Copy link
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

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

Shouldn't other calls to zend_interned_strings_activate() also be wrapped in if (!CG(interned_strings_initialized)) {? Otherwise another call to it will leak the existing hash table data. It would probably make sense to move the check to zend_interned_strings_activate() itself. You could probably also drop the global by checking for HT_IS_INITIALIZED on the hashtable.

Furthermore, there's actually some code that relies on an empty persistent string table to recognize whether we're using opcache.

php-src/Zend/zend.c

Lines 1321 to 1323 in 886bf82

if (zend_hash_num_elements(&CG(interned_strings)) > 0) {
zend_map_ptr_reset();
}

We're also calling zend_interned_strings_deactivate() from php_request_shutdown(). If we're initializing interned strings before requests are handled, I assume they should survive the request, but they wouldn't and might thus use-after-free.

Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

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

I think the approach is wrong.

According to back-trace, we are loading extension with type == MODULE_PERSISTENT, but at the same time we are trying to register its name as an interned string with "request" time using zend_new_interned_string_request(). zend_new_interned_string_permanent() should be used at this point. The problem is in FPM tricks.

Ideally fpm_php_init_child() should be called before switching to request storage. This probably won't work with non-static FPM pools.

It's possible to switch string storage to permanent and back calling zend_interned_strings_switch_storage() in appropriate places of FPM , but this may cause problems when opcache loaded. In the worst case we will need an API to override and restore interned_string_request_handler.

@bukka
Copy link
Member Author

bukka commented Sep 22, 2023

@dstogov thanks for the review and suggestions. See below my comments:

According to back-trace, we are loading extension with type == MODULE_PERSISTENT, but at the same time we are trying to register its name as an interned string with "request" time using zend_new_interned_string_request(). zend_new_interned_string_permanent() should be used at this point. The problem is in FPM tricks.

You are of course right. This usage is incorrect. It was actually done in this way sooner in FPM and engine changes later probably broke this but no one probably defines extension in FPM config and not using opcache at the same time. I found it only when testing another thing in my debug build. Still, it is a crash so it should be fixed.

Ideally fpm_php_init_child() should be called before switching to request storage. This probably won't work with non-static FPM pools.

Yeah this wouldn't work. The reason is that it needs to load extension in a child is because php_admin_value is a pool configuration. It means it might differ between pools so the only thing how to make it work is to load extension dynamically in each child because pool is just a group of children. I think it is another problem that could be resolved by a pool manager: #11723 as we could do it in that pool manager process before startup in the same way how it is loaded through INI.

It's possible to switch string storage to permanent and back calling zend_interned_strings_switch_storage() in appropriate places of FPM , but this may cause problems when opcache loaded. In the worst case we will need an API to override and restore interned_string_request_handler.

That zend_interned_strings_switch_storage seems like something that could work. I created a PR #12277 for that. I have been checking opcache and the only potential issues that I found is that it won't be copied by accel_copy_permanent_strings and thus won't be shared but not sure what implications of that are. Could this be problematic and is there anything else?

@bukka
Copy link
Member Author

bukka commented Oct 2, 2023

@dstogov Just a gentle reminder when you have time to check the above and the new PR out. Thanks.

@bukka
Copy link
Member Author

bukka commented Nov 17, 2023

This was implemented in #12277

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.

3 participants