[PHP-CVS] [php-src] master: ext/zlib: deflate_init() assertion failure on uninitialised typed properties

[email protected] (David Carlier)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: David Carlier (devnexen)
Date: 2026-08-22T15:41:56+01:00

Commit: https://github.com/php/php-src/commit/c0e7d498c543c11329615b2c71d18556825c46e2
Raw diff: https://github.com/php/php-src/commit/c0e7d498c543c11329615b2c71d18556825c46e2.diff

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

Changed paths:
  A  ext/zlib/tests/gh22142.phpt
  D  ext/zlib/tests/gh22142_inflate.phpt
  M  NEWS
  M  ext/zlib/zlib.c


Diff:

diff --git a/NEWS b/NEWS
index 887ff5b3401d..92e4b175d593 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 000000000000..d41fab7e3312
--- /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 853099e4911d..000000000000
--- 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 44ab23233ac8..f88350081fe3 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(
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.