Commit 70f0ca5f19f for php.net

commit 70f0ca5f19f6d93ce9114f360addaffc3623d2ca
Author: Weilin Du <108666168+LamentXU123@users.noreply.github.com>
Date:   Wed Apr 29 14:45:45 2026 +0800

    Zlib: Improve type checking of several integer options in deflate_init() (#21860)

    `zval_get_long()` does a forceful cast and will even cast values like arrays or objects into integers, something that points to a bug. Use the more modern `zval_try_get_long()` API instead.

    Create a helper function to handle the options consistently.

    Co-authored-by: Gina Peter Banyard <girgias@php.net>

diff --git a/NEWS b/NEWS
index 45f40a07607..3da5464750c 100644
--- a/NEWS
+++ b/NEWS
@@ -205,6 +205,7 @@ PHP                                                                        NEWS

 - Zlib:
   . deflate_init() now raises a TypeError when the value for option
-    "strategy" is not of type int. (Weilin Du)
+    "level", "memory", "window", or "strategy" is not of type int.
+    (Weilin Du)

 <<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
diff --git a/UPGRADING b/UPGRADING
index df81eb88969..56049f30cf9 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -103,7 +103,7 @@ PHP 8.6 UPGRADE NOTES

 - Zlib:
   . deflate_init() now raises a TypeError when the value for option
-    "strategy" is not of type int.
+    "level", "memory", "window", or "strategy" is not of type int.

 ========================================
 2. New Features
diff --git a/ext/zlib/tests/deflate_init_strategy_type_error.phpt b/ext/zlib/tests/deflate_init_strategy_type_error.phpt
index 0227d1bf6c2..2a59082bc32 100644
--- a/ext/zlib/tests/deflate_init_strategy_type_error.phpt
+++ b/ext/zlib/tests/deflate_init_strategy_type_error.phpt
@@ -1,16 +1,42 @@
 --TEST--
-deflate_init(): strategy option type validation
+deflate_init(): options type validation
 --EXTENSIONS--
 zlib
 --FILE--
 <?php

+class A {}
+$fp = fopen('php://memory', 'r');
+
 try {
-    deflate_init(ZLIB_ENCODING_DEFLATE, ['strategy' => []]);
+    deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 'foo']);
 } catch (TypeError $e) {
     echo $e->getMessage(), PHP_EOL;
 }

+try {
+    deflate_init(ZLIB_ENCODING_DEFLATE, ['memory' => []]);
+} catch (TypeError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+    deflate_init(ZLIB_ENCODING_DEFLATE, ['window' => new A()]);
+} catch (TypeError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+    deflate_init(ZLIB_ENCODING_DEFLATE, ['strategy' => $fp]);
+} catch (TypeError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+fclose($fp);
+
 ?>
 --EXPECT--
-deflate_init(): Argument #2 ($options) the value for option "strategy" must be of type int, array given
+deflate_init(): Argument #2 ($options) the value for option "level" must be of type int, string given
+deflate_init(): Argument #2 ($options) the value for option "memory" must be of type int, array given
+deflate_init(): Argument #2 ($options) the value for option "window" must be of type int, A given
+deflate_init(): Argument #2 ($options) the value for option "strategy" must be of type int, resource given
diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c
index ae03496b7c1..b1ee09635e5 100644
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -854,6 +854,30 @@ static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_
 	return true;
 }

+ZEND_ATTRIBUTE_NONNULL static bool zlib_get_long_option(HashTable *options, const char *option_name, size_t option_name_len, zend_long *value)
+{
+	bool failed = false;
+	zval *option_buffer = zend_hash_str_find(options, option_name, option_name_len);
+
+	if (!option_buffer) {
+		return true;
+	}
+
+	/* The |H ZPP specifier may leave HashTable entries wrapped in IS_INDIRECT. */
+	ZVAL_DEINDIRECT(option_buffer);
+	*value = zval_try_get_long(option_buffer, &failed);
+	if (UNEXPECTED(failed)) {
+		zend_argument_type_error(
+			2,
+			"the value for option \"%.*s\" must be of type int, %s given",
+			(int) option_name_len, option_name, zend_zval_value_name(option_buffer)
+		);
+		return false;
+	}
+
+	return true;
+}
+
 /* {{{ Initialize an incremental inflate context with the specified encoding */
 PHP_FUNCTION(inflate_init)
 {
@@ -1080,49 +1104,38 @@ PHP_FUNCTION(deflate_init)
 	zend_long encoding, level = -1, memory = 8, window = 15, strategy = Z_DEFAULT_STRATEGY;
 	char *dict = NULL;
 	size_t dictlen = 0;
-	HashTable *options = NULL;
-	zval *option_buffer;
+	HashTable *options = (HashTable*)&zend_empty_array;

 	if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "l|H", &encoding, &options)) {
 		RETURN_THROWS();
 	}

-	if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("level"))) != NULL) {
-		ZVAL_DEINDIRECT(option_buffer);
-		level = zval_get_long(option_buffer);
+	if (!zlib_get_long_option(options, ZEND_STRL("level"), &level)) {
+		RETURN_THROWS();
 	}
 	if (level < -1 || level > 9) {
 		zend_value_error("deflate_init(): \"level\" option must be between -1 and 9");
 		RETURN_THROWS();
 	}

-	if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("memory"))) != NULL) {
-		ZVAL_DEINDIRECT(option_buffer);
-		memory = zval_get_long(option_buffer);
+	if (!zlib_get_long_option(options, ZEND_STRL("memory"), &memory)) {
+		RETURN_THROWS();
 	}
 	if (memory < 1 || memory > 9) {
 		zend_value_error("deflate_init(): \"memory\" option must be between 1 and 9");
 		RETURN_THROWS();
 	}

-	if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("window"))) != NULL) {
-		ZVAL_DEINDIRECT(option_buffer);
-		window = zval_get_long(option_buffer);
+	if (!zlib_get_long_option(options, ZEND_STRL("window"), &window)) {
+		RETURN_THROWS();
 	}
 	if (window < 8 || window > 15) {
 		zend_value_error("deflate_init(): \"window\" option must be between 8 and 15");
 		RETURN_THROWS();
 	}

-	if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("strategy"))) != NULL) {
-		bool failed = false;
-
-		ZVAL_DEINDIRECT(option_buffer);
-		strategy = zval_try_get_long(option_buffer, &failed);
-		if (UNEXPECTED(failed)) {
-			zend_argument_type_error(2, "the value for option \"strategy\" must be of type int, %s given", zend_zval_value_name(option_buffer));
-			RETURN_THROWS();
-		}
+	if (!zlib_get_long_option(options, ZEND_STRL("strategy"), &strategy)) {
+		RETURN_THROWS();
 	}
 	switch (strategy) {
 		case Z_FILTERED: