Commit 93ce0500aa9 for php.net

commit 93ce0500aa90623bb61abaacf98fe905d47dc5f6
Author: Niels Dossche <7771979+ndossche@users.noreply.github.com>
Date:   Sun Nov 16 01:50:09 2025 +0100

    Fix assertion failures resulting in crashes with stream filter object parameters

    This works for dynamic props but not for non-dynamic props due to the
    missing INDIRECT handling.

    Closes GH-20500.

diff --git a/NEWS b/NEWS
index fc03fd36f15..cacfdc28892 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-20435 (SensitiveParameter doesn't work for named argument
     passing to variadic parameter). (ndossche)

+- Bz2:
+  . Fix assertion failures resulting in crashes with stream filter
+    object parameters. (ndossche)
+
 - Date:
   . Fix crashes when trying to instantiate uninstantiable classes via date
     static constructors. (ndossche)
@@ -45,6 +49,10 @@ PHP                                                                        NEWS
 - Zip:
   . Fix crash in property existence test. (ndossche)

+- Zlib:
+  . Fix assertion failures resulting in crashes with stream filter
+    object parameters. (ndossche)
+
 20 Nov 2025, PHP 8.3.28

 - Core:
diff --git a/ext/bz2/bz2_filter.c b/ext/bz2/bz2_filter.c
index 9b3480b4a5c..b093ac7d752 100644
--- a/ext/bz2/bz2_filter.c
+++ b/ext/bz2/bz2_filter.c
@@ -337,12 +337,14 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
 			zval *tmpzval = NULL;

 			if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
-				if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "concatenated", sizeof("concatenated")-1))) {
+				HashTable *ht = HASH_OF(filterparams);
+
+				if ((tmpzval = zend_hash_str_find_ind(ht, "concatenated", sizeof("concatenated")-1))) {
 					data->expect_concatenated = zend_is_true(tmpzval);
 					tmpzval = NULL;
 				}

-				tmpzval = zend_hash_str_find(HASH_OF(filterparams), "small", sizeof("small")-1);
+				tmpzval = zend_hash_str_find_ind(ht, "small", sizeof("small")-1);
 			} else {
 				tmpzval = filterparams;
 			}
