diff --git a/NEWS b/NEWS index efc85058719e..e947b61b9d4c 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,8 @@ PHP NEWS (kocsismate) . Fixed bug GH-22292 (AST pretty printing does not correctly handle invalid variable names). (timwolla) + . Fixed bug GH-22291 (AST pretty printing does not correctly handle braces + in string interpolation). (timwolla) - BCMath: . Added NUL-byte validation to BCMath functions. (jorgsowa) diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt index 79ea3703ead6..3bb08504f581 100644 --- a/Zend/tests/assert/expect_015.phpt +++ b/Zend/tests/assert/expect_015.phpt @@ -201,7 +201,7 @@ assert(0 && ($a = function &(array &$a, ?X $b = null) use($c, &$d): ?X { $s[$i] = $a[$j]; } foreach ($a as $key => &$val) { - print "$key => $val\n"; + print "{$key} => {$val}\n"; } while ($s[$i]) { $i++; @@ -299,12 +299,12 @@ assert(0 && ($a = function (): ?static { echo 1; } $x = '\'"`$a'; - $x = "'\"`$a"; - $x = `'"\`$a`; + $x = "'\"`{$a}"; + $x = `'"\`{$a}`; $x = "{$a}b"; $x = "{$a}b"; $x = " {$foo->bar} {${$foo->bar}} "; - $x = " ${'---'} "; + $x = " {${'---'}} "; foo(); \foo(); namespace\foo(); diff --git a/Zend/tests/assert/expect_020.phpt b/Zend/tests/assert/expect_020.phpt index d305667a28d7..2146abc96045 100644 --- a/Zend/tests/assert/expect_020.phpt +++ b/Zend/tests/assert/expect_020.phpt @@ -16,5 +16,5 @@ assert(0 && ($a = function () { --EXPECT-- assert(): assert(0 && ($a = function () { $var = 'test'; - $str = "$var, {$var[1]}, {$var}[], {$var[1]}[], {$var}[], {$var[1]}[]"; + $str = "{$var}, {$var[1]}, {$var}[], {$var[1]}[], {$var}[], {$var[1]}[]"; })) failed diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 983299c0a9d8..c908f6b18c7f 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1675,19 +1675,6 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int zend_ast_export_ex(str, ast, priority, indent); } -static ZEND_COLD bool zend_ast_valid_var_char(char ch) -{ - unsigned char c = (unsigned char)ch; - - if (c != '_' && c < 127 && - (c < '0' || c > '9') && - (c < 'A' || c > 'Z') && - (c < 'a' || c > 'z')) { - return false; - } - return true; -} - static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len) { unsigned char c; @@ -1714,11 +1701,6 @@ static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len) return true; } -static ZEND_COLD bool zend_ast_var_needs_braces(char ch) -{ - return ch == '[' || zend_ast_valid_var_char(ch); -} - static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int indent) { if (ast->kind == ZEND_AST_ZVAL) { @@ -1775,14 +1757,6 @@ static ZEND_COLD void zend_ast_export_encaps_list(smart_str *str, char quote, co ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING); zend_ast_export_qstr(str, quote, Z_STR_P(zv)); - } else if (ast->kind == ZEND_AST_VAR && - ast->child[0]->kind == ZEND_AST_ZVAL && - (i + 1 == list->children || - list->child[i + 1]->kind != ZEND_AST_ZVAL || - !zend_ast_var_needs_braces( - *Z_STRVAL_P( - zend_ast_get_zval(list->child[i + 1]))))) { - zend_ast_export_ex(str, ast, 0, indent); } else { smart_str_appendc(str, '{'); zend_ast_export_ex(str, ast, 0, indent); diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c index 5aa8d246177e..42491bf21041 100644 --- a/ext/openssl/openssl_backend_common.c +++ b/ext/openssl/openssl_backend_common.c @@ -1810,6 +1810,7 @@ zend_result php_openssl_cipher_update(const EVP_CIPHER *cipher_type, const char *aad, size_t aad_len, int enc) { int i = 0; + size_t outlen = data_len + EVP_CIPHER_block_size(cipher_type); /* For AEAD modes that do not support vector AAD, treat NULL AAD as zero-length AAD */ if (!mode->aad_supports_vector && aad == NULL) { @@ -1831,7 +1832,19 @@ zend_result php_openssl_cipher_update(const EVP_CIPHER *cipher_type, return FAILURE; } - *poutbuf = zend_string_alloc((int)data_len + EVP_CIPHER_block_size(cipher_type), 0); +#ifdef EVP_CIPH_WRAP_MODE + if ((EVP_CIPHER_mode(cipher_type)) == EVP_CIPH_WRAP_MODE) { + /* + * RFC 5649 wrap-with-padding rounds the input up to the block size + * and prepends an integrity block, we reserve one extra block. + * See EVP_EncryptUpdate(3): wrap mode may write up to + * inl + cipher_block_size bytes. + */ + outlen += EVP_CIPHER_block_size(cipher_type); + } +#endif + + *poutbuf = zend_string_alloc(outlen, false); if (!EVP_CipherUpdate(cipher_ctx, (unsigned char*)ZSTR_VAL(*poutbuf), &i, (const unsigned char *)data, (int)data_len)) { diff --git a/ext/openssl/tests/gh22186.phpt b/ext/openssl/tests/gh22186.phpt new file mode 100644 index 000000000000..8f28e6c45b58 --- /dev/null +++ b/ext/openssl/tests/gh22186.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-22186 (Heap buffer overflow in openssl_encrypt with AES-WRAP-PAD) +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + +--EXPECT-- +done diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 2477b371ea4e..0610e79196f9 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -563,6 +563,7 @@ PHP_MINIT_FUNCTION(spl) PHP_RINIT_FUNCTION(spl) /* {{{ */ { spl_autoload_extensions = NULL; + spl_object_storage_reset_get_hash_depth(); return SUCCESS; } /* }}} */ diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index f897ab1350cc..613bf5384dcb 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -47,6 +47,11 @@ static zend_object_handlers spl_handler_MultipleIterator; ZEND_TLS uint32_t spl_object_storage_get_hash_depth; +void spl_object_storage_reset_get_hash_depth(void) +{ + spl_object_storage_get_hash_depth = 0; +} + typedef struct _spl_SplObjectStorage { /* {{{ */ HashTable storage; zend_long index; diff --git a/ext/spl/spl_observer.h b/ext/spl/spl_observer.h index 08d3126d9c8b..bbb3ed656ccb 100644 --- a/ext/spl/spl_observer.h +++ b/ext/spl/spl_observer.h @@ -31,4 +31,6 @@ extern PHPAPI zend_class_entry *spl_ce_MultipleIterator; PHP_MINIT_FUNCTION(spl_observer); +void spl_object_storage_reset_get_hash_depth(void); + #endif /* SPL_OBSERVER_H */ diff --git a/ext/spl/tests/spl_object_storage_gethash_bailout.phpt b/ext/spl/tests/spl_object_storage_gethash_bailout.phpt new file mode 100644 index 000000000000..a6327bd62095 --- /dev/null +++ b/ext/spl/tests/spl_object_storage_gethash_bailout.phpt @@ -0,0 +1,44 @@ +--TEST-- +SplObjectStorage getHash() depth counter is reset after a bailout in a user getHash() +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- +offsetSet(new stdClass()); + echo "poison"; +} else { + $s = new SplObjectStorage(); + $s->offsetSet(new stdClass()); + echo "check-ok count=", count($s); +} +PHP; + +php_cli_server_start($code, 'router.php'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS; +// Request 1 bails out (OOM) inside the overridden getHash() mid-offsetSet. +@file_get_contents($base . '/poison'); +// A later request on the same worker must not be poisoned by a stuck counter. +echo @file_get_contents($base . '/check'), "\n"; +echo @file_get_contents($base . '/check'), "\n"; +?> +--EXPECT-- +check-ok count=1 +check-ok count=1 diff --git a/ext/standard/head.c b/ext/standard/head.c index 03f2e6189ee5..773b6919b780 100644 --- a/ext/standard/head.c +++ b/ext/standard/head.c @@ -218,14 +218,23 @@ static zend_result php_head_parse_cookie_options_array(HashTable *options, zend_ if (zend_string_equals_literal_ci(key, "expires")) { *expires = zval_get_long(value); } else if (zend_string_equals_literal_ci(key, "path")) { + if (*path) { + zend_string_release(*path); + } *path = zval_get_string(value); } else if (zend_string_equals_literal_ci(key, "domain")) { + if (*domain) { + zend_string_release(*domain); + } *domain = zval_get_string(value); } else if (zend_string_equals_literal_ci(key, "secure")) { *secure = zend_is_true(value); } else if (zend_string_equals_literal_ci(key, "httponly")) { *httponly = zend_is_true(value); } else if (zend_string_equals_literal_ci(key, "samesite")) { + if (*samesite) { + zend_string_release(*samesite); + } *samesite = zval_get_string(value); } else if (zend_string_equals_literal_ci(key, "partitioned")) { *partitioned = zend_is_true(value); diff --git a/ext/standard/tests/assert/gh22291.phpt b/ext/standard/tests/assert/gh22291.phpt new file mode 100644 index 000000000000..a50bd5861f05 --- /dev/null +++ b/ext/standard/tests/assert/gh22291.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-22291: AST pretty printing does not correctly handle braces in string interpolation +--FILE-- +getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +string(5) "{abc}" +string(3) "abc" +assert(!"{{$foo}}") diff --git a/ext/standard/tests/network/setcookie_option_case_variant_leak.phpt b/ext/standard/tests/network/setcookie_option_case_variant_leak.phpt new file mode 100644 index 000000000000..4797d2f259c9 --- /dev/null +++ b/ext/standard/tests/network/setcookie_option_case_variant_leak.phpt @@ -0,0 +1,15 @@ +--TEST-- +setcookie() does not leak when an option array has case-variant duplicate keys +--FILE-- + '/aaaaaaaaaaaaaaaa' . $i, 'Path' => '/bbbbbbbbbbbbbbbb' . $i]); + header_remove(); +} +// Each duplicate-key call leaked the first path string before the fix, +// growing usage by tens of bytes per iteration (hundreds of KB here). +var_dump(memory_get_usage() - $base < 50000); +?> +--EXPECT-- +bool(true)