Skip to content

Fix GH-13264: fgets() and stream_get_line() do not return false on filter fatal error #18778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: PHP-8.3
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions ext/sqlite3/sqlite3.c
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
sqlite3_stream->position = sqlite3_stream->position + offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
} else {
Expand All @@ -1176,6 +1177,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
sqlite3_stream->position = sqlite3_stream->position + offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
}
Expand All @@ -1188,6 +1190,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
sqlite3_stream->position = offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
case SEEK_END:
Expand All @@ -1203,6 +1206,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
sqlite3_stream->position = sqlite3_stream->size + offset;
*newoffs = sqlite3_stream->position;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
default:
Expand Down
72 changes: 40 additions & 32 deletions ext/standard/tests/filters/gh13264.phpt
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
--TEST--
GH-81475: Memory leak during stream filter failure
GH-13264: fgets() and stream_get_line() do not return false on filter fatal error
--SKIPIF--
<?php require 'filter_errors.inc'; filter_errors_skipif('zlib.inflate'); ?>
--FILE--
<?php
// Prepare a big enough input so that it is not entirely buffered
$stream = fopen('php://memory', 'r+');
$content = '';
for ($i = 0; $i < 10000; $i++) {
$content .= "Hello $i\n";
}
fwrite($stream, gzcompress($content));
function create_stream()
{
// Prepare a big enough input so that it is not entirely buffered
$stream = fopen('php://memory', 'r+');
$content = '';
for ($i = 0; $i < 10000; $i++) {
$content .= "Hello $i\n";
}
fwrite($stream, gzcompress($content));

// Mess up the checksum
fseek($stream, -1, SEEK_CUR);
fwrite($stream, '1');

// Mess up the checksum
fseek($stream, -1, SEEK_CUR);
fwrite($stream, '1');
// Rewind and add the zlib filter
rewind($stream);
stream_filter_append($stream, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15]);

// Rewind and add the zlib filter
rewind($stream);
stream_filter_append($stream, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15]);
return $stream;
}

// Read the filtered stream line by line.
// Read the filtered stream line by line using fgets.
$stream = create_stream();
while (($line = fgets($stream)) !== false) {
$error = error_get_last();
if ($error !== null) {
// An error is thrown but fgets didn't return false
var_dump(error_get_last());
var_dump($line);
}
$error = error_get_last();
if ($error !== null) {
// An error is thrown but fgets didn't return false
var_dump(error_get_last());
var_dump($line);
}
}
fclose($stream);
error_clear_last();

// Read the filtered stream line by line using stream_get_line.
$stream = create_stream();
while (($line = stream_get_line($stream, 0, "\n")) !== false) {
$error = error_get_last();
if ($error !== null) {
// An error is thrown but fgets didn't return false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a minor copy-paste error in this comment:

Suggested change
// An error is thrown but fgets didn't return false
// An error is thrown but stream_get_line didn't return false

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will change it before the commit (sparing CI cycles for now :) )

var_dump(error_get_last());
var_dump($line);
}
}
fclose($stream);
?>
--EXPECTF--

Notice: fgets(): zlib: data error in %s on line %d
array(4) {
["type"]=>
int(8)
["message"]=>
string(25) "fgets(): zlib: data error"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
}
string(%d) "Hello%s"

Notice: stream_get_line(): zlib: data error in %s on line %d
3 changes: 3 additions & 0 deletions main/php_streams.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ struct _php_stream {
/* whether stdio cast flushing is in progress */
uint16_t fclose_stdiocast_flush_in_progress:1;

/* whether fatal error happened and all operations should terminates as soon as possible */
uint16_t fatal_error:1;

char mode[16]; /* "rwb" etc. ala stdio */

uint32_t flags; /* PHP_STREAM_FLAG_XXX */
Expand Down
5 changes: 5 additions & 0 deletions main/streams/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,12 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
ms->fpos = ms->fpos + offset;
*newoffs = ms->fpos;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
} else {
stream->eof = 0;
stream->fatal_error = 0;
ms->fpos = ms->fpos + offset;
*newoffs = ms->fpos;
return 0;
Expand All @@ -153,13 +155,15 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
ms->fpos = offset;
*newoffs = ms->fpos;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
case SEEK_END:
if (offset > 0) {
ms->fpos = ZSTR_LEN(ms->data) + offset;
*newoffs = ms->fpos;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
} else if (ZSTR_LEN(ms->data) < (size_t)(-offset)) {
ms->fpos = 0;
Expand All @@ -169,6 +173,7 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
ms->fpos = ZSTR_LEN(ms->data) + offset;
*newoffs = ms->fpos;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
default:
Expand Down
16 changes: 14 additions & 2 deletions main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ PHPAPI zend_result _php_stream_fill_read_buffer(php_stream *stream, size_t size)
/* some fatal error. Theoretically, the stream is borked, so all
* further reads should fail. */
stream->eof = 1;
stream->fatal_error = 1;
/* free all data left in brigades */
while ((bucket = brig_inp->head)) {
/* Remove unconsumed buckets from the input brigade */
Expand Down Expand Up @@ -1009,7 +1010,12 @@ PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen,
}
}

php_stream_fill_read_buffer(stream, toread);
if (php_stream_fill_read_buffer(stream, toread) == FAILURE && stream->fatal_error) {
if (grow_mode) {
efree(bufstart);
}
return NULL;
}

if (stream->writepos - stream->readpos == 0) {
break;
Expand Down Expand Up @@ -1084,7 +1090,9 @@ PHPAPI zend_string *php_stream_get_record(php_stream *stream, size_t maxlen, con

to_read_now = MIN(maxlen - buffered_len, stream->chunk_size);

php_stream_fill_read_buffer(stream, buffered_len + to_read_now);
if (php_stream_fill_read_buffer(stream, buffered_len + to_read_now) == FAILURE && stream->fatal_error) {
return NULL;
}

just_read = STREAM_BUFFERED_AMOUNT(stream) - buffered_len;

Expand Down Expand Up @@ -1357,6 +1365,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
stream->readpos += offset; /* if offset = ..., then readpos = writepos */
stream->position += offset;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
break;
Expand All @@ -1366,6 +1375,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
stream->readpos += offset - stream->position;
stream->position = offset;
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}
break;
Expand Down Expand Up @@ -1400,6 +1410,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) {
if (ret == 0) {
stream->eof = 0;
stream->fatal_error = 0;
}

/* invalidate the buffer contents */
Expand All @@ -1422,6 +1433,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
offset -= didread;
}
stream->eof = 0;
stream->fatal_error = 0;
return 0;
}

Expand Down
Loading