[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_