Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions Zend/tests/assert/expect_015.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/assert/expect_020.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 0 additions & 26 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion ext/openssl/openssl_backend_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)) {
Expand Down
32 changes: 32 additions & 0 deletions ext/openssl/tests/gh22186.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
GH-22186 (Heap buffer overflow in openssl_encrypt with AES-WRAP-PAD)
--EXTENSIONS--
openssl
--SKIPIF--
<?php
/* openssl_get_cipher_methods() enumerates provider ciphers, but openssl_encrypt()
* resolves names via the legacy EVP_get_cipherbyname(), so on some builds the
* cipher is listed yet not usable. Probe the actual call path instead. */
if (!@openssl_encrypt("test", "aes-128-wrap-pad", str_repeat("k", 16),
OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY, str_repeat("\0", 4))) {
die('skip aes-128-wrap-pad not usable on this OpenSSL build');
}
?>
--FILE--
<?php
$pass = str_repeat("k", 16);
$iv = str_repeat("\0", 4);

for ($i = 1; $i < 258; $i++) {
$data = str_repeat("a", $i);
$enc = openssl_encrypt($data, 'aes-128-wrap-pad', $pass, OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY, $iv);
$dec = openssl_decrypt($enc, 'aes-128-wrap-pad', $pass, OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY, $iv);
if ($dec !== $data) {
die("mismatch at $i\n");
}
}

echo "done\n";
?>
--EXPECT--
done
1 change: 1 addition & 0 deletions ext/spl/php_spl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
} /* }}} */

Expand Down
5 changes: 5 additions & 0 deletions ext/spl/spl_observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions ext/spl/spl_observer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
44 changes: 44 additions & 0 deletions ext/spl/tests/spl_object_storage_gethash_bailout.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
SplObjectStorage getHash() depth counter is reset after a bailout in a user getHash()
--SKIPIF--
<?php
if (!file_exists(__DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc")) {
die("skip sapi/cli/tests/php_cli_server.inc required but not found");
}
?>
--INI--
allow_url_fopen=1
--FILE--
<?php
include __DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc";

$code = <<<'PHP'
if ($_SERVER["REQUEST_URI"] === "/poison") {
class Poison extends SplObjectStorage {
public function getHash($o): string {
ini_set("memory_limit", "2M");
str_repeat("a", 100 * 1024 * 1024);
return "x";
}
}
(new Poison())->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
9 changes: 9 additions & 0 deletions ext/standard/head.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions ext/standard/tests/assert/gh22291.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
GH-22291: AST pretty printing does not correctly handle braces in string interpolation
--FILE--
<?php

try {
$foo = 'abc';
var_dump("{{$foo}}");
var_dump("{$foo}");
assert(!"{{$foo}}");
} catch (Error $e) {
echo $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
string(5) "{abc}"
string(3) "abc"
assert(!"{{$foo}}")
15 changes: 15 additions & 0 deletions ext/standard/tests/network/setcookie_option_case_variant_leak.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
setcookie() does not leak when an option array has case-variant duplicate keys
--FILE--
<?php
$base = memory_get_usage();
for ($i = 0; $i < 5000; $i++) {
@setcookie('name', 'value', ['path' => '/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)
Loading