aboutsummaryrefslogtreecommitdiffstats
path: root/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2024-22019.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-oe/recipes-devtools/nodejs/nodejs/CVE-2024-22019.patch')
-rw-r--r--meta-oe/recipes-devtools/nodejs/nodejs/CVE-2024-22019.patch556
1 files changed, 556 insertions, 0 deletions
diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2024-22019.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2024-22019.patch
new file mode 100644
index 0000000000..ca1c7981cc
--- /dev/null
+++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2024-22019.patch
@@ -0,0 +1,556 @@
+From 911cb33cdadab57a75f97186290ea8f3903a6171 Mon Sep 17 00:00:00 2001
+From: Paolo Insogna <paolo@cowtech.it>
+Date: Tue, 9 Jan 2024 18:10:04 +0100
+Subject: [PATCH] http: add maximum chunk extension size
+
+PR-URL: https://github.com/nodejs-private/node-private/pull/520
+Refs: https://github.com/nodejs-private/node-private/pull/518
+
+CVE-ID: CVE-2024-22019
+
+Upstream-Status: Backport [https://github.com/nodejs/node/commit/911cb33cdadab57a]
+
+Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
+---
+ deps/llhttp/CMakeLists.txt | 2 +-
+ deps/llhttp/include/llhttp.h | 7 +-
+ deps/llhttp/src/api.c | 7 +
+ deps/llhttp/src/llhttp.c | 122 ++++++++++++++--
+ doc/api/errors.md | 12 ++
+ lib/_http_server.js | 8 ++
+ src/node_http_parser.cc | 20 ++-
+ .../test-http-chunk-extensions-limit.js | 131 ++++++++++++++++++
+ tools/update-llhttp.sh | 2 +-
+ 9 files changed, 292 insertions(+), 19 deletions(-)
+ create mode 100644 test/parallel/test-http-chunk-extensions-limit.js
+
+diff --git a/deps/llhttp/CMakeLists.txt b/deps/llhttp/CMakeLists.txt
+index d038203..747564a 100644
+--- a/deps/llhttp/CMakeLists.txt
++++ b/deps/llhttp/CMakeLists.txt
+@@ -1,7 +1,7 @@
+ cmake_minimum_required(VERSION 3.5.1)
+ cmake_policy(SET CMP0069 NEW)
+
+-project(llhttp VERSION 6.0.11)
++project(llhttp VERSION 6.1.0)
+ include(GNUInstallDirs)
+
+ set(CMAKE_C_STANDARD 99)
+diff --git a/deps/llhttp/include/llhttp.h b/deps/llhttp/include/llhttp.h
+index 2da66f1..78f27ab 100644
+--- a/deps/llhttp/include/llhttp.h
++++ b/deps/llhttp/include/llhttp.h
+@@ -2,8 +2,8 @@
+ #define INCLUDE_LLHTTP_H_
+
+ #define LLHTTP_VERSION_MAJOR 6
+-#define LLHTTP_VERSION_MINOR 0
+-#define LLHTTP_VERSION_PATCH 11
++#define LLHTTP_VERSION_MINOR 1
++#define LLHTTP_VERSION_PATCH 0
+
+ #ifndef LLHTTP_STRICT_MODE
+ # define LLHTTP_STRICT_MODE 0
+@@ -348,6 +348,9 @@ struct llhttp_settings_s {
+ */
+ llhttp_cb on_headers_complete;
+
++ /* Possible return values 0, -1, HPE_USER */
++ llhttp_data_cb on_chunk_parameters;
++
+ /* Possible return values 0, -1, HPE_USER */
+ llhttp_data_cb on_body;
+
+diff --git a/deps/llhttp/src/api.c b/deps/llhttp/src/api.c
+index c4ce197..d3065b3 100644
+--- a/deps/llhttp/src/api.c
++++ b/deps/llhttp/src/api.c
+@@ -355,6 +355,13 @@ int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) {
+ }
+
+
++int llhttp__on_chunk_parameters(llhttp_t* s, const char* p, const char* endp) {
++ int err;
++ SPAN_CALLBACK_MAYBE(s, on_chunk_parameters, p, endp - p);
++ return err;
++}
++
++
+ int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) {
+ int err;
+ CALLBACK_MAYBE(s, on_chunk_complete);
+diff --git a/deps/llhttp/src/llhttp.c b/deps/llhttp/src/llhttp.c
+index 5e7c5d1..5eb19f6 100644
+--- a/deps/llhttp/src/llhttp.c
++++ b/deps/llhttp/src/llhttp.c
+@@ -340,6 +340,8 @@ enum llparse_state_e {
+ s_n_llhttp__internal__n_invoke_is_equal_content_length,
+ s_n_llhttp__internal__n_chunk_size_almost_done,
+ s_n_llhttp__internal__n_chunk_parameters,
++ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters,
++ s_n_llhttp__internal__n_chunk_parameters_ows,
+ s_n_llhttp__internal__n_chunk_size_otherwise,
+ s_n_llhttp__internal__n_chunk_size,
+ s_n_llhttp__internal__n_chunk_size_digit,
+@@ -539,6 +541,10 @@ int llhttp__on_body(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
++int llhttp__on_chunk_parameters(
++ llhttp__internal_t* s, const unsigned char* p,
++ const unsigned char* endp);
++
+ int llhttp__on_status(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+@@ -1226,8 +1232,7 @@ static llparse_state_t llhttp__internal__run(
+ goto s_n_llhttp__internal__n_chunk_parameters;
+ }
+ case 2: {
+- p++;
+- goto s_n_llhttp__internal__n_chunk_size_almost_done;
++ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_10;
+@@ -1236,6 +1241,34 @@ static llparse_state_t llhttp__internal__run(
+ /* UNREACHABLE */;
+ abort();
+ }
++ case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters:
++ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters: {
++ if (p == endp) {
++ return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
++ }
++ state->_span_pos0 = (void*) p;
++ state->_span_cb0 = llhttp__on_chunk_parameters;
++ goto s_n_llhttp__internal__n_chunk_parameters;
++ /* UNREACHABLE */;
++ abort();
++ }
++ case s_n_llhttp__internal__n_chunk_parameters_ows:
++ s_n_llhttp__internal__n_chunk_parameters_ows: {
++ if (p == endp) {
++ return s_n_llhttp__internal__n_chunk_parameters_ows;
++ }
++ switch (*p) {
++ case ' ': {
++ p++;
++ goto s_n_llhttp__internal__n_chunk_parameters_ows;
++ }
++ default: {
++ goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
++ }
++ }
++ /* UNREACHABLE */;
++ abort();
++ }
+ case s_n_llhttp__internal__n_chunk_size_otherwise:
+ s_n_llhttp__internal__n_chunk_size_otherwise: {
+ if (p == endp) {
+@@ -1246,13 +1279,9 @@ static llparse_state_t llhttp__internal__run(
+ p++;
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
+ }
+- case ' ': {
+- p++;
+- goto s_n_llhttp__internal__n_chunk_parameters;
+- }
+ case ';': {
+ p++;
+- goto s_n_llhttp__internal__n_chunk_parameters;
++ goto s_n_llhttp__internal__n_chunk_parameters_ows;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_11;
+@@ -6074,6 +6103,24 @@ static llparse_state_t llhttp__internal__run(
+ /* UNREACHABLE */;
+ abort();
+ }
++ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters: {
++ const unsigned char* start;
++ int err;
++
++ start = state->_span_pos0;
++ state->_span_pos0 = NULL;
++ err = llhttp__on_chunk_parameters(state, start, p);
++ if (err != 0) {
++ state->error = err;
++ state->error_pos = (const char*) (p + 1);
++ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
++ return s_error;
++ }
++ p++;
++ goto s_n_llhttp__internal__n_chunk_size_almost_done;
++ /* UNREACHABLE */;
++ abort();
++ }
+ s_n_llhttp__internal__n_error_10: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk parameters";
+@@ -8441,6 +8488,8 @@ enum llparse_state_e {
+ s_n_llhttp__internal__n_invoke_is_equal_content_length,
+ s_n_llhttp__internal__n_chunk_size_almost_done,
+ s_n_llhttp__internal__n_chunk_parameters,
++ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters,
++ s_n_llhttp__internal__n_chunk_parameters_ows,
+ s_n_llhttp__internal__n_chunk_size_otherwise,
+ s_n_llhttp__internal__n_chunk_size,
+ s_n_llhttp__internal__n_chunk_size_digit,
+@@ -8635,6 +8684,10 @@ int llhttp__on_body(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+
++int llhttp__on_chunk_parameters(
++ llhttp__internal_t* s, const unsigned char* p,
++ const unsigned char* endp);
++
+ int llhttp__on_status(
+ llhttp__internal_t* s, const unsigned char* p,
+ const unsigned char* endp);
+@@ -9299,8 +9352,7 @@ static llparse_state_t llhttp__internal__run(
+ goto s_n_llhttp__internal__n_chunk_parameters;
+ }
+ case 2: {
+- p++;
+- goto s_n_llhttp__internal__n_chunk_size_almost_done;
++ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_6;
+@@ -9309,6 +9361,34 @@ static llparse_state_t llhttp__internal__run(
+ /* UNREACHABLE */;
+ abort();
+ }
++ case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters:
++ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters: {
++ if (p == endp) {
++ return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
++ }
++ state->_span_pos0 = (void*) p;
++ state->_span_cb0 = llhttp__on_chunk_parameters;
++ goto s_n_llhttp__internal__n_chunk_parameters;
++ /* UNREACHABLE */;
++ abort();
++ }
++ case s_n_llhttp__internal__n_chunk_parameters_ows:
++ s_n_llhttp__internal__n_chunk_parameters_ows: {
++ if (p == endp) {
++ return s_n_llhttp__internal__n_chunk_parameters_ows;
++ }
++ switch (*p) {
++ case ' ': {
++ p++;
++ goto s_n_llhttp__internal__n_chunk_parameters_ows;
++ }
++ default: {
++ goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
++ }
++ }
++ /* UNREACHABLE */;
++ abort();
++ }
+ case s_n_llhttp__internal__n_chunk_size_otherwise:
+ s_n_llhttp__internal__n_chunk_size_otherwise: {
+ if (p == endp) {
+@@ -9319,13 +9399,9 @@ static llparse_state_t llhttp__internal__run(
+ p++;
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
+ }
+- case ' ': {
+- p++;
+- goto s_n_llhttp__internal__n_chunk_parameters;
+- }
+ case ';': {
+ p++;
+- goto s_n_llhttp__internal__n_chunk_parameters;
++ goto s_n_llhttp__internal__n_chunk_parameters_ows;
+ }
+ default: {
+ goto s_n_llhttp__internal__n_error_7;
+@@ -13951,6 +14027,24 @@ static llparse_state_t llhttp__internal__run(
+ /* UNREACHABLE */;
+ abort();
+ }
++ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters: {
++ const unsigned char* start;
++ int err;
++
++ start = state->_span_pos0;
++ state->_span_pos0 = NULL;
++ err = llhttp__on_chunk_parameters(state, start, p);
++ if (err != 0) {
++ state->error = err;
++ state->error_pos = (const char*) (p + 1);
++ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
++ return s_error;
++ }
++ p++;
++ goto s_n_llhttp__internal__n_chunk_size_almost_done;
++ /* UNREACHABLE */;
++ abort();
++ }
+ s_n_llhttp__internal__n_error_6: {
+ state->error = 0x2;
+ state->reason = "Invalid character in chunk parameters";
+diff --git a/doc/api/errors.md b/doc/api/errors.md
+index dcf8744..a76bfe5 100644
+--- a/doc/api/errors.md
++++ b/doc/api/errors.md
+@@ -3043,6 +3043,18 @@ malconfigured clients, if more than 8 KiB of HTTP header data is received then
+ HTTP parsing will abort without a request or response object being created, and
+ an `Error` with this code will be emitted.
+
++<a id="HPE_CHUNK_EXTENSIONS_OVERFLOW"></a>
++
++### `HPE_CHUNK_EXTENSIONS_OVERFLOW`
++
++<!-- YAML
++added: REPLACEME
++-->
++
++Too much data was received for a chunk extensions. In order to protect against
++malicious or malconfigured clients, if more than 16 KiB of data is received
++then an `Error` with this code will be emitted.
++
+ <a id="HPE_UNEXPECTED_CONTENT_LENGTH"></a>
+
+ ### `HPE_UNEXPECTED_CONTENT_LENGTH`
+diff --git a/lib/_http_server.js b/lib/_http_server.js
+index 4e23266..263bb52 100644
+--- a/lib/_http_server.js
++++ b/lib/_http_server.js
+@@ -706,6 +706,11 @@ const requestHeaderFieldsTooLargeResponse = Buffer.from(
+ `HTTP/1.1 431 ${STATUS_CODES[431]}\r\n` +
+ 'Connection: close\r\n\r\n', 'ascii'
+ );
++const requestChunkExtensionsTooLargeResponse = Buffer.from(
++ `HTTP/1.1 413 ${STATUS_CODES[413]}\r\n` +
++ 'Connection: close\r\n\r\n', 'ascii',
++);
++
+ function socketOnError(e) {
+ // Ignore further errors
+ this.removeListener('error', socketOnError);
+@@ -719,6 +724,9 @@ function socketOnError(e) {
+ case 'HPE_HEADER_OVERFLOW':
+ response = requestHeaderFieldsTooLargeResponse;
+ break;
++ case 'HPE_CHUNK_EXTENSIONS_OVERFLOW':
++ response = requestChunkExtensionsTooLargeResponse;
++ break;
+ case 'ERR_HTTP_REQUEST_TIMEOUT':
+ response = requestTimeoutResponse;
+ break;
+diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc
+index 74f3248..b92e848 100644
+--- a/src/node_http_parser.cc
++++ b/src/node_http_parser.cc
+@@ -79,6 +79,8 @@ const uint32_t kOnExecute = 5;
+ const uint32_t kOnTimeout = 6;
+ // Any more fields than this will be flushed into JS
+ const size_t kMaxHeaderFieldsCount = 32;
++// Maximum size of chunk extensions
++const size_t kMaxChunkExtensionsSize = 16384;
+
+ const uint32_t kLenientNone = 0;
+ const uint32_t kLenientHeaders = 1 << 0;
+@@ -206,6 +208,7 @@ class Parser : public AsyncWrap, public StreamListener {
+
+ int on_message_begin() {
+ num_fields_ = num_values_ = 0;
++ chunk_extensions_nread_ = 0;
+ url_.Reset();
+ status_message_.Reset();
+ header_parsing_start_time_ = uv_hrtime();
+@@ -443,9 +446,22 @@ class Parser : public AsyncWrap, public StreamListener {
+ return 0;
+ }
+
+- // Reset nread for the next chunk
++ int on_chunk_extension(const char* at, size_t length) {
++ chunk_extensions_nread_ += length;
++
++ if (chunk_extensions_nread_ > kMaxChunkExtensionsSize) {
++ llhttp_set_error_reason(&parser_,
++ "HPE_CHUNK_EXTENSIONS_OVERFLOW:Chunk extensions overflow");
++ return HPE_USER;
++ }
++
++ return 0;
++ }
++
++ // Reset nread for the next chunk and also reset the extensions counter
+ int on_chunk_header() {
+ header_nread_ = 0;
++ chunk_extensions_nread_ = 0;
+ return 0;
+ }
+
+@@ -887,6 +903,7 @@ class Parser : public AsyncWrap, public StreamListener {
+ const char* current_buffer_data_;
+ bool pending_pause_ = false;
+ uint64_t header_nread_ = 0;
++ uint64_t chunk_extensions_nread_ = 0;
+ uint64_t max_http_header_size_;
+ uint64_t headers_timeout_;
+ uint64_t header_parsing_start_time_ = 0;
+@@ -921,6 +938,7 @@ const llhttp_settings_t Parser::settings = {
+ Proxy<DataCall, &Parser::on_header_field>::Raw,
+ Proxy<DataCall, &Parser::on_header_value>::Raw,
+ Proxy<Call, &Parser::on_headers_complete>::Raw,
++ Proxy<DataCall, &Parser::on_chunk_extension>::Raw,
+ Proxy<DataCall, &Parser::on_body>::Raw,
+ Proxy<Call, &Parser::on_message_complete>::Raw,
+ Proxy<Call, &Parser::on_chunk_header>::Raw,
+diff --git a/test/parallel/test-http-chunk-extensions-limit.js b/test/parallel/test-http-chunk-extensions-limit.js
+new file mode 100644
+index 0000000..6868b3d
+--- /dev/null
++++ b/test/parallel/test-http-chunk-extensions-limit.js
+@@ -0,0 +1,131 @@
++'use strict';
++
++const common = require('../common');
++const http = require('http');
++const net = require('net');
++const assert = require('assert');
++
++// Verify that chunk extensions are limited in size when sent all together.
++{
++ const server = http.createServer((req, res) => {
++ req.on('end', () => {
++ res.writeHead(200, { 'Content-Type': 'text/plain' });
++ res.end('bye');
++ });
++
++ req.resume();
++ });
++
++ server.listen(0, () => {
++ const sock = net.connect(server.address().port);
++ let data = '';
++
++ sock.on('data', (chunk) => data += chunk.toString('utf-8'));
++
++ sock.on('end', common.mustCall(function() {
++ assert.strictEqual(data, 'HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n\r\n');
++ server.close();
++ }));
++
++ sock.end('' +
++ 'GET / HTTP/1.1\r\n' +
++ 'Host: localhost:8080\r\n' +
++ 'Transfer-Encoding: chunked\r\n\r\n' +
++ '2;' + 'A'.repeat(20000) + '=bar\r\nAA\r\n' +
++ '0\r\n\r\n'
++ );
++ });
++}
++
++// Verify that chunk extensions are limited in size when sent in intervals.
++{
++ const server = http.createServer((req, res) => {
++ req.on('end', () => {
++ res.writeHead(200, { 'Content-Type': 'text/plain' });
++ res.end('bye');
++ });
++
++ req.resume();
++ });
++
++ server.listen(0, () => {
++ const sock = net.connect(server.address().port);
++ let remaining = 20000;
++ let data = '';
++
++ const interval = setInterval(
++ () => {
++ if (remaining > 0) {
++ sock.write('A'.repeat(1000));
++ } else {
++ sock.write('=bar\r\nAA\r\n0\r\n\r\n');
++ clearInterval(interval);
++ }
++
++ remaining -= 1000;
++ },
++ common.platformTimeout(20),
++ ).unref();
++
++ sock.on('data', (chunk) => data += chunk.toString('utf-8'));
++
++ sock.on('end', common.mustCall(function() {
++ assert.strictEqual(data, 'HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n\r\n');
++ server.close();
++ }));
++
++ sock.write('' +
++ 'GET / HTTP/1.1\r\n' +
++ 'Host: localhost:8080\r\n' +
++ 'Transfer-Encoding: chunked\r\n\r\n' +
++ '2;'
++ );
++ });
++}
++
++// Verify the chunk extensions is correctly reset after a chunk
++{
++ const server = http.createServer((req, res) => {
++ req.on('end', () => {
++ res.writeHead(200, { 'content-type': 'text/plain', 'connection': 'close', 'date': 'now' });
++ res.end('bye');
++ });
++
++ req.resume();
++ });
++
++ server.listen(0, () => {
++ const sock = net.connect(server.address().port);
++ let data = '';
++
++ sock.on('data', (chunk) => data += chunk.toString('utf-8'));
++
++ sock.on('end', common.mustCall(function() {
++ assert.strictEqual(
++ data,
++ 'HTTP/1.1 200 OK\r\n' +
++ 'content-type: text/plain\r\n' +
++ 'connection: close\r\n' +
++ 'date: now\r\n' +
++ 'Transfer-Encoding: chunked\r\n' +
++ '\r\n' +
++ '3\r\n' +
++ 'bye\r\n' +
++ '0\r\n' +
++ '\r\n',
++ );
++
++ server.close();
++ }));
++
++ sock.end('' +
++ 'GET / HTTP/1.1\r\n' +
++ 'Host: localhost:8080\r\n' +
++ 'Transfer-Encoding: chunked\r\n\r\n' +
++ '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
++ '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
++ '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
++ '0\r\n\r\n'
++ );
++ });
++}
+diff --git a/tools/update-llhttp.sh b/tools/update-llhttp.sh
+index 12e2f46..a95eef1 100755
+--- a/tools/update-llhttp.sh
++++ b/tools/update-llhttp.sh
+@@ -59,5 +59,5 @@ echo ""
+ echo "Please git add llhttp, commit the new version:"
+ echo ""
+ echo "$ git add -A deps/llhttp"
+-echo "$ git commit -m \"deps: update nghttp2 to $LLHTTP_VERSION\""
++echo "$ git commit -m \"deps: update llhttp to $LLHTTP_VERSION\""
+ echo ""
+--
+2.40.0