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/soap/soap.c b/ext/soap/soap.c
index 8a103e09636d..83584283740d 100644
--- a/ext/soap/soap.c
+++ b/ext/soap/soap.c
@@ -1414,7 +1414,7 @@ PHP_METHOD(SoapServer, handle)
}
}
- if ((soap_action_z = zend_hash_str_find(Z_ARRVAL_P(server_vars), ZEND_STRL("HTTP_SOAPACTION"))) != NULL && Z_TYPE_P(soap_action_z) == IS_STRING) {
+ if ((soap_action_z = zend_hash_str_find(Z_ARRVAL_P(server_vars), ZEND_STRL("HTTP_SOAPACTION"))) != NULL && Z_TYPE_P(soap_action_z) == IS_STRING && Z_STRLEN_P(soap_action_z) > 0) {
soap_action = Z_STRVAL_P(soap_action_z);
}
}
@@ -3179,6 +3179,10 @@ static sdlFunctionPtr find_function_using_soap_action(const sdl *sdl, const char
soap_action_length -= 2;
}
+ if (UNEXPECTED(soap_action_length == 0)) {
+ return NULL;
+ }
+
/* TODO: This may depend on a particular target namespace, in which case this won't find a match when multiple different
* target namespaces are used until #45282 is resolved. */
sdlFunctionPtr function;
diff --git a/ext/soap/tests/bugs/gh22285.phpt b/ext/soap/tests/bugs/gh22285.phpt
new file mode 100644
index 000000000000..8c7e0933588e
--- /dev/null
+++ b/ext/soap/tests/bugs/gh22285.phpt
@@ -0,0 +1,45 @@
+--TEST--
+GH-22285 (SoapServer dispatches to the first function when the SOAPAction header is empty)
+--CREDITS--
+Jarkko Hyvärinen
+--EXTENSIONS--
+soap
+--INI--
+soap.wsdl_cache_enabled=0
+--SKIPIF--
+
+--POST--
+
+
+
+ World
+
+
+
+--FILE--
+ 'Hello ' . $params->name];
+ }
+ public function goodbye($params) {
+ return ['message' => 'Goodbye ' . $params->name];
+ }
+}
+
+$server = new SoapServer(__DIR__ . '/gh22285.wsdl', [
+ 'cache_wsdl' => WSDL_CACHE_NONE,
+ 'encoding' => 'UTF-8',
+ 'soap_version' => SOAP_1_1,
+]);
+$server->setClass('TestWS');
+$_SERVER['HTTP_SOAPACTION'] = '""';
+$server->handle();
+?>
+--EXPECTF--
+
+Goodbye World
diff --git a/ext/soap/tests/bugs/gh22285.wsdl b/ext/soap/tests/bugs/gh22285.wsdl
new file mode 100644
index 000000000000..66b9e6749071
--- /dev/null
+++ b/ext/soap/tests/bugs/gh22285.wsdl
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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)