Skip to content

Fix use-after-free with nested FFI::addr() calls #9599

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

iluuu1994
Copy link
Member

Fixes GH-9598

@dstogov I did not see another way to solve this. Basically, cdata1.ptr -> cdata1.ptr_holder -> cdata2.ptr_holder -> Example. Right after \FFI::addr(\FFI::addr($struct)) cdata2 is released. Since this object is released by the VM when the TMPVAR is destroyed we need to increase the refcount of the CData object itself.

Please let me know if you can see a better solution. This is the first time I've looked at the FFI internals, very much possible I'm missing something simple.

Thanks!

@dstogov
Copy link
Member

dstogov commented Sep 23, 2022

@iluuu1994 thanks for looking into this.

See https://www.php.net/manual/en/ffi.addr.php

  1. FFI::addr() creates unmanaged pointer. PHP/FFI won't try to keep the pointed data alive.
  2. I suspect FFI::addr(FFI::addr($var)) was used to get ptr_ptr to the var, but its equivalent in C is &(&var). This C code doesn't make sense at all, why should it work in PHP :)

I don't think this should be fixed, may be it's better to throw a error.
I may be wrong. You are welcome for discussion. I hope, I'll be able to look into this deeper on Monday.

@iluuu1994
Copy link
Member Author

iluuu1994 commented Sep 23, 2022

@dstogov Ah! Thanks for explaining. Should've read the documentation better 🙂

I suspect FFI::addr(FFI::addr($var)) was used to get ptr_ptr to the var, but its equivalent in C is &(&var). This C code doesn't make sense at all, why should it work in PHP :)

Yeah, I found this in a Symfony test which I'll fix instead then.

I don't think this should be fixed, may be it's better to throw a error.

This would be the case when GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1, right? What confused me is that this is explicitly handled in FFI::addr():

php-src/ext/ffi/ffi.c

Lines 4231 to 4242 in b61c81c

if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1) {
if (ZEND_FFI_TYPE_IS_OWNED(cdata->type)) {
/* transfer type ownership */
cdata->type = type;
new_type->pointer.type = ZEND_FFI_TYPE_MAKE_OWNED(type);
}
if (cdata->flags & ZEND_FFI_FLAG_OWNED) {
/* transfer ownership */
cdata->flags &= ~ZEND_FFI_FLAG_OWNED;
new_cdata->flags |= ZEND_FFI_FLAG_OWNED;
}
}

That branch then wouldn't make much sense as it is.

Edit: I later found out transferring ownership of other things like structs works just fine as for those the CData can safely be deallocated with the data still being fully valid. I added a test for this in #9601.

nicolas-grekas added a commit to symfony/symfony that referenced this pull request Sep 23, 2022
…n test (iluuu1994)

This PR was merged into the 6.2 branch.

Discussion
----------

[VarDumper] Fix use-after-free with nested FFI::addr() in test

| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Tickets       | php/php-src#9598
| License       | MIT
| Doc PR        |

This issue was found by the PHP community build, after which I created php/php-src#9598. Basically, the inner `\FFI::addr()` `CData` will be deallocated after the second `\FFI::addr()` call which will be stored and then points to invalid memory. However, [it turns out](php/php-src#9599 (comment)) nesting `\FFI::addr()` calls without temporarily storing the result is actually not allowed.

https://www.php.net/manual/en/ffi.addr.php

> Creates an unmanaged pointer to the C data represented by the given `FFI\CData`. The source ptr must survive the resulting pointer. This function is mainly useful to pass arguments to C functions by pointer.

We'll see if we can improve `FFI` by throwing an exception when passing temporary values to `FFI::addr()`. Either way, this test is not valid.

Commits
-------

4c79915 Fix use-after-free with nested FFI::addr() in VarDumper test
symfony-splitter pushed a commit to symfony/var-dumper that referenced this pull request Sep 23, 2022
…n test (iluuu1994)

This PR was merged into the 6.2 branch.

Discussion
----------

[VarDumper] Fix use-after-free with nested FFI::addr() in test

| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Tickets       | php/php-src#9598
| License       | MIT
| Doc PR        |

This issue was found by the PHP community build, after which I created php/php-src#9598. Basically, the inner `\FFI::addr()` `CData` will be deallocated after the second `\FFI::addr()` call which will be stored and then points to invalid memory. However, [it turns out](php/php-src#9599 (comment)) nesting `\FFI::addr()` calls without temporarily storing the result is actually not allowed.

https://www.php.net/manual/en/ffi.addr.php

> Creates an unmanaged pointer to the C data represented by the given `FFI\CData`. The source ptr must survive the resulting pointer. This function is mainly useful to pass arguments to C functions by pointer.

We'll see if we can improve `FFI` by throwing an exception when passing temporary values to `FFI::addr()`. Either way, this test is not valid.

Commits
-------

4c799152ec Fix use-after-free with nested FFI::addr() in VarDumper test
@iluuu1994 iluuu1994 closed this Sep 23, 2022
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.

2 participants