Commit d5f84c738b9 for php
commit d5f84c738b984b67079fddf8f4d77201a2bf01d6
Author: Edmond <1571649+edmonddantes@users.noreply.github.com>
Date: Tue Oct 6 23:55:53 2026 +0000
Fix use-after-free when removing a failing zlib.inflate filter (#24176)
Corrupt input makes zlib.inflate raise an E_NOTICE. An error handler can
remove the filter and free its state, so resetting the input pointers after
the notice writes to freed memory.
Release the input bucket and reset the filter state before raising the
notice, then return without accessing the filter again. Add regression
coverage for error handlers that remove both write and read filters.
Closing the stream itself from an error handler is outside this fix and
requires protection around the filter chain walk.
Closes #24176
diff --git a/NEWS b/NEWS
index 5c01150cabe..05dc5a7c733 100644
--- a/NEWS
+++ b/NEWS
@@ -49,6 +49,10 @@ PHP NEWS
is called from a progress or cancel callback during close().
(Ilia Alshanetsky)
+- Zlib:
+ . Fixed use-after-free when an error handler removes a failing zlib.inflate
+ filter (GH-24176). (Edmond)
+
22 Oct 2026, PHP 8.4.27
- BCMath:
diff --git a/ext/zlib/tests/zlib_filter_inflate_error_handler_removes_filter.phpt b/ext/zlib/tests/zlib_filter_inflate_error_handler_removes_filter.phpt
new file mode 100644
index 00000000000..ee5bab1052f
--- /dev/null
+++ b/ext/zlib/tests/zlib_filter_inflate_error_handler_removes_filter.phpt
@@ -0,0 +1,30 @@
+--TEST--
+zlib.inflate filter removed by the error handler of its own notice
+--EXTENSIONS--
+zlib
+--FILE--
+<?php
+$fp = fopen('php://memory', 'w+');
+$filter = stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE);
+set_error_handler(function (int $errno, string $errstr) use (&$filter) {
+ echo $errstr, "\n";
+ var_dump(stream_filter_remove($filter));
+ return true;
+});
+var_dump(fwrite($fp, "\xff\xff\xff\xff"));
+
+$fp = fopen('php://memory', 'w+');
+fwrite($fp, "\xff\xff\xff\xff");
+rewind($fp);
+$filter = stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_READ);
+var_dump(fread($fp, 10));
+echo "Done\n";
+?>
+--EXPECT--
+fwrite(): zlib: data error
+bool(true)
+bool(false)
+fread(): zlib: data error
+bool(true)
+bool(false)
+Done
diff --git a/ext/zlib/zlib_filter.c b/ext/zlib/zlib_filter.c
index e5491afec39..85711be8caa 100644
--- a/ext/zlib/zlib_filter.c
+++ b/ext/zlib/zlib_filter.c
@@ -90,11 +90,12 @@ static php_stream_filter_status_t php_zlib_inflate_filter(
exit_status = PSFS_PASS_ON;
} else if (status != Z_OK && status != Z_BUF_ERROR) {
/* Something bad happened */
- php_error_docref(NULL, E_NOTICE, "zlib: %s", zError(status));
php_stream_bucket_delref(bucket);
/* reset these because despite the error the filter may be used again */
data->strm.next_in = data->inbuf;
data->strm.avail_in = 0;
+ /* Last: an error handler may remove this filter and free data. */
+ php_error_docref(NULL, E_NOTICE, "zlib: %s", zError(status));
return PSFS_ERR_FATAL;
}
desired -= data->strm.avail_in; /* desired becomes what we consumed this round through */