Skip to content

Commit daca078

Browse files
sam-githubtargos
authored andcommitted
http: llhttp opt-in insecure HTTP header parsing
Allow insecure HTTP header parsing. Make clear it is insecure. See: - #30553 - #27711 (comment) - #30515 PR-URL: #30567 Reviewed-By: Fedor Indutny <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 7e1dee3 commit daca078

8 files changed

+47
-4
lines changed

doc/api/cli.md

+11
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,16 @@ added: v9.0.0
419419
Specify the `module` of a custom [experimental ECMAScript Module loader][].
420420
`module` may be either a path to a file, or an ECMAScript Module name.
421421

422+
### `--insecure-http-parser`
423+
<!-- YAML
424+
added: REPLACEME
425+
-->
426+
427+
Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
428+
interoperability with non-conformant HTTP implementations. It may also allow
429+
request smuggling and other HTTP attacks that rely on invalid headers being
430+
accepted. Avoid using this option.
431+
422432
### `--max-http-header-size=size`
423433
<!-- YAML
424434
added: v11.6.0
@@ -1064,6 +1074,7 @@ Node.js options that are allowed are:
10641074
* `--http-parser`
10651075
* `--icu-data-dir`
10661076
* `--input-type`
1077+
* `--insecure-http-parser`
10671078
* `--inspect-brk`
10681079
* `--inspect-port`, `--debug-port`
10691080
* `--inspect-publish-uid`

doc/node.1

+6
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ Specify the
213213
.Ar module
214214
to use as a custom module loader.
215215
.
216+
.It Fl -insecure-http-parser
217+
Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
218+
interoperability with non-conformant HTTP implementations. It may also allow
219+
request smuggling and other HTTP attacks that rely on invalid headers being
220+
accepted. Avoid using this option.
221+
.
216222
.It Fl -max-http-header-size Ns = Ns Ar size
217223
Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
218224
.

lib/_http_client.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
freeParser,
4040
parsers,
4141
HTTPParser,
42+
isLenient,
4243
prepareError,
4344
} = require('_http_common');
4445
const { OutgoingMessage } = require('_http_outgoing');
@@ -669,7 +670,8 @@ function tickOnSocket(req, socket) {
669670
req.socket = socket;
670671
parser.initialize(HTTPParser.RESPONSE,
671672
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
672-
req.maxHeaderSize || 0);
673+
req.maxHeaderSize || 0,
674+
isLenient());
673675
parser.socket = socket;
674676
parser.outgoing = req;
675677
req.parser = parser;

lib/_http_common.js

+13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const {
2828
const { setImmediate } = require('timers');
2929

3030
const { methods, HTTPParser } = internalBinding('http_parser');
31+
const { getOptionValue } = require('internal/options');
32+
const insecureHTTPParser = getOptionValue('--insecure-http-parser');
3133

3234
const FreeList = require('internal/freelist');
3335
const incoming = require('_http_incoming');
@@ -237,6 +239,16 @@ function prepareError(err, parser, rawPacket) {
237239
err.message = `Parse Error: ${err.reason}`;
238240
}
239241

242+
let warnedLenient = false;
243+
244+
function isLenient() {
245+
if (insecureHTTPParser && !warnedLenient) {
246+
warnedLenient = true;
247+
process.emitWarning('Using insecure HTTP parsing');
248+
}
249+
return insecureHTTPParser;
250+
}
251+
240252
module.exports = {
241253
_checkInvalidHeaderChar: checkInvalidHeaderChar,
242254
_checkIsHttpToken: checkIsHttpToken,
@@ -249,5 +261,6 @@ module.exports = {
249261
parsers,
250262
kIncomingMessage,
251263
HTTPParser,
264+
isLenient,
252265
prepareError,
253266
};

lib/_http_server.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
chunkExpression,
4040
kIncomingMessage,
4141
HTTPParser,
42+
isLenient,
4243
_checkInvalidHeaderChar: checkInvalidHeaderChar,
4344
prepareError,
4445
} = require('_http_common');
@@ -410,7 +411,8 @@ function connectionListenerInternal(server, socket) {
410411
parser.initialize(
411412
HTTPParser.REQUEST,
412413
new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
413-
server.maxHeaderSize || 0
414+
server.maxHeaderSize || 0,
415+
isLenient(),
414416
);
415417
parser.socket = socket;
416418

src/node_http_parser.cc

+5-2
Original file line numberDiff line numberDiff line change
@@ -486,11 +486,13 @@ class Parser : public AsyncWrap, public StreamListener {
486486

487487
static void Initialize(const FunctionCallbackInfo<Value>& args) {
488488
Environment* env = Environment::GetCurrent(args);
489+
bool lenient = args[3]->IsTrue();
489490

490491
uint64_t max_http_header_size = 0;
491492

492493
CHECK(args[0]->IsInt32());
493494
CHECK(args[1]->IsObject());
495+
494496
if (args.Length() > 2) {
495497
CHECK(args[2]->IsNumber());
496498
max_http_header_size = args[2].As<Number>()->Value();
@@ -515,7 +517,7 @@ class Parser : public AsyncWrap, public StreamListener {
515517

516518
parser->set_provider_type(provider);
517519
parser->AsyncReset(args[1].As<Object>());
518-
parser->Init(type, max_http_header_size);
520+
parser->Init(type, max_http_header_size, lenient);
519521
}
520522

521523
template <bool should_pause>
@@ -762,8 +764,9 @@ class Parser : public AsyncWrap, public StreamListener {
762764
}
763765

764766

765-
void Init(llhttp_type_t type, uint64_t max_http_header_size) {
767+
void Init(llhttp_type_t type, uint64_t max_http_header_size, bool lenient) {
766768
llhttp_init(&parser_, type, &settings);
769+
llhttp_set_lenient(&parser_, lenient);
767770
header_nread_ = 0;
768771
url_.Reset();
769772
status_message_.Reset();

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
375375
&EnvironmentOptions::heap_snapshot_signal,
376376
kAllowedInEnvironment);
377377
AddOption("--http-parser", "", NoOp{}, kAllowedInEnvironment);
378+
AddOption("--insecure-http-parser",
379+
"use an insecure HTTP parser that accepts invalid HTTP headers",
380+
&EnvironmentOptions::insecure_http_parser,
381+
kAllowedInEnvironment);
378382
AddOption("--input-type",
379383
"set module type for string input",
380384
&EnvironmentOptions::module_type,

src/node_options.h

+2
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ class EnvironmentOptions : public Options {
158158
bool print_eval = false;
159159
bool force_repl = false;
160160

161+
bool insecure_http_parser = false;
162+
161163
bool tls_min_v1_0 = false;
162164
bool tls_min_v1_1 = false;
163165
bool tls_min_v1_2 = false;

0 commit comments

Comments
 (0)