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>
This commit is contained in:
Weilin DuandGina Peter Banyard authored and GitHub committed 2026-04-29 07:45:45 +01:00
1 parent 43889fa5cc
commit 70f0ca5f19
4 files changed
+65 -25

No files matched your search

+2 -1
View File
@@ -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! >>>
+1 -1
View File
@@ -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
@@ -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
+33 -20
View File
@@ -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: