[php-src] master: Merge branch 'PHP-8.5'

Ilia Alshanetsky <[email protected]> Sun, 19 Jul 2026 14:56:59 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-19T10:51:16-04:00

Commit: https://github.com/php/php-src/commit/c4612661aeec14f73463df6ade72c04f232182fc
Raw diff: https://github.com/php/php-src/commit/c4612661aeec14f73463df6ade72c04f232182fc.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  ext/standard: reject a dechunk chunk size that overflows size_t

Changed paths:
  A  ext/standard/tests/filters/dechunk_size_overflow.phpt
  M  ext/standard/filters.c


Diff:

diff --git a/ext/standard/filters.c b/ext/standard/filters.c
index c0741b46074e..1874ed183ceb 100644
--- a/ext/standard/filters.c
+++ b/ext/standard/filters.c
@@ -1810,12 +1810,14 @@ static size_t php_dechunk(char *buf, size_t len, php_chunked_filter_data *data)
 				data->chunk_size = 0;
 			case CHUNK_SIZE:
 				while (p < end) {
+					size_t digit;
+
 					if (*p >= '0' && *p <= '9') {
-						data->chunk_size = (data->chunk_size * 16) + (*p - '0');
+						digit = *p - '0';
 					} else if (*p >= 'A' && *p <= 'F') {
-						data->chunk_size = (data->chunk_size * 16) + (*p - 'A' + 10);
+						digit = *p - 'A' + 10;
 					} else if (*p >= 'a' && *p <= 'f') {
-						data->chunk_size = (data->chunk_size * 16) + (*p - 'a' + 10);
+						digit = *p - 'a' + 10;
 					} else if (data->state == CHUNK_SIZE_START) {
 						data->state = CHUNK_ERROR;
 						break;
@@ -1823,6 +1825,11 @@ static size_t php_dechunk(char *buf, size_t len, php_chunked_filter_data *data)
 						data->state = CHUNK_SIZE_EXT;
 						break;
 					}
+					if (data->chunk_size > (SIZE_MAX / 16)) {
+						data->state = CHUNK_ERROR;
+						break;
+					}
+					data->chunk_size = (data->chunk_size * 16) + digit;
 					data->state = CHUNK_SIZE;
 					p++;
 				}
diff --git a/ext/standard/tests/filters/dechunk_size_overflow.phpt b/ext/standard/tests/filters/dechunk_size_overflow.phpt
new file mode 100644
index 000000000000..ac42733e16e0
--- /dev/null
+++ b/ext/standard/tests/filters/dechunk_size_overflow.phpt
@@ -0,0 +1,38 @@
+--TEST--
+dechunk filter must reject a chunk size that overflows size_t
+--SKIPIF--
+<?php
+$filters = stream_get_filters();
+if(! in_array( "dechunk", $filters )) die( "skip Chunked filter not available." );
+?>
+--INI--
+allow_url_fopen=1
+--FILE--
+<?php
+/* Both sizes exceed SIZE_MAX, so parsing stops and the rest is passed through
+   raw. The guard trips at SIZE_MAX/16, so how many digits are consumed first
+   follows the width of size_t: %s covers the leftover run. Unguarded, the
+   first size wraps to 0, which reads as the terminating chunk and drops the
+   body; the second wraps to SIZE_MAX and swallows the rest as one chunk. */
+$streams = [
+    "data://text/plain,10000000000000000\nBODYDATA\n0\n",
+    "data://text/plain,fffffffffffffffff\nBODYDATA\n0\n",
+    "data://text/plain,5\nhello\n0\n",
+];
+foreach ($streams as $name) {
+    $fp = fopen($name, "r");
+    stream_filter_append($fp, "dechunk", STREAM_FILTER_READ);
+    var_dump(stream_get_contents($fp));
+    fclose($fp);
+}
+?>
+--EXPECTF--
+string(%d) "%s
+BODYDATA
+0
+"
+string(%d) "%s
+BODYDATA
+0
+"
+string(5) "hello"