1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Fix GH-20370: forbid user stream filters to violate typed property constraints (#20373)
This commit is contained in:
Alexandre Daubois
2025-12-04 09:11:52 +01:00
6 changed files with 205 additions and 9 deletions

4
NEWS
View File

@@ -74,6 +74,10 @@ PHP NEWS
via deep structures). (ndossche)
. Fixed bug GH-20584 (Information Leak of Memory). (ndossche)
- Streams:
. Fixed bug GH-20370 (User stream filters could violate typed property
constraints). (alexandre-daubois)
- XML:
. Fixed bug GH-20439 (xml_set_default_handler() does not properly handle
special characters in attributes when passing data to callback). (ndossche)

View File

@@ -0,0 +1,44 @@
--TEST--
GH-20370 (User filters should respect typed properties)
--FILE--
<?php
class pass_filter
{
public $filtername;
public $params;
public int $stream = 1;
function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register("pass", "pass_filter");
$fp = fopen("php://memory", "w");
stream_filter_append($fp, "pass");
try {
fwrite($fp, "data");
} catch (TypeError $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
try {
fclose($fp);
} catch (TypeError $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
unset($fp); // prevent cleanup at shutdown
?>
--EXPECTF--
Warning: fwrite(): Unprocessed filter buckets remaining on input brigade in %s on line %d
TypeError: Cannot assign resource to property pass_filter::$stream of type int
TypeError: Cannot assign resource to property pass_filter::$stream of type int

View File

@@ -0,0 +1,51 @@
--TEST--
GH-20370 (User filters should update dynamic stream property if it exists)
--FILE--
<?php
#[\AllowDynamicProperties]
class pass_filter
{
public $filtername;
public $params;
function onCreate(): bool
{
$this->stream = null;
return true;
}
function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
var_dump(property_exists($this, 'stream'));
if (is_resource($this->stream)) {
var_dump(get_resource_type($this->stream));
}
return PSFS_PASS_ON;
}
}
stream_filter_register("pass", "pass_filter");
$fp = fopen("php://memory", "w");
stream_filter_append($fp, "pass");
fwrite($fp, "data");
rewind($fp);
echo fread($fp, 1024) . "\n";
?>
--EXPECTF--
bool(true)
string(6) "stream"
bool(true)
string(6) "stream"
bool(true)
string(6) "stream"
bool(true)
string(6) "stream"
data
bool(true)

View File

@@ -0,0 +1,37 @@
--TEST--
GH-20370 (User filters should not create stream property if not declared)
--FILE--
<?php
class pass_filter
{
public $filtername;
public $params;
function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
var_dump(property_exists($this, 'stream'));
return PSFS_PASS_ON;
}
}
stream_filter_register("pass", "pass_filter");
$fp = fopen("php://memory", "w");
stream_filter_append($fp, "pass");
fwrite($fp, "data");
rewind($fp);
echo fread($fp, 1024) . "\n";
?>
--EXPECT--
bool(false)
bool(false)
bool(false)
bool(false)
data
bool(false)

View File

@@ -0,0 +1,38 @@
--TEST--
GH-20370 (User filters should handle private stream property correctly)
--FILE--
<?php
class pass_filter
{
public $filtername;
public $params;
private $stream;
function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
function onClose()
{
var_dump($this->stream); // should be null
}
}
stream_filter_register("pass", "pass_filter");
$fp = fopen("php://memory", "w");
stream_filter_append($fp, "pass", STREAM_FILTER_WRITE);
fwrite($fp, "data");
rewind($fp);
echo fread($fp, 1024) . "\n";
?>
--EXPECT--
data
NULL

View File

@@ -148,14 +148,31 @@ static php_stream_filter_status_t userfilter_filter(
uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
zval *stream_prop = zend_hash_str_find_ind(Z_OBJPROP_P(obj), "stream", sizeof("stream")-1);
if (stream_prop) {
/* Give the userfilter class a hook back to the stream */
zval_ptr_dtor(stream_prop);
php_stream_to_zval(stream, stream_prop);
Z_ADDREF_P(stream_prop);
/* Give the userfilter class a hook back to the stream */
zend_class_entry *old_scope = EG(fake_scope);
EG(fake_scope) = Z_OBJCE_P(obj);
zend_string *stream_name = ZSTR_INIT_LITERAL("stream", 0);
bool stream_property_exists = Z_OBJ_HT_P(obj)->has_property(Z_OBJ_P(obj), stream_name, ZEND_PROPERTY_EXISTS, NULL);
if (stream_property_exists) {
zval stream_zval;
php_stream_to_zval(stream, &stream_zval);
zend_update_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), stream_name, &stream_zval);
/* If property update threw an exception, skip filter execution */
if (EG(exception)) {
EG(fake_scope) = old_scope;
if (buckets_in->head) {
php_error_docref(NULL, E_WARNING, "Unprocessed filter buckets remaining on input brigade");
}
zend_string_release(stream_name);
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
stream->flags |= orig_no_fclose;
return PSFS_ERR_FATAL;
}
}
EG(fake_scope) = old_scope;
ZVAL_STRINGL(&func_name, "filter", sizeof("filter")-1);
/* Setup calling arguments */
@@ -196,11 +213,16 @@ static php_stream_filter_status_t userfilter_filter(
/* filter resources are cleaned up by the stream destructor,
* keeping a reference to the stream resource here would prevent it
* from being destroyed properly */
if (stream_prop) {
convert_to_null(stream_prop);
* from being destroyed properly.
* Since the property accepted a resource assignment above, it must have
* no type hint or be typed as mixed, so we can safely assign null.
*/
if (stream_property_exists) {
zend_update_property_null(Z_OBJCE_P(obj), Z_OBJ_P(obj), ZSTR_VAL(stream_name), ZSTR_LEN(stream_name));
}
zend_string_release(stream_name);
zval_ptr_dtor(&args[3]);
zval_ptr_dtor(&args[2]);
zval_ptr_dtor(&args[1]);