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: