Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v10.15.0 proposal #25176

Merged
merged 10 commits into from
Dec 26, 2018
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ release.
</tr>
<tr>
<td valign="top">
<b><a href="doc/changelogs/CHANGELOG_V10.md#10.14.2">10.14.2</a></b><br/>
<b><a href="doc/changelogs/CHANGELOG_V10.md#10.15.0">10.15.0</a></b><br/>
<a href="doc/changelogs/CHANGELOG_V10.md#10.14.2">10.14.2</a><br/>
<a href="doc/changelogs/CHANGELOG_V10.md#10.14.1">10.14.1</a><br/>
<a href="doc/changelogs/CHANGELOG_V10.md#10.14.0">10.14.0</a><br/>
<a href="doc/changelogs/CHANGELOG_V10.md#10.13.0">10.13.0</a><br/>
Expand Down
15 changes: 11 additions & 4 deletions deps/http_parser/http_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include <string.h>
#include <limits.h>

static uint32_t max_header_size = HTTP_MAX_HEADER_SIZE;

#ifndef ULLONG_MAX
# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
#endif
Expand Down Expand Up @@ -137,20 +139,20 @@ do { \
} while (0)

/* Don't allow the total size of the HTTP headers (including the status
* line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect
* line) to exceed max_header_size. This check is here to protect
* embedders against denial-of-service attacks where the attacker feeds
* us a never-ending header that the embedder keeps buffering.
*
* This check is arguably the responsibility of embedders but we're doing
* it on the embedder's behalf because most won't bother and this way we
* make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger
* make the web a little safer. max_header_size is still far bigger
* than any reasonable request or response so this should never affect
* day-to-day operation.
*/
#define COUNT_HEADER_SIZE(V) \
do { \
parser->nread += (V); \
if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \
if (UNLIKELY(parser->nread > max_header_size)) { \
SET_ERRNO(HPE_HEADER_OVERFLOW); \
goto error; \
} \
Expand Down Expand Up @@ -1471,7 +1473,7 @@ size_t http_parser_execute (http_parser *parser,
const char* p_lf;
size_t limit = data + len - p;

limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
limit = MIN(limit, max_header_size);

p_cr = (const char*) memchr(p, CR, limit);
p_lf = (const char*) memchr(p, LF, limit);
Expand Down Expand Up @@ -2437,3 +2439,8 @@ http_parser_version(void) {
HTTP_PARSER_VERSION_MINOR * 0x00100 |
HTTP_PARSER_VERSION_PATCH * 0x00001;
}

void
http_parser_set_max_header_size(uint32_t size) {
max_header_size = size;
}
3 changes: 3 additions & 0 deletions deps/http_parser/http_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,9 @@ void http_parser_pause(http_parser *parser, int paused);
/* Checks if this is the final chunk of the body. */
int http_body_is_final(const http_parser *parser);

/* Change the maximum header size provided at compile time. */
void http_parser_set_max_header_size(uint32_t size);

#ifdef __cplusplus
}
#endif
Expand Down
8 changes: 8 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ added: v9.0.0

Specify the `file` of the custom [experimental ECMAScript Module][] loader.

### `--max-http-header-size=size`
<!-- YAML
added: v10.15.0
-->

Specify the maximum size, in bytes, of HTTP headers. Defaults to 8KB.

### `--napi-modules`
<!-- YAML
added: v7.10.0
Expand Down Expand Up @@ -584,6 +591,7 @@ Node.js options that are allowed are:
- `--inspect-brk`
- `--inspect-port`
- `--loader`
- `--max-http-header-size`
- `--napi-modules`
- `--no-deprecation`
- `--no-force-async-hooks-checks`
Expand Down
8 changes: 7 additions & 1 deletion doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1856,9 +1856,15 @@ Creation of a [`zlib`][] object failed due to incorrect configuration.

<a id="HPE_HEADER_OVERFLOW"></a>
### HPE_HEADER_OVERFLOW
<!-- YAML
changes:
- version: v10.15.0
pr-url: https://github.com/nodejs/node/commit/186035243fad247e3955f
description: Max header size in `http_parser` was set to 8KB.
-->

Too much HTTP header data was received. In order to protect against malicious or
malconfigured clients, if more than 80KB of HTTP header data is received then
malconfigured clients, if more than 8KB 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.

Expand Down
11 changes: 11 additions & 0 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -1894,6 +1894,16 @@ added: v0.5.9
Global instance of `Agent` which is used as the default for all HTTP client
requests.

## http.maxHeaderSize
<!-- YAML
added: v10.15.0
-->

* {number}

Read-only property specifying the maximum allowed size of HTTP headers in bytes.
Defaults to 8KB. Configurable using the [`--max-http-header-size`][] CLI option.

## http.request(options[, callback])
## http.request(url[, options][, callback])
<!-- YAML
Expand Down Expand Up @@ -2079,6 +2089,7 @@ will be emitted in the following order:
Note that setting the `timeout` option or using the `setTimeout()` function will
not abort the request or do anything besides add a `'timeout'` event.

[`--max-http-header-size`]: cli.html#cli_max_http_header_size_size
[`'checkContinue'`]: #http_event_checkcontinue
[`'request'`]: #http_event_request
[`'response'`]: #http_event_response
Expand Down
27 changes: 27 additions & 0 deletions doc/changelogs/CHANGELOG_V10.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</tr>
<tr>
<td valign="top">
<a href="#10.15.0">10.15.0</a><br/>
<a href="#10.14.2">10.14.2</a><br/>
<a href="#10.14.1">10.14.1</a><br/>
<a href="#10.14.0">10.14.0</a><br/>
Expand Down Expand Up @@ -47,6 +48,32 @@
* [io.js](CHANGELOG_IOJS.md)
* [Archive](CHANGELOG_ARCHIVE.md)

<a id="10.15.0"></a>
## 2018-12-26, Version 10.15.0 'Dubnium' (LTS), @MylesBorins

The 10.14.0 security release introduced some unexpected breakages on the 10.x release line.
This is a special release to fix a regression in the HTTP binary upgrade response body and add
a missing CLI flag to adjust the max header size of the http parser.

### Notable Changes

* **cli**:
- add --max-http-header-size flag (cjihrig) [#24811](https://github.com/nodejs/node/pull/24811)
* **http**:
- add maxHeaderSize property (cjihrig) [#24860](https://github.com/nodejs/node/pull/24860)

### Commits

* [[`9b2ffc81c0`](https://github.com/nodejs/node/commit/9b2ffc81c0)] - **(SEMVER-MINOR)** **cli**: add --max-http-header-size flag (cjihrig) [#24811](https://github.com/nodejs/node/pull/24811)
* [[`6183c7107d`](https://github.com/nodejs/node/commit/6183c7107d)] - **(SEMVER-MINOR)** **deps**: cherry-pick http\_parser\_set\_max\_header\_size (cjihrig) [#24811](https://github.com/nodejs/node/pull/24811)
* [[`e669733595`](https://github.com/nodejs/node/commit/e669733595)] - **doc**: describe current HTTP header size limit (Sam Roberts) [#24700](https://github.com/nodejs/node/pull/24700)
* [[`b6d3afb257`](https://github.com/nodejs/node/commit/b6d3afb257)] - **(SEMVER-MINOR)** **http**: add maxHeaderSize property (cjihrig) [#24860](https://github.com/nodejs/node/pull/24860)
* [[`1aea1e3634`](https://github.com/nodejs/node/commit/1aea1e3634)] - **http**: fix regression of binary upgrade response body (Matteo Collina) [#25039](https://github.com/nodejs/node/pull/25039)
* [[`a57aed144a`](https://github.com/nodejs/node/commit/a57aed144a)] - **(SEMVER-MINOR)** **src**: add kUInteger parsing (Matteo Collina) [#24811](https://github.com/nodejs/node/pull/24811)
* [[`527407c49f`](https://github.com/nodejs/node/commit/527407c49f)] - **src**: cache the result of GetOptions() in JS land (Joyee Cheung) [#24091](https://github.com/nodejs/node/pull/24091)
* [[`728bc631e5`](https://github.com/nodejs/node/commit/728bc631e5)] - **test**: fix expectation in test-bootstrap-modules (Ali Ijaz Sheikh) [#25112](https://github.com/nodejs/node/pull/25112)
* [[`3e14212f0e`](https://github.com/nodejs/node/commit/3e14212f0e)] - **test**: remove magic numbers in test-gc-http-client-onerror (Rich Trott) [#24943](https://github.com/nodejs/node/pull/24943)

<a id="10.14.2"></a>
## 2018-12-11, Version 10.14.2 'Dubnium' (LTS), @MylesBorins prepared by @codebytere

Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ Specify the
as a custom loader, to load
.Fl -experimental-modules .
.
.It Fl -max-http-header-size Ns = Ns Ar size
Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
.
.It Fl -napi-modules
This option is a no-op.
It is kept for compatibility.
Expand Down
2 changes: 1 addition & 1 deletion lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const {
ERR_CRYPTO_FIPS_FORCED,
ERR_CRYPTO_FIPS_UNAVAILABLE
} = require('internal/errors').codes;
const constants = process.binding('constants').crypto;
const constants = internalBinding('constants').crypto;
const {
fipsMode,
fipsForced
Expand Down
14 changes: 14 additions & 0 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const {
Server,
ServerResponse
} = require('_http_server');
let maxHeaderSize;

function createServer(opts, requestListener) {
return new Server(opts, requestListener);
Expand Down Expand Up @@ -62,3 +63,16 @@ module.exports = {
get,
request
};

Object.defineProperty(module.exports, 'maxHeaderSize', {
configurable: true,
enumerable: true,
get() {
if (maxHeaderSize === undefined) {
const { getOptionValue } = require('internal/options');
maxHeaderSize = getOptionValue('--max-http-header-size');
}

return maxHeaderSize;
}
});
3 changes: 1 addition & 2 deletions lib/internal/bash_completion.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use strict';
const { getOptions } = internalBinding('options');
const { options, aliases } = require('internal/options');

function print(stream) {
const { options, aliases } = getOptions();
const all_opts = [...options.keys(), ...aliases.keys()];

stream.write(`_node_complete() {
Expand Down
3 changes: 1 addition & 2 deletions lib/internal/bootstrap/loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,7 @@

NativeModule.isInternal = function(id) {
return id.startsWith('internal/') ||
(id === 'worker_threads' &&
!internalBinding('options').getOptions('--experimental-worker'));
(id === 'worker_threads' && !config.experimentalWorker);
};
}

Expand Down
16 changes: 8 additions & 8 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,13 @@
NativeModule.require('internal/inspector_async_hook').setup();
}

const { getOptions } = internalBinding('options');
const helpOption = getOptions('--help');
const completionBashOption = getOptions('--completion-bash');
const experimentalModulesOption = getOptions('--experimental-modules');
const experimentalVMModulesOption = getOptions('--experimental-vm-modules');
const experimentalWorkerOption = getOptions('--experimental-worker');
const { getOptionValue } = NativeModule.require('internal/options');
const helpOption = getOptionValue('--help');
const completionBashOption = getOptionValue('--completion-bash');
const experimentalModulesOption = getOptionValue('--experimental-modules');
const experimentalVMModulesOption =
getOptionValue('--experimental-vm-modules');
const experimentalWorkerOption = getOptionValue('--experimental-worker');
if (helpOption) {
NativeModule.require('internal/print_help').print(process.stdout);
return;
Expand Down Expand Up @@ -631,10 +632,9 @@

const get = () => {
const {
getOptions,
envSettings: { kAllowedInEnvironment }
} = internalBinding('options');
const { options, aliases } = getOptions();
const { options, aliases } = NativeModule.require('internal/options');

const allowedNodeEnvironmentFlags = [];
for (const [name, info] of options) {
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/modules/cjs/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {
CHAR_HASH,
} = require('internal/constants');

const { getOptions } = internalBinding('options');
const { getOptionValue } = require('internal/options');

// Invoke with makeRequireFunction(module) where |module| is the Module object
// to use as the context for the require() function.
Expand Down Expand Up @@ -107,7 +107,7 @@ const builtinLibs = [
'v8', 'vm', 'zlib'
];

if (getOptions('--experimental-worker')) {
if (getOptionValue('--experimental-worker')) {
builtinLibs.push('worker_threads');
builtinLibs.sort();
}
Expand Down
8 changes: 4 additions & 4 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ const {
stripBOM,
stripShebang
} = require('internal/modules/cjs/helpers');
const options = internalBinding('options');
const preserveSymlinks = options.getOptions('--preserve-symlinks');
const preserveSymlinksMain = options.getOptions('--preserve-symlinks-main');
const experimentalModules = options.getOptions('--experimental-modules');
const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalModules = getOptionValue('--experimental-modules');

const {
ERR_INVALID_ARG_TYPE,
Expand Down
6 changes: 3 additions & 3 deletions lib/internal/modules/esm/default_resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ const internalFS = require('internal/fs/utils');
const { NativeModule } = require('internal/bootstrap/loaders');
const { extname } = require('path');
const { realpathSync } = require('fs');
const { getOptions } = internalBinding('options');
const preserveSymlinks = getOptions('--preserve-symlinks');
const preserveSymlinksMain = getOptions('--preserve-symlinks-main');
const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const {
ERR_MISSING_MODULE,
ERR_MODULE_RESOLUTION_LEGACY,
Expand Down
18 changes: 18 additions & 0 deletions lib/internal/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const { getOptions } = internalBinding('options');
const { options, aliases } = getOptions();

function getOptionValue(option) {
const result = options.get(option);
if (!result) {
return undefined;
}
return result.value;
}

module.exports = {
options,
aliases,
getOptionValue
};
6 changes: 4 additions & 2 deletions lib/internal/print_help.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const { getOptions, types } = internalBinding('options');

const { types } = internalBinding('options');

const typeLookup = [];
for (const key of Object.keys(types))
Expand Down Expand Up @@ -58,6 +59,7 @@ function getArgDescription(type) {
case 'kHostPort':
return '[host:]port';
case 'kInteger':
case 'kUInteger':
case 'kString':
case 'kStringList':
return '...';
Expand Down Expand Up @@ -132,7 +134,7 @@ function format({ options, aliases = new Map(), firstColumn, secondColumn }) {
}

function print(stream) {
const { options, aliases } = getOptions();
const { options, aliases } = require('internal/options');

// Use 75 % of the available width, and at least 70 characters.
const width = Math.max(70, (stream.columns || 0) * 0.75);
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/process/esm_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ exports.setup = function() {

let ESMLoader = new Loader();
const loaderPromise = (async () => {
const userLoader = internalBinding('options').getOptions('--loader');
const userLoader = require('internal/options').getOptionValue('--loader');
if (userLoader) {
const hooks = await ESMLoader.import(
userLoader, pathToFileURL(`${process.cwd()}/`).href);
Expand Down
2 changes: 1 addition & 1 deletion lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const {
ERR_SCRIPT_EXECUTION_INTERRUPTED
} = require('internal/errors').codes;
const { sendInspectorCommand } = require('internal/util/inspector');
const experimentalREPLAwait = internalBinding('options').getOptions(
const experimentalREPLAwait = require('internal/options').getOptionValue(
'--experimental-repl-await'
);
const { isRecoverableError } = require('internal/repl/recoverable');
Expand Down
2 changes: 1 addition & 1 deletion lib/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ module.exports = {
compileFunction,
};

if (internalBinding('options').getOptions('--experimental-vm-modules')) {
if (require('internal/options').getOptionValue('--experimental-vm-modules')) {
const { SourceTextModule } = require('internal/vm/source_text_module');
module.exports.SourceTextModule = SourceTextModule;
}
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
'lib/internal/modules/esm/translators.js',
'lib/internal/safe_globals.js',
'lib/internal/net.js',
'lib/internal/options.js',
'lib/internal/print_help.js',
'lib/internal/process/esm_loader.js',
'lib/internal/process/main_thread_only.js',
Expand Down
Loading