Commit c0e7d498c54 for php.net
commit c0e7d498c543c11329615b2c71d18556825c46e2
Author: David Carlier <devnexen@gmail.com>
Date: Sat Aug 22 14:46:28 2026 +0100
ext/zlib: deflate_init() assertion failure on uninitialised typed properties
Fix GH-22142
An object passed as options exposes uninitialised typed properties as
IS_UNDEF slots, which zval_try_get_long() rejected through an unreachable
branch. Those slots are now skipped, matching get_object_vars().
Close GH-23409
diff --git a/NEWS b/NEWS
index 887ff5b3401..92e4b175d59 100644
--- a/NEWS
+++ b/NEWS
@@ -83,6 +83,10 @@ PHP NEWS
. Fixed bug GH-17787 (ZipArchive stream stops reading early when the archive
is freed while the stream is still open). (Eyüp Can Akman)
+- Zlib:
+ . Fixed bug GH-22142 (Assertion failure in deflate_init() when an option
+ object has uninitialised typed properties). (David Carlier)
+
13 Aug 2026, PHP 8.6.0beta1
- Core:
diff --git a/ext/zlib/tests/gh22142.phpt b/ext/zlib/tests/gh22142.phpt
new file mode 100644
index 00000000000..d41fab7e331
--- /dev/null
+++ b/ext/zlib/tests/gh22142.phpt
@@ -0,0 +1,50 @@
+--TEST--
+GH-22142 (Assertion failure in zendi_try_get_long() on IS_UNDEF)
+--CREDITS--
+JIANG Yuancheng
+--EXTENSIONS--
+zlib
+--INI--
+error_reporting=E_ALL & ~E_DEPRECATED
+--FILE--
+<?php
+
+class DeflateOptions {
+ public int $level;
+ public int $memory;
+ public int $window;
+ public int $strategy;
+ public string $dictionary;
+}
+
+class InflateOptions {
+ public int $window;
+ public string $dictionary;
+}
+
+class BadDeflateOptions {
+ public int $level = 42;
+ public int $memory;
+}
+
+$deflate = new DeflateOptions();
+var_dump(deflate_init(ZLIB_ENCODING_DEFLATE, $deflate) instanceof DeflateContext);
+var_dump(deflate_init(ZLIB_ENCODING_DEFLATE, get_object_vars($deflate)) instanceof DeflateContext);
+
+$inflate = new InflateOptions();
+var_dump(inflate_init(ZLIB_ENCODING_DEFLATE, $inflate) instanceof InflateContext);
+var_dump(inflate_init(ZLIB_ENCODING_DEFLATE, get_object_vars($inflate)) instanceof InflateContext);
+
+try {
+ deflate_init(ZLIB_ENCODING_DEFLATE, new BadDeflateOptions());
+} catch (ValueError $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+ValueError: deflate_init(): "level" option must be between -1 and 9
diff --git a/ext/zlib/tests/gh22142_inflate.phpt b/ext/zlib/tests/gh22142_inflate.phpt
deleted file mode 100644
index 853099e4911..00000000000
--- a/ext/zlib/tests/gh22142_inflate.phpt
+++ /dev/null
@@ -1,21 +0,0 @@
---TEST--
-GH-22142 (Assertion failure in zendi_try_get_long() on IS_UNDEF)
---EXTENSIONS--
-zlib
---FILE--
-<?php
-
-class Options {
- public int $window;
-}
-
-try {
- inflate_init(ZLIB_ENCODING_DEFLATE, new Options());
-} catch (TypeError $e) {
- echo $e::class, ': ', $e->getMessage(), PHP_EOL;
-}
-
-?>
---EXPECTF--
-Deprecated: inflate_init(): Passing an object for argument #2 $option to inflate_init() is deprecated, call get_object_vars() first instead in %s on line %d
-TypeError: inflate_init(): Argument #2 ($options) the value for option "window" must be of type int, null given
diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c
index 44ab23233ac..f88350081fe 100644
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -779,11 +779,26 @@ PHP_ZLIB_DECODE_FUNC(gzdecode, PHP_ZLIB_ENCODING_GZIP);
PHP_ZLIB_DECODE_FUNC(gzuncompress, PHP_ZLIB_ENCODING_DEFLATE);
/* }}} */
+ZEND_ATTRIBUTE_NONNULL static zval *zlib_find_option(HashTable *options, const char *name, size_t name_len)
+{
+ zval *option = zend_hash_str_find(options, name, name_len);
+
+ if (!option) {
+ return NULL;
+ }
+
+ ZVAL_DEINDIRECT(option);
+
+ if (UNEXPECTED(Z_TYPE_P(option) == IS_UNDEF)) {
+ return NULL;
+ }
+ return option;
+}
+
static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_t *dictlen) {
zval *option_buffer;
- if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("dictionary"))) != NULL) {
- ZVAL_DEINDIRECT(option_buffer);
+ if (options && (option_buffer = zlib_find_option(options, ZEND_STRL("dictionary"))) != NULL) {
ZVAL_DEREF(option_buffer);
switch (Z_TYPE_P(option_buffer)) {
case IS_STRING: {
@@ -853,14 +868,12 @@ static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_
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);
+ zval *option_buffer = zlib_find_option(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(