Fix GH-16326: Memory management is broken for bad dictionaries

We must not `efree()` `zend_string`s, since they may have a refcount
greater than one, and may even be interned.

We also must not confuse `zend_string *` with `zend_string **`.

And we should play it safe by using `safe_emalloc()` to avoid
theoretical integer overflows.

We also simplify a bit, according to suggestions of @TimWolla.

Closes GH-16335.
This commit is contained in:
Christoph M. Becker 2024-10-10 13:21:44 +02:00
parent b6ca871396
commit d94be24f30
No known key found for this signature in database
GPG key ID: D66C9593118BCCB6
3 changed files with 43 additions and 19 deletions

4
NEWS
View file

@ -46,6 +46,10 @@ PHP NEWS
. Fixed bug GH-16292 (Segmentation fault in ext/xmlreader/php_xmlreader.c). . Fixed bug GH-16292 (Segmentation fault in ext/xmlreader/php_xmlreader.c).
(nielsdos) (nielsdos)
- Zlib:
. Fixed bug GH-16326 (Memory management is broken for bad dictionaries.)
(cmb)
24 Oct 2024, PHP 8.2.25 24 Oct 2024, PHP 8.2.25
- Calendar: - Calendar:

View file

@ -0,0 +1,26 @@
--TEST--
GH-16326 (Memory management is broken for bad dictionaries)
--EXTENSIONS--
zlib
--FILE--
<?php
try {
deflate_init(ZLIB_ENCODING_DEFLATE, ["dictionary" => [" ", ""]]);
} catch (ValueError $ex) {
echo $ex->getMessage(), "\n";
}
try {
deflate_init(ZLIB_ENCODING_DEFLATE, ["dictionary" => ["hello", "wor\0ld"]]);
} catch (ValueError $ex) {
echo $ex->getMessage(), "\n";
}
try {
deflate_init(ZLIB_ENCODING_DEFLATE, ["dictionary" => [" ", new stdClass]]);
} catch (Error $ex) {
echo $ex->getMessage(), "\n";
}
?>
--EXPECT--
deflate_init(): Argument #2 ($options) must not contain empty strings
deflate_init(): Argument #2 ($options) must not contain strings with null bytes
Object of class stdClass could not be converted to string

View file

@ -807,35 +807,29 @@ static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_
if (zend_hash_num_elements(dictionary) > 0) { if (zend_hash_num_elements(dictionary) > 0) {
char *dictptr; char *dictptr;
zval *cur; zval *cur;
zend_string **strings = emalloc(sizeof(zend_string *) * zend_hash_num_elements(dictionary)); zend_string **strings = safe_emalloc(zend_hash_num_elements(dictionary), sizeof(zend_string *), 0);
zend_string **end, **ptr = strings - 1; zend_string **end, **ptr = strings - 1;
ZEND_HASH_FOREACH_VAL(dictionary, cur) { ZEND_HASH_FOREACH_VAL(dictionary, cur) {
size_t i;
*++ptr = zval_get_string(cur); *++ptr = zval_get_string(cur);
if (!*ptr || ZSTR_LEN(*ptr) == 0 || EG(exception)) { ZEND_ASSERT(*ptr);
if (*ptr) { if (ZSTR_LEN(*ptr) == 0 || EG(exception)) {
efree(*ptr); do {
} zend_string_release(*ptr);
while (--ptr >= strings) { } while (--ptr >= strings);
efree(ptr);
}
efree(strings); efree(strings);
if (!EG(exception)) { if (!EG(exception)) {
zend_argument_value_error(2, "must not contain empty strings"); zend_argument_value_error(2, "must not contain empty strings");
} }
return 0; return 0;
} }
for (i = 0; i < ZSTR_LEN(*ptr); i++) { if (zend_str_has_nul_byte(*ptr)) {
if (ZSTR_VAL(*ptr)[i] == 0) { do {
do { zend_string_release(*ptr);
efree(ptr); } while (--ptr >= strings);
} while (--ptr >= strings); efree(strings);
efree(strings); zend_argument_value_error(2, "must not contain strings with null bytes");
zend_argument_value_error(2, "must not contain strings with null bytes"); return 0;
return 0;
}
} }
*dictlen += ZSTR_LEN(*ptr) + 1; *dictlen += ZSTR_LEN(*ptr) + 1;