[php-src] Issue #22845: Bucket brigade UAF
[email protected] (bahramsasa65-ship-it) Tue, 21 Jul 2026 10:51:24 +0000
| Newsgroups | php.bugs |
|---|---|
| Message-ID | <[email protected]> |
Issue: https://github.com/php/php-src/issues/22845 Author: bahramsasa65-ship-it ### Description=0A=0Aphp_user_filter's filter($in, $out, ...) receives $in = and $out as PHP resources created by zend_register_resource() in userfilter= _filter() but those php_stream_bucket_brigade structures are allocated on = the C stack of the calling function (brig_a/brig_b in _php_stream_filter_fl= ush, filter.c:452; brig_in/brig_out in _php_stream_filter_append filter.c:3= 66). The le_bucket_brigade resource type has no destructor (user_filters.c:= 91), and after the callback returns the code only runs zval_ptr_dtor() on i= ts local references (user_filters.c:249-250) without ever calling zend_list= _close() so a resource copy stashed by userland (e.g. $GLOBALS['x'] =3D $in= ;) survives with res->ptr still pointing at the now-freed stack frame. When= that stale resource is later passed to stream_bucket_make_writeable() or s= tream_bucket_append(), zend_fetch_resource() succeeds (it only checks the r= esource type, not liveness) and returns the dangling brigade pointer, which= is then dereferenced - a use-after-free read and via brigade->tail->next = =3D bucket a use-after-free write into freed stack memory. The root cause i= s that a request-lived PHP resource is allowed to outlive its stack-allocat= ed backing store without being invalidated. The fix is to call zend_list_cl= ose(Z_RES(args[0])) / zend_list_close(Z_RES(args[1])) immediately after the= filter() call (before the zval_ptr_dtors) so any stored copies become non-= fetchable, or to stop backing these resources with stack memory.=0A=0A=0A= =0A### PHP Version=0A=0A```plain=0A8.6.0-dev=0A```=0A=0A### Operating Syste= m=0A=0A_No response_