Skip to content

Commit

Permalink
http2: make maximum tolerated rejected streams configurable
Browse files Browse the repository at this point in the history
PR-URL: #30534
Fixes: #30505
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: David Carlier <devnexen@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
lundibundi authored and targos committed Jan 13, 2020
1 parent 518fd8f commit fb4f71b
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 2 deletions.
18 changes: 18 additions & 0 deletions doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -1939,6 +1939,9 @@ error will be thrown.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/30534
description: Added `maxSessionRejectedStreams` option with a default of 100.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/30534
description: Added `maxSessionInvalidFrames` option with a default of 1000.
Expand Down Expand Up @@ -2005,6 +2008,12 @@ changes:
* `maxSessionInvalidFrames` {integer} Sets the maximum number of invalid
frames that will be tolerated before the session is closed.
**Default:** `1000`.
* `maxSessionRejectedStreams` {integer} Sets the maximum number of rejected
upon creation streams that will be tolerated before the session is closed.
Each rejection is associated with an `NGHTTP2_ENHANCE_YOUR_CALM`
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `selectPadding` {Function} When `options.paddingStrategy` is equal to
`http2.constants.PADDING_STRATEGY_CALLBACK`, provides the callback function
used to determine the padding. See [Using `options.selectPadding()`][].
Expand Down Expand Up @@ -2060,6 +2069,9 @@ server.listen(80);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/30534
description: Added `maxSessionRejectedStreams` option with a default of 100.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/30534
description: Added `maxSessionInvalidFrames` option with a default of 1000.
Expand Down Expand Up @@ -2126,6 +2138,12 @@ changes:
* `maxSessionInvalidFrames` {integer} Sets the maximum number of invalid
frames that will be tolerated before the session is closed.
**Default:** `1000`.
* `maxSessionRejectedStreams` {integer} Sets the maximum number of rejected
upon creation streams that will be tolerated before the session is closed.
Each rejection is associated with an `NGHTTP2_ENHANCE_YOUR_CALM`
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `selectPadding` {Function} When `options.paddingStrategy` is equal to
`http2.constants.PADDING_STRATEGY_CALLBACK`, provides the callback function
used to determine the padding. See [Using `options.selectPadding()`][].
Expand Down
14 changes: 14 additions & 0 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ const {
kSessionPriorityListenerCount,
kSessionFrameErrorListenerCount,
kSessionMaxInvalidFrames,
kSessionMaxRejectedStreams,
kSessionUint8FieldCount,
kSessionHasRemoteSettingsListeners,
kSessionRemoteSettingsIsUpToDate,
Expand Down Expand Up @@ -970,6 +971,12 @@ function setupHandle(socket, type, options) {
uint32[0] = options.maxSessionInvalidFrames;
}

if (isUint32(options.maxSessionRejectedStreams)) {
const uint32 = new Uint32Array(
this[kNativeFields].buffer, kSessionMaxRejectedStreams, 1);
uint32[0] = options.maxSessionRejectedStreams;
}

const settings = typeof options.settings === 'object' ?
options.settings : {};

Expand Down Expand Up @@ -2804,6 +2811,13 @@ function initializeOptions(options) {
if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'maxSessionInvalidFrames');

if (options.maxSessionRejectedStreams !== undefined) {
validateUint32(
options.maxSessionRejectedStreams,
'maxSessionRejectedStreams'
);
}

// Used only with allowHTTP1
options.Http1IncomingMessage = options.Http1IncomingMessage ||
http.IncomingMessage;
Expand Down
4 changes: 3 additions & 1 deletion src/node_http2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,8 @@ int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
if (UNLIKELY(!session->CanAddStream() ||
Http2Stream::New(session, id, frame->headers.cat) ==
nullptr)) {
if (session->rejected_stream_count_++ > 100 &&
if (session->rejected_stream_count_++ >
session->js_fields_.max_rejected_streams &&
!IsReverted(SECURITY_REVERT_CVE_2019_9514)) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
Expand Down Expand Up @@ -3111,6 +3112,7 @@ void Initialize(Local<Object> target,
NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
NODE_DEFINE_CONSTANT(target, kSessionMaxInvalidFrames);
NODE_DEFINE_CONSTANT(target, kSessionMaxRejectedStreams);
NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);

NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
Expand Down
4 changes: 3 additions & 1 deletion src/node_http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ typedef struct {
uint8_t priority_listener_count;
uint8_t frame_error_listener_count;
uint32_t max_invalid_frames = 1000;
uint32_t max_rejected_streams = 100;
} SessionJSFields;

// Indices for js_fields_, which serves as a way to communicate data with JS
Expand All @@ -693,6 +694,7 @@ enum SessionUint8Fields {
kSessionFrameErrorListenerCount =
offsetof(SessionJSFields, frame_error_listener_count),
kSessionMaxInvalidFrames = offsetof(SessionJSFields, max_invalid_frames),
kSessionMaxRejectedStreams = offsetof(SessionJSFields, max_rejected_streams),
kSessionUint8FieldCount = sizeof(SessionJSFields)
};

Expand Down Expand Up @@ -1028,7 +1030,7 @@ class Http2Session : public AsyncWrap, public StreamListener {
// limit will result in the session being destroyed, as an indication of a
// misbehaving peer. This counter is reset once new streams are being
// accepted again.
int32_t rejected_stream_count_ = 0;
uint32_t rejected_stream_count_ = 0;
// Also use the invalid frame count as a measure for rejecting input frames.
uint32_t invalid_frame_count_ = 0;

Expand Down
16 changes: 16 additions & 0 deletions test/parallel/test-http2-createsecureserver-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ Object.entries({
},
},
],
maxSessionRejectedStreams: [
{
val: -1,
err: {
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE',
},
},
{
val: Number.NEGATIVE_INFINITY,
err: {
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE',
},
},
],
}).forEach(([opt, tests]) => {
tests.forEach(({ val, err }) => {
assert.throws(
Expand Down
16 changes: 16 additions & 0 deletions test/parallel/test-http2-createserver-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ Object.entries({
},
},
],
maxSessionRejectedStreams: [
{
val: -1,
err: {
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE',
},
},
{
val: Number.NEGATIVE_INFINITY,
err: {
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE',
},
},
]
}).forEach(([opt, tests]) => {
tests.forEach(({ val, err }) => {
assert.throws(
Expand Down

0 comments on commit fb4f71b

Please sign in to comment.