mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Merge branch 'PHP-8.5'
* PHP-8.5: Fix GH-20370: forbid user stream filters to violate typed property constraints (#20373)
This commit is contained in:
2
NEWS
2
NEWS
@@ -50,6 +50,8 @@ PHP NEWS
|
||||
- Streams:
|
||||
. Added so_reuseaddr streams context socket option that allows disabling
|
||||
address resuse.
|
||||
. Fixed bug GH-20370 (User stream filters could violate typed property
|
||||
constraints). (alexandre-daubois)
|
||||
|
||||
- Zip:
|
||||
. Fixed ZipArchive callback being called after executor has shut down.
|
||||
|
||||
44
ext/standard/tests/filters/gh20370.phpt
Normal file
44
ext/standard/tests/filters/gh20370.phpt
Normal 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
|
||||
@@ -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)
|
||||
37
ext/standard/tests/filters/gh20370_no_stream_property.phpt
Normal file
37
ext/standard/tests/filters/gh20370_no_stream_property.phpt
Normal 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)
|
||||
@@ -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
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user