File CVE-2024-22019.patch of Package nodejs14

Index: node-v14.21.3/deps/llhttp/include/llhttp.h
===================================================================
--- node-v14.21.3.orig/deps/llhttp/include/llhttp.h
+++ node-v14.21.3/deps/llhttp/include/llhttp.h
@@ -255,6 +255,10 @@ 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;
 
   /* Possible return values 0, -1, `HPE_PAUSED` */
Index: node-v14.21.3/deps/llhttp/src/api.c
===================================================================
--- node-v14.21.3.orig/deps/llhttp/src/api.c
+++ node-v14.21.3/deps/llhttp/src/api.c
@@ -15,6 +15,21 @@
     err = settings->NAME(__VA_ARGS__);                                        \
   } while (0)
 
+#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN)                         \
+  do {                                                                        \
+    const llhttp_settings_t* settings;                                        \
+    settings = (const llhttp_settings_t*) (PARSER)->settings;                 \
+    if (settings == NULL || settings->NAME == NULL) {                         \
+      err = 0;                                                                \
+      break;                                                                  \
+    }                                                                         \
+    err = settings->NAME((PARSER), (START), (LEN));                           \
+    if (err == -1) {                                                          \
+      err = HPE_USER;                                                         \
+      llhttp_set_error_reason((PARSER), "Span callback error in " #NAME);     \
+    }                                                                         \
+  } while (0)
+
 void llhttp_init(llhttp_t* parser, llhttp_type_t type,
                  const llhttp_settings_t* settings) {
   llhttp__internal_init(parser);
@@ -201,6 +216,13 @@ int llhttp__on_chunk_header(llhttp_t* s,
   return err;
 }
 
+
+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;
Index: node-v14.21.3/deps/llhttp/src/llhttp.c
===================================================================
--- node-v14.21.3.orig/deps/llhttp/src/llhttp.c
+++ node-v14.21.3/deps/llhttp/src/llhttp.c
@@ -307,6 +307,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,
@@ -482,6 +484,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);
@@ -1118,8 +1124,7 @@ static llparse_state_t llhttp__internal_
           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;
@@ -1128,6 +1133,34 @@ static llparse_state_t llhttp__internal_
       /* 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) {
@@ -1138,13 +1171,9 @@ static llparse_state_t llhttp__internal_
           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;
@@ -5449,6 +5478,24 @@ static llparse_state_t llhttp__internal_
     /* 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";
@@ -7414,6 +7461,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,
@@ -7584,6 +7633,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);
@@ -8185,8 +8238,7 @@ static llparse_state_t llhttp__internal_
           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;
@@ -8195,6 +8247,34 @@ static llparse_state_t llhttp__internal_
       /* 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) {
@@ -8205,13 +8285,9 @@ static llparse_state_t llhttp__internal_
           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;
@@ -12312,6 +12388,24 @@ static llparse_state_t llhttp__internal_
     /* 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";
Index: node-v14.21.3/doc/api/errors.md
===================================================================
--- node-v14.21.3.orig/doc/api/errors.md
+++ node-v14.21.3/doc/api/errors.md
@@ -2326,6 +2326,18 @@ malconfigured clients, if more than 8KB
 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`
 
Index: node-v14.21.3/test/parallel/test-http-chunk-extensions-limit.js
===================================================================
--- /dev/null
+++ node-v14.21.3/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'
+    );
+  });
+}
Index: node-v14.21.3/lib/_http_server.js
===================================================================
--- node-v14.21.3.orig/lib/_http_server.js
+++ node-v14.21.3/lib/_http_server.js
@@ -636,6 +636,11 @@ const requestHeaderFieldsTooLargeRespons
   `HTTP/1.1 431 ${STATUS_CODES[431]}${CRLF}` +
   `Connection: close${CRLF}${CRLF}`, '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);
@@ -649,6 +654,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;
Index: node-v14.21.3/src/node_http_parser.cc
===================================================================
--- node-v14.21.3.orig/src/node_http_parser.cc
+++ node-v14.21.3/src/node_http_parser.cc
@@ -78,6 +78,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;
 
 inline bool IsOWS(char c) {
   return c == ' ' || c == '\t';
@@ -202,6 +204,7 @@ class Parser : public AsyncWrap, public
 
   int on_message_begin() {
     num_fields_ = num_values_ = 0;
+    chunk_extensions_nread_ = 0;
     url_.Reset();
     status_message_.Reset();
     header_parsing_start_time_ = uv_hrtime();
@@ -451,9 +454,22 @@ class Parser : public AsyncWrap, public
     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;
   }
 
@@ -904,6 +920,7 @@ class Parser : public AsyncWrap, public
   unsigned int execute_depth_ = 0;
   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;
@@ -938,6 +955,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,
openSUSE Build Service is sponsored by