[php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6
| From: | Jakub Zelenka | Date: | Tue, 06 Oct 2026 14:38:01 +0000 |
| Subject: | [php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6 | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142008@lists.php.net to get a copy of this message | ||
Author: Jakub Zelenka (bukka)
Date: 2026-10-06T16:37:48+02:00
Commit: https://github.com/php/php-src/commit/367c38d8dc29ad3f10268bd14b09dfd4694a3417
Raw diff: https://github.com/php/php-src/commit/367c38d8dc29ad3f10268bd14b09dfd4694a3417.diff
Merge branch 'PHP-8.5' into PHP-8.6
* PHP-8.5:
Fix bzopen() ownership of the stream it wraps
Changed paths:
A ext/bz2/tests/bzopen_resource_fclose.phpt
M NEWS
M ext/bz2/bz2.c
Diff:
diff --git a/NEWS b/NEWS
index 8b30e7f4d3e7..2ca57a21019f 100644
--- a/NEWS
+++ b/NEWS
@@ -6,11 +6,27 @@ PHP NEWS
. Fixed BcMath\Number results that truncate to zero keeping a negative sign
and comparing less than zero. (Ilia Alshanetsky)
-- CLI:
+- BZ2:
+ . Fixed double close of the descriptor and use-after-free of the inner
+ stream when bzopen() is given a stream resource. (Jakub Zelenka)
+
+- CLI
+ . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
+ request activation). (matyhtf)
+ . Fixed bug GH-23764 (Built-in server leaks a file descriptor on every HEAD
+ request for a static file). (jakubskopal)
+ . Fixed crash in the built-in server when a client is reset before being
+ accepted. (David Carlier)
. Fixed the built-in server truncating an error page and leaking its
unsent part after a partial socket write. (Ilia Alshanetsky)
-- Core:
+- Core
+ . Fixed bug GH-23644 (Optimizer leaves a constant-vs-constant comparison
+ unfolded, crashing the VM in zval_undefined_cv). (ndossche)
+ . Fixed OSS-Fuzz 532353396 (assertion failure with static type). (Girgias)
+ . Fix GH-23662 (Avoid NAN warning in print_r()). (CodedByManish)
+ . Fix GH-21999: GC inconsistency with lazy object, var_dump(), and object
+ comparison. (Arnaud)
. Fixed OSS-Fuzz #536440507 (Immutable class incorrect assertion).
(ndossche)
. Fixed bug GH-23628 (Tracing JIT reads undefined property slots of lazy
diff --git a/ext/bz2/bz2.c b/ext/bz2/bz2.c
index 6af8286c5335..891e51bd563e 100644
--- a/ext/bz2/bz2.c
+++ b/ext/bz2/bz2.c
@@ -57,7 +57,7 @@ ZEND_GET_MODULE(bz2)
struct php_bz2_stream_data_t {
BZFILE *bz_file;
- php_stream *stream;
+ zend_resource *stream_res;
};
/* {{{ BZip2 stream implementation */
@@ -129,8 +129,15 @@ static int php_bz2iop_close(php_stream *stream, int close_handle)
BZ2_bzclose(self->bz_file);
}
- if (self->stream) {
- php_stream_free(self->stream, PHP_STREAM_FREE_CLOSE | (close_handle == 0 ?
PHP_STREAM_FREE_PRESERVE_HANDLE : 0));
+ /* The inner stream may have been closed by the user, only its resource is held */
+ if (self->stream_res) {
+ php_stream *inner = zend_fetch_resource2(
+ self->stream_res, NULL, php_file_le_stream(), php_file_le_pstream());
+ if (inner) {
+ php_stream_free(inner, PHP_STREAM_FREE_CLOSE | (close_handle == 0 ?
PHP_STREAM_FREE_PRESERVE_HANDLE : 0));
+ } else {
+ zend_list_delete(self->stream_res);
+ }
}
efree(self);
@@ -156,6 +163,23 @@ const php_stream_ops php_stream_bz2io_ops = {
};
/* {{{ Bzip2 stream openers */
+
+/* bzlib closes the descriptor it is given, so it gets a duplicate */
+static BZFILE *php_bz2_bzdopen(php_socket_t fd, const char *mode)
+{
+ int dup_fd = dup((int) fd);
+ if (dup_fd == -1) {
+ return NULL;
+ }
+
+ BZFILE *bz = BZ2_bzdopen(dup_fd, mode);
+ if (!bz) {
+ close(dup_fd);
+ }
+
+ return bz;
+}
+
PHP_BZ2_API php_stream *_php_stream_bz2open_from_BZFILE(BZFILE *bz,
const char *mode, php_stream *innerstream STREAMS_DC)
{
@@ -163,8 +187,9 @@ PHP_BZ2_API php_stream *_php_stream_bz2open_from_BZFILE(BZFILE *bz,
self = emalloc(sizeof(*self));
- self->stream = innerstream;
+ self->stream_res = NULL;
if (innerstream) {
+ self->stream_res = innerstream->res;
GC_ADDREF(innerstream->res);
}
self->bz_file = bz;
@@ -221,7 +246,7 @@ PHP_BZ2_API php_stream *_php_stream_bz2open(php_stream_wrapper *wrapper,
if (stream) {
php_socket_t fd;
if (SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS)) {
- bz_file = BZ2_bzdopen((int)fd, mode);
+ bz_file = php_bz2_bzdopen(fd, mode);
}
}
@@ -407,7 +432,10 @@ PHP_FUNCTION(bzopen)
RETURN_FALSE;
}
- bz = BZ2_bzdopen((int)fd, mode);
+ bz = php_bz2_bzdopen(fd, mode);
+ if (!bz) {
+ RETURN_FALSE;
+ }
stream = php_stream_bz2open_from_BZFILE(bz, mode, stream);
} else {
diff --git a/ext/bz2/tests/bzopen_resource_fclose.phpt b/ext/bz2/tests/bzopen_resource_fclose.phpt
new file mode 100644
index 000000000000..d194ba406979
--- /dev/null
+++ b/ext/bz2/tests/bzopen_resource_fclose.phpt
@@ -0,0 +1,41 @@
+--TEST--
+bzopen() on a stream resource: the inner stream may be closed first
+--EXTENSIONS--
+bz2
+--FILE--
+<?php
+$file = tempnam(sys_get_temp_dir(), 'bz2');
+$victim = tempnam(sys_get_temp_dir(), 'bz2');
+$payload = str_repeat("payload", 100);
+
+$fp = fopen($file, 'w');
+$bz = bzopen($fp, 'w');
+var_dump(bzwrite($bz, $payload));
+
+// The bz2 stream owns its own descriptor, so closing the inner stream neither
+// invalidates it nor makes it write into the next descriptor opened
+var_dump(fclose($fp));
+$other = fopen($victim, 'w');
+bzclose($bz);
+var_dump(fclose($other));
+
+var_dump(bzdecompress(file_get_contents($file)) === $payload);
+var_dump(filesize($victim));
+
+$fp = fopen($file, 'r');
+$bz = bzopen($fp, 'r');
+var_dump(fclose($fp));
+var_dump(bzread($bz, 8192) === $payload);
+bzclose($bz);
+
+unlink($file);
+unlink($victim);
+?>
+--EXPECT--
+int(700)
+bool(true)
+bool(true)
+bool(true)
+int(0)
+bool(true)
+bool(true)