-
Notifications
You must be signed in to change notification settings - Fork 7.9k
fix segfault in ZEND_BIND_STATIC
#12758
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
Conversation
In case a `ZEND_BIND_STATIC` is being executed, while the current chunk is full, the `zend_array_dup()` call will trigger a OOM in ZendMM which will crash, as the opline might be a dangling pointer.
I added the missing test 🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests fail, and I have remarks and questions. VM change itself looks good and should not impact performance as it simply moves a SAVE_OPLINE we already did anyway.
cc @iluuu1994
This is once again similar to #12648. This change is harmless, because (as Niels pointed out) the opline is saved anyway. I wonder if an alternative approach for Levis PR might be to initialize Lines 3998 to 4002 in a4e80b0
This might overall be cheaper than initializing all oplines that may OOM. |
Perhaps yeah, although we do a lot of SAVE_OPLINE anyway because a lot of instructions can throw, in which case it wouldn't save anything I think (unless we can delay the SAVE_OPLINE then...). Of course, this is speculation and overhead should be measured. |
@nielsdos I agree, my comment was somewhat speculative and this should be measured. In theory, one handler execution per function that skips Edit: Also, |
It was in i_init_func_execute_data in the past. It's been probably removed from there as it's technically not necessary and we want it in the opline handlers for being accurate. I'm curious whether it would be instead possible to make IP and FP truly global across all of php-src and extensions? Avoiding the need to store it at all (short of switching frames) and universally accessible. This would obviously reduce some register availability, but I'd be interested in performance characteristics of that - and it would make things simpler as to not having to store IP at all. The only thing which would then need handling are extensions which execute callbacks in response to a C callback from an external library. |
@bwoebi It would indeed be nice to avoid the whole
https://gcc.gnu.org/onlinedocs/gcc/Global-Register-Variables.html
External libraries that call into extension code might clobber the IP register. Similarly, the extension might modify the IP register and not restore the register for the external library (which it should according to ABI). I'm not very familiar with this topic, maybe there are nice solutions to this. |
ccf4148
to
5acd417
Compare
Yes, it would require support at the extension coupling point. Like instead of just NTS vs ZTS and DEBUG vs NDEBUG, we'd now also need REG / NOREG. But in practice whole platforms are using the the same. GCC and clang both support global registers
Yes, that means, if you wish to access IP / FP in callbacks from an external non-extension code, you need to manually store IP/FP into thread local state before the external function call, and backup the register contents in a var and restore it in callback start, then set it back to backup var. Macros could help with that. But it's a relatively rare case that you need to access IP/FP from external callbacks. |
5acd417
to
2449f9e
Compare
@nielsdos / @iluuu1994 thanks for your feedback so far. I'll try and see if I can make reproducer for the other cases @morrisonlevi found in his PR and try to make other PR's to PHP 8.1 today |
Actually, I don't think it is, because the original issue here is OOM. We often use |
@bwoebi Just FYI, I'm experimenting with this. I think the idea is sound, apart from OOM. I'll post a PoC tomorrow or so. |
@iluuu1994 for the edge case of the fatal error, we could possibly handle it in zend_catch, i.e. after register restore, that we just compute the error (sprintf), store it and then handle afterwards, enriching with the now available registers. |
And also, often. I think it's PCRE, xml and tidy. And that's it for php-src, which use custom allocators within external library code here. |
Merged together with #12768. |
In case a
ZEND_BIND_STATIC
is being executed, while the current chunk is full, thezend_array_dup()
call will trigger a OOM in ZendMM which will crash, as the opline might be a dangling pointer.I am currently working on a stable test for this, but it is reproducible with the following PHP code:
The
10364
in the for loop is the "magic" number to fill up all the bins in the current chunk (this might vary).Now in the call to
ref()
PHP will execute theZEND_BIND_STATIC
opcode and choose the path viazend_array_dub()
in https://github.com/php/php-src/blob/PHP-8.1/Zend/zend_vm_def.h#L8793 which will try to allocate a new chunk, failing todo so as the memory limit is reached and then crash while trying to follow theopline
pointer inzend_get_executed_lineno()
while trying to create the "Allowed memory size of ..."-Error message.I am currently working on a test, to reproduce this in a stable manner and not using a magic number that might change.