@@ -362,7 +364,9 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
 			zval *tmpzval;

 			if (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) {
-				if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "blocks", sizeof("blocks")-1))) {
+				HashTable *ht = HASH_OF(filterparams);
+
+				if ((tmpzval = zend_hash_str_find_ind(ht, "blocks", sizeof("blocks")-1))) {
 					/* How much memory to allocate (1 - 9) x 100kb */
 					zend_long blocks = zval_get_long(tmpzval);
 					if (blocks < 1 || blocks > 9) {
@@ -372,7 +376,7 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
 					}
 				}

-				if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "work", sizeof("work")-1))) {
+				if ((tmpzval = zend_hash_str_find_ind(ht, "work", sizeof("work")-1))) {
 					/* Work Factor (0 - 250) */
 					zend_long work = zval_get_long(tmpzval);
 					if (work < 0 || work > 250) {
diff --git a/ext/bz2/tests/filter_broken_object_options.phpt b/ext/bz2/tests/filter_broken_object_options.phpt
new file mode 100644
index 00000000000..84e49a64ccb
--- /dev/null
+++ b/ext/bz2/tests/filter_broken_object_options.phpt
@@ -0,0 +1,25 @@
+--TEST--
+bz2 filter assertion failure with non-dynamic properties in filter param object
+--EXTENSIONS--
+bz2
+--FILE--
+<?php
+
+class ParamsCompress {
+    public int $blocks = 5;
+    public int $work = 10;
+}
+
+class ParamsDecompress {
+    public bool $concatenated = true;
+    public bool $small = true;
+}
+
+$fp = fopen('php://stdout', 'w');
+stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, new ParamsCompress);
+stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, new ParamsDecompress);
+fwrite($fp, "Hello world, hopefully not broken\n");
+
+?>
+--EXPECT--
+Hello world, hopefully not broken
diff --git a/ext/phar/stream.c b/ext/phar/stream.c
index 1151b520bba..c455a8880d2 100644
--- a/ext/phar/stream.c
+++ b/ext/phar/stream.c
@@ -211,18 +211,18 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
 		php_url_free(resource);
 		efree(internal_file);

-		if (context && Z_TYPE(context->options) != IS_UNDEF && (pzoption = zend_hash_str_find(HASH_OF(&context->options), "phar", sizeof("phar")-1)) != NULL) {
+		if (context && Z_TYPE(context->options) != IS_UNDEF && (pzoption = zend_hash_str_find_ind(HASH_OF(&context->options), "phar", sizeof("phar")-1)) != NULL) {
 			pharcontext = HASH_OF(pzoption);
 			if (idata->internal_file->uncompressed_filesize == 0
 				&& idata->internal_file->compressed_filesize == 0
-				&& (pzoption = zend_hash_str_find(pharcontext, "compress", sizeof("compress")-1)) != NULL
+				&& (pzoption = zend_hash_str_find_ind(pharcontext, "compress", sizeof("compress")-1)) != NULL
 				&& Z_TYPE_P(pzoption) == IS_LONG
 				&& (Z_LVAL_P(pzoption) & ~PHAR_ENT_COMPRESSION_MASK) == 0
 			) {
 				idata->internal_file->flags &= ~PHAR_ENT_COMPRESSION_MASK;
 				idata->internal_file->flags |= Z_LVAL_P(pzoption);
 			}
-			if ((pzoption = zend_hash_str_find(pharcontext, "metadata", sizeof("metadata")-1)) != NULL) {
+			if ((pzoption = zend_hash_str_find_ind(pharcontext, "metadata", sizeof("metadata")-1)) != NULL) {
 				phar_metadata_tracker_free(&idata->internal_file->metadata_tracker, idata->internal_file->is_persistent);

 				metadata = pzoption;
diff --git a/ext/zlib/tests/filter_broken_object_options.phpt b/ext/zlib/tests/filter_broken_object_options.phpt
new file mode 100644
index 00000000000..beb0fef9fb1
--- /dev/null
+++ b/ext/zlib/tests/filter_broken_object_options.phpt
@@ -0,0 +1,21 @@
+--TEST--
+zlib filter assertion failure with non-dynamic properties in filter param object
+--EXTENSIONS--
+zlib
+--FILE--
+<?php
+
+class Params {
+    public int $memory = 6;
+    public int $window = 15;
+    public int $level = 6;
+}
+
+$fp = fopen('php://stdout', 'w');
+stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, new Params);
+stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE, new Params);
+fwrite($fp, "Hello world, hopefully not broken\n");
+
+?>
+--EXPECT--
+Hello world, hopefully not broken
diff --git a/ext/zlib/zlib_filter.c b/ext/zlib/zlib_filter.c
index 24d418ae04c..e5491afec39 100644
--- a/ext/zlib/zlib_filter.c
+++ b/ext/zlib/zlib_filter.c
@@ -323,7 +323,7 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
 			zval *tmpzval;

 			if ((Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) &&
-				(tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
+				(tmpzval = zend_hash_str_find_ind(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
 				/* log-2 base of history window (9 - 15) */
 				zend_long tmp = zval_get_long(tmpzval);
 				if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 32) {
@@ -354,8 +354,10 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f

 			switch (Z_TYPE_P(filterparams)) {
 				case IS_ARRAY:
-				case IS_OBJECT:
-					if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "memory", sizeof("memory") -1))) {
+				case IS_OBJECT: {
+					HashTable *ht = HASH_OF(filterparams);
+
+					if ((tmpzval = zend_hash_str_find_ind(ht, "memory", sizeof("memory") -1))) {
 						/* Memory Level (1 - 9) */
 						tmp = zval_get_long(tmpzval);
 						if (tmp < 1 || tmp > MAX_MEM_LEVEL) {
@@ -365,7 +367,7 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
 						}
 					}

-					if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
+					if ((tmpzval = zend_hash_str_find_ind(ht, "window", sizeof("window") - 1))) {
 						/* log-2 base of history window (9 - 15) */
 						tmp = zval_get_long(tmpzval);
 						if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 16) {
@@ -375,13 +377,14 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
 						}
 					}

-					if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), "level", sizeof("level") - 1))) {
+					if ((tmpzval = zend_hash_str_find_ind(ht, "level", sizeof("level") - 1))) {
 						tmp = zval_get_long(tmpzval);

 						/* Pseudo pass through to catch level validating code */
 						goto factory_setlevel;
 					}
 					break;
+				}
 				case IS_STRING:
 				case IS_DOUBLE:
 				case IS_LONG: