]> granicus.if.org Git - php/commitdiff
Fix #80384: limit read buffer size
authorAdam Seitz <adamjseitz@gmail.com>
Tue, 1 Dec 2020 23:40:16 +0000 (00:40 +0100)
committerChristoph M. Becker <cmbecker69@gmx.de>
Wed, 23 Dec 2020 12:49:56 +0000 (13:49 +0100)
In the case of a stream with no filters, php_stream_fill_read_buffer
only reads stream->chunk_size into the read buffer. If the stream has
filters attached, it could unnecessarily buffer a large amount of data.

With this change, php_stream_fill_read_buffer only proceeds until either
the requested size or stream->chunk_size is available in the read buffer.

Co-authored-by: Christoph M. Becker <cmbecker69@gmx.de>
Closes GH-6444.

NEWS
ext/standard/tests/streams/bug79984.phpt
main/streams/streams.c
tests/basic/bug80384.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 26554ff9ca27476e2df9302c44f14b09331ec2a4..03610ab9c50041d6cd9988fcb3d6834c9e1cff3b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,8 @@ PHP                                                                        NEWS
 
 - Core:
   . Fixed bug #80523 (bogus parse error on >4GB source code). (Nikita)
+  . Fixed bug #80384 (filter buffers entire read until file closed). (Adam
+    Seitz, cmb)
 
 - Date:
   . Fixed bug #80376 (last day of the month causes runway cpu usage. (Derick)
index 7126458ffff876042746a99fb68164f4971afc63..3a7eca091a9702fc4c1a4da5356af4156fdeec91 100644 (file)
@@ -52,6 +52,6 @@ fclose($f2);
 --EXPECT--
 filter onCreate
 filtered 8192 bytes.
-filtered 128 bytes and closing.
+filtered 128 bytes and closing. Stream has reached end-of-file.
 int(8320)
 filter onClose
index ab413872e0bd41a5a22ad474ea1265669ff96adf..5f6bf88aa989a065eaeb25043579f1e517bedbad 100644 (file)
@@ -542,6 +542,7 @@ PHPAPI int _php_stream_fill_read_buffer(php_stream *stream, size_t size)
        /* allocate/fill the buffer */
 
        if (stream->readfilters.head) {
+               size_t to_read_now = MIN(size, stream->chunk_size);
                char *chunk_buf;
                php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
                php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
@@ -549,7 +550,7 @@ PHPAPI int _php_stream_fill_read_buffer(php_stream *stream, size_t size)
                /* allocate a buffer for reading chunks */
                chunk_buf = emalloc(stream->chunk_size);
 
-               while (!stream->eof && (stream->writepos - stream->readpos < (zend_off_t)size)) {
+               while (!stream->eof && (stream->writepos - stream->readpos < (zend_off_t)to_read_now)) {
                        ssize_t justread = 0;
                        int flags;
                        php_stream_bucket *bucket;
diff --git a/tests/basic/bug80384.phpt b/tests/basic/bug80384.phpt
new file mode 100644 (file)
index 0000000..cf30e86
--- /dev/null
@@ -0,0 +1,28 @@
+--TEST--
+Bug #80384 large reads cause filters to internally buffer large amounts of memory
+--FILE--
+<?php
+/* First, create a file to read */
+$tmp_filename = __DIR__ . "/bug80384.tmp";
+$fp = fopen($tmp_filename, 'w');
+for ($i=0; $i<1024; $i++) {
+    fwrite($fp, str_repeat('ABCDEFGH', 1024));
+}
+fclose($fp);
+
+/* Stream the file through a filter */
+$fp = fopen($tmp_filename, 'r');
+$filter = stream_filter_append($fp, "string.rot13");
+
+$mem_start = memory_get_usage();
+fread($fp, 8 * 1024 * 1024);
+$mem_final = memory_get_usage();
+fclose($fp);
+var_dump($mem_final - $mem_start < 32768);
+?>
+--CLEAN--
+<?php
+unlink(__DIR__ . "/bug80384.tmp");
+?>
+--EXPECT--
+bool(true)