From f81d7a8bcb20eb7dc874dc59acd4af90227285a9 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 19 Jul 2024 22:52:03 +0200 Subject: [PATCH 1/2] Fix GH-15034: Integer overflow on stream_notification_callback byte_max parameter with files bigger than 2GB We were using atoi, which is only for integers. When the size does not fit in an integer this breaks. Use ZEND_STRTOUL instead. Also make sure invalid data isn't accidentally parsed into a file size. --- ext/standard/http_fopen_wrapper.c | 14 +++++++-- ext/standard/tests/http/gh15034.phpt | 44 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 ext/standard/tests/http/gh15034.phpt diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index a9950fa6d48d2..983d6dc4b2cd4 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -791,8 +791,18 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) { php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0); } else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) { - file_size = atoi(http_header_value); - php_stream_notify_file_size(context, file_size, http_header_line, 0); + /* https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length */ + const char *ptr = http_header_value; + /* must contain only digits, no + or - symbols */ + if (*ptr >= '0' && *ptr <= '9') { + char* endptr = NULL; + size_t parsed = ZEND_STRTOUL(ptr, &endptr, 10); + /* check whether there was no garbage in the header value and the conversion was successful */ + if (endptr && !*endptr) { + file_size = parsed; + php_stream_notify_file_size(context, file_size, http_header_line, 0); + } + } } else if ( !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1) && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1) diff --git a/ext/standard/tests/http/gh15034.phpt b/ext/standard/tests/http/gh15034.phpt new file mode 100644 index 0000000000000..c0841ef954437 --- /dev/null +++ b/ext/standard/tests/http/gh15034.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-15034 (Integer overflow on stream_notification_callback byte_max parameter with files bigger than 2GB) +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--FILE-- + $pid, 'uri' => $uri] = http_server($responses); + +$params = ['notification' => function( + int $notification_code, + int $severity, + ?string $message, + int $message_code, + int $bytes_transferred, + int $bytes_max +) { + global $max; + $max = $bytes_max; +}]; +$contextResource = stream_context_create([], $params); + +$resource = fopen($uri, 'r', false, $contextResource); +fclose($resource); + +http_server_kill($pid); + +var_dump($max); +?> +--EXPECT-- +int(3000000000) From af507b4f0f58c53558cfc9b394a645134ee2237b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:25:27 +0200 Subject: [PATCH 2/2] truncate --- ext/standard/http_fopen_wrapper.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 983d6dc4b2cd4..dca176ad3f526 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -795,11 +795,12 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, const char *ptr = http_header_value; /* must contain only digits, no + or - symbols */ if (*ptr >= '0' && *ptr <= '9') { - char* endptr = NULL; + char *endptr = NULL; size_t parsed = ZEND_STRTOUL(ptr, &endptr, 10); /* check whether there was no garbage in the header value and the conversion was successful */ if (endptr && !*endptr) { - file_size = parsed; + /* truncate for 32-bit such that no negative file sizes occur */ + file_size = MIN(parsed, ZEND_LONG_MAX); php_stream_notify_file_size(context, file_size, http_header_line, 0); } }