|
| 1 | +From: Niels Dossche <7771979+ndossche@users.noreply.github.com> |
| 2 | +Date: Tue, 25 Nov 2025 23:11:38 +0100 |
| 3 | +Subject: Fix GH-20584: Information Leak of Memory |
| 4 | + |
| 5 | +The string added had uninitialized memory due to |
| 6 | +php_read_stream_all_chunks() not moving the buffer position, resulting |
| 7 | +in the same data always being overwritten instead of new data being |
| 8 | +added to the end of the buffer. |
| 9 | + |
| 10 | +This is backport as there is a security impact as described in |
| 11 | +GHSA-3237-qqm7-mfv7 . |
| 12 | + |
| 13 | +(cherry picked from commit c5f28c7cf0a052f48e47877c7aa5c5bcc54f1cfc) |
| 14 | +(cherry picked from commit ed665eb1903737d2b52b27368b155f6208604ed9) |
| 15 | +--- |
| 16 | + ext/standard/image.c | 20 +++++++++++++++--- |
| 17 | + ext/standard/tests/image/gh20584.phpt | 39 +++++++++++++++++++++++++++++++++++ |
| 18 | + 2 files changed, 56 insertions(+), 3 deletions(-) |
| 19 | + create mode 100644 ext/standard/tests/image/gh20584.phpt |
| 20 | + |
| 21 | +diff --git a/ext/standard/image.c b/ext/standard/image.c |
| 22 | +index fc4d6bd..06f0a00 100644 |
| 23 | +--- a/ext/standard/image.c |
| 24 | ++++ b/ext/standard/image.c |
| 25 | +@@ -434,8 +434,22 @@ static int php_skip_variable(php_stream * stream) |
| 26 | + } |
| 27 | + /* }}} */ |
| 28 | + |
| 29 | +-/* {{{ php_read_APP |
| 30 | +- */ |
| 31 | ++static size_t php_read_stream_all_chunks(php_stream *stream, char *buffer, size_t length) |
| 32 | ++{ |
| 33 | ++ size_t read_total = 0; |
| 34 | ++ do { |
| 35 | ++ ssize_t read_now = php_stream_read(stream, buffer, length - read_total); |
| 36 | ++ read_total += read_now; |
| 37 | ++ if (read_now < stream->chunk_size && read_total != length) { |
| 38 | ++ return 0; |
| 39 | ++ } |
| 40 | ++ buffer += read_now; |
| 41 | ++ } while (read_total < length); |
| 42 | ++ |
| 43 | ++ return read_total; |
| 44 | ++} |
| 45 | ++ |
| 46 | ++/* {{{ php_read_APP */ |
| 47 | + static int php_read_APP(php_stream * stream, unsigned int marker, zval *info) |
| 48 | + { |
| 49 | + unsigned short length; |
| 50 | +@@ -451,7 +465,7 @@ static int php_read_APP(php_stream * stream, unsigned int marker, zval *info) |
| 51 | + |
| 52 | + buffer = emalloc(length); |
| 53 | + |
| 54 | +- if (php_stream_read(stream, buffer, (zend_long) length) != length) { |
| 55 | ++ if (php_read_stream_all_chunks(stream, buffer, length) != length) { |
| 56 | + efree(buffer); |
| 57 | + return 0; |
| 58 | + } |
| 59 | +diff --git a/ext/standard/tests/image/gh20584.phpt b/ext/standard/tests/image/gh20584.phpt |
| 60 | +new file mode 100644 |
| 61 | +index 0000000..d117f21 |
| 62 | +--- /dev/null |
| 63 | ++++ b/ext/standard/tests/image/gh20584.phpt |
| 64 | +@@ -0,0 +1,39 @@ |
| 65 | ++--TEST-- |
| 66 | ++GH-20584 (Information Leak of Memory) |
| 67 | ++--CREDITS-- |
| 68 | ++Nikita Sveshnikov (Positive Technologies) |
| 69 | ++--FILE-- |
| 70 | ++<?php |
| 71 | ++// Minimal PoC: corruption/uninitialized memory leak when reading APP1 via php://filter |
| 72 | ++$file = __DIR__ . '/gh20584.jpg'; |
| 73 | ++ |
| 74 | ++// Make APP1 large enough so it is read in multiple chunks |
| 75 | ++$chunk = 8192; |
| 76 | ++$tail = 123; |
| 77 | ++$payload = str_repeat('A', $chunk) . str_repeat('B', $chunk) . str_repeat('Z', |
| 78 | ++$tail); |
| 79 | ++$app1Len = 2 + strlen($payload); |
| 80 | ++ |
| 81 | ++// Minimal JPEG: SOI + APP1 + SOF0(1x1) + EOI |
| 82 | ++$sof = "\xFF\xC0" . pack('n', 11) . "\x08" . pack('n',1) . pack('n',1) . |
| 83 | ++"\x01\x11\x00"; |
| 84 | ++$jpeg = "\xFF\xD8" . "\xFF\xE1" . pack('n', $app1Len) . $payload . $sof . |
| 85 | ++"\xFF\xD9"; |
| 86 | ++file_put_contents($file, $jpeg); |
| 87 | ++ |
| 88 | ++// Read through a filter to enforce multiple reads |
| 89 | ++$src = 'php://filter/read=string.rot13|string.rot13/resource=' . $file; |
| 90 | ++$info = null; |
| 91 | ++@getimagesize($src, $info); |
| 92 | ++$exp = $payload; |
| 93 | ++$ret = $info['APP1']; |
| 94 | ++ |
| 95 | ++var_dump($ret === $exp); |
| 96 | ++ |
| 97 | ++?> |
| 98 | ++--CLEAN-- |
| 99 | ++<?php |
| 100 | ++@unlink(__DIR__ . '/gh20584.jpg'); |
| 101 | ++?> |
| 102 | ++--EXPECT-- |
| 103 | ++bool(true) |
0 commit comments