Skip to content
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
6 changes: 5 additions & 1 deletion ext/soap/soap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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;
Expand Down
45 changes: 45 additions & 0 deletions ext/soap/tests/bugs/gh22285.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
if (php_sapi_name()=='cli') echo 'skip';
?>
--POST--
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="urn:TestService">
<SOAP-ENV:Body>
<ns1:goodbyeIn>
<name>World</name>
</ns1:goodbyeIn>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
--FILE--
<?php
class TestWS {
public function hello($params) {
return ['message' => '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--
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:TestService"><SOAP-ENV:Body><ns1:goodbyeOut><message>Goodbye World</message></ns1:goodbyeOut></SOAP-ENV:Body></SOAP-ENV:Envelope>
96 changes: 96 additions & 0 deletions ext/soap/tests/bugs/gh22285.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
name="TestService"
targetNamespace="urn:TestService"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="urn:TestService"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

<wsdl:types>
<xsd:schema targetNamespace="urn:TestService">
<xsd:element name="helloIn">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="helloOut">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="message" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="goodbyeIn">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="goodbyeOut">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="message" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>

<wsdl:message name="helloRequest">
<wsdl:part element="tns:helloIn" name="helloIn" />
</wsdl:message>
<wsdl:message name="helloResponse">
<wsdl:part element="tns:helloOut" name="helloOut" />
</wsdl:message>
<wsdl:message name="goodbyeRequest">
<wsdl:part element="tns:goodbyeIn" name="goodbyeIn" />
</wsdl:message>
<wsdl:message name="goodbyeResponse">
<wsdl:part element="tns:goodbyeOut" name="goodbyeOut" />
</wsdl:message>

<wsdl:portType name="TestServicePortType">
<wsdl:operation name="hello">
<wsdl:input message="tns:helloRequest" name="helloRequest" />
<wsdl:output message="tns:helloResponse" name="helloResponse" />
</wsdl:operation>
<wsdl:operation name="goodbye">
<wsdl:input message="tns:goodbyeRequest" name="goodbyeRequest" />
<wsdl:output message="tns:goodbyeResponse" name="goodbyeResponse" />
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="TestServiceBinding" type="tns:TestServicePortType">
<wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="hello">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="helloRequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="helloResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="goodbye">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="goodbyeRequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="goodbyeResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="TestService">
<wsdl:port name="TestServicePort" binding="tns:TestServiceBinding">
<wsdlsoap:address location="http://localhost/server.php" />
</wsdl:port>
</wsdl:service>

</wsdl:definitions>
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
Loading
Loading