Skip to content

Commit 4184806

Browse files
danbevBethGriggs
authored andcommitted
http2: add unknownProtocol timeout
This commit add a configuration options named unknownProtocolTimeout which can be specified to set a value for the timeout in milliseconds that a server should wait when an unknowProtocol is sent to it. When this happens a timer will be started and the if the socket has not been destroyed during that time the timer callback will destoy it. Refs: https://hackerone.com/reports/1043360 CVE-ID: CVE-2021-22883 PR-URL: nodejs-private/node-private#246 Reviewed-By: Beth Griggs <bgriggs@redhat.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent 43ae9c4 commit 4184806

File tree

3 files changed

+84
-5
lines changed

3 files changed

+84
-5
lines changed

doc/api/http2.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -2048,7 +2048,9 @@ added: v8.4.0
20482048
The `'unknownProtocol'` event is emitted when a connecting client fails to
20492049
negotiate an allowed protocol (i.e. HTTP/2 or HTTP/1.1). The event handler
20502050
receives the socket for handling. If no listener is registered for this event,
2051-
the connection is terminated. See the [Compatibility API][].
2051+
the connection is terminated. A timeout may be specified using the
2052+
`'unknownProtocolTimeout'` option passed to [`http2.createSecureServer()`][].
2053+
See the [Compatibility API][].
20522054

20532055
#### `server.close([callback])`
20542056
<!-- YAML
@@ -2120,6 +2122,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
21202122
<!-- YAML
21212123
added: v8.4.0
21222124
changes:
2125+
- version: REPLACEME
2126+
pr-url: https://github.com/nodejs-private/node-private/pull/246
2127+
description: Added `unknownProtocolTimeout` option with a default of 10000.
21232128
- version:
21242129
- v14.4.0
21252130
- v12.18.0
@@ -2226,6 +2231,10 @@ changes:
22262231
`Http2ServerResponse` class to use.
22272232
Useful for extending the original `Http2ServerResponse`.
22282233
**Default:** `Http2ServerResponse`.
2234+
* `unknownProtocolTimeout` {number} Specifies a timeout in milliseconds that
2235+
a server should wait when an [`'unknownProtocol'`][] is emitted. If the
2236+
socket has not been destroyed by that time the server will destroy it.
2237+
**Default:** `10000`.
22292238
* ...: Any [`net.createServer()`][] option can be provided.
22302239
* `onRequestHandler` {Function} See [Compatibility API][]
22312240
* Returns: {Http2Server}
@@ -2262,6 +2271,9 @@ server.listen(80);
22622271
<!-- YAML
22632272
added: v8.4.0
22642273
changes:
2274+
- version: REPLACEME
2275+
pr-url: https://github.com/nodejs-private/node-private/pull/246
2276+
description: Added `unknownProtocolTimeout` option with a default of 10000.
22652277
- version:
22662278
- v14.4.0
22672279
- v12.18.0
@@ -2358,6 +2370,10 @@ changes:
23582370
servers, the identity options (`pfx` or `key`/`cert`) are usually required.
23592371
* `origins` {string[]} An array of origin strings to send within an `ORIGIN`
23602372
frame immediately following creation of a new server `Http2Session`.
2373+
* `unknownProtocolTimeout` {number} Specifies a timeout in milliseconds that
2374+
a server should wait when an [`'unknownProtocol'`][] event is emitted. If
2375+
the socket has not been destroyed by that time the server will destroy it.
2376+
**Default:** `10000`.
23612377
* `onRequestHandler` {Function} See [Compatibility API][]
23622378
* Returns: {Http2SecureServer}
23632379

@@ -2391,6 +2407,9 @@ server.listen(80);
23912407
<!-- YAML
23922408
added: v8.4.0
23932409
changes:
2410+
- version: REPLACEME
2411+
pr-url: https://github.com/nodejs-private/node-private/pull/246
2412+
description: Added `unknownProtocolTimeout` option with a default of 10000.
23942413
- version:
23952414
- v14.4.0
23962415
- v12.18.0
@@ -2474,6 +2493,10 @@ changes:
24742493
instance passed to `connect` and the `options` object, and returns any
24752494
[`Duplex`][] stream that is to be used as the connection for this session.
24762495
* ...: Any [`net.connect()`][] or [`tls.connect()`][] options can be provided.
2496+
* `unknownProtocolTimeout` {number} Specifies a timeout in milliseconds that
2497+
a server should wait when an [`'unknownProtocol'`][] event is emitted. If
2498+
the socket has not been destroyed by that time the server will destroy it.
2499+
**Default:** `10000`.
24772500
* `listener` {Function} Will be registered as a one-time listener of the
24782501
[`'connect'`][] event.
24792502
* Returns: {ClientHttp2Session}

lib/internal/http2/core.js

+27-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const { URL } = require('internal/url');
4848
const net = require('net');
4949
const { Duplex } = require('stream');
5050
const tls = require('tls');
51-
const { setImmediate } = require('timers');
51+
const { setImmediate, setTimeout, clearTimeout } = require('timers');
5252

5353
const {
5454
kIncomingMessage,
@@ -2899,14 +2899,14 @@ function handleHeaderContinue(headers) {
28992899
this.emit('continue');
29002900
}
29012901

2902-
const setTimeout = {
2902+
const setTimeoutValue = {
29032903
configurable: true,
29042904
enumerable: true,
29052905
writable: true,
29062906
value: setStreamTimeout
29072907
};
2908-
ObjectDefineProperty(Http2Stream.prototype, 'setTimeout', setTimeout);
2909-
ObjectDefineProperty(Http2Session.prototype, 'setTimeout', setTimeout);
2908+
ObjectDefineProperty(Http2Stream.prototype, 'setTimeout', setTimeoutValue);
2909+
ObjectDefineProperty(Http2Session.prototype, 'setTimeout', setTimeoutValue);
29102910

29112911

29122912
// When the socket emits an error, destroy the associated Http2Session and
@@ -2966,6 +2966,22 @@ function connectionListener(socket) {
29662966
debug('Unknown protocol from %s:%s',
29672967
socket.remoteAddress, socket.remotePort);
29682968
if (!this.emit('unknownProtocol', socket)) {
2969+
debug('Unknown protocol timeout: %s', options.unknownProtocolTimeout);
2970+
// Install a timeout if the socket was not successfully closed, then
2971+
// destroy the socket to ensure that the underlying resources are
2972+
// released.
2973+
const timer = setTimeout(() => {
2974+
if (!socket.destroyed) {
2975+
debug('UnknownProtocol socket timeout, destroy socket');
2976+
socket.destroy();
2977+
}
2978+
}, options.unknownProtocolTimeout);
2979+
// Un-reference the timer to avoid blocking of application shutdown and
2980+
// clear the timeout if the socket was successfully closed.
2981+
timer.unref();
2982+
2983+
socket.once('close', () => clearTimeout(timer));
2984+
29692985
// We don't know what to do, so let's just tell the other side what's
29702986
// going on in a format that they *might* understand.
29712987
socket.end('HTTP/1.0 403 Forbidden\r\n' +
@@ -3011,6 +3027,13 @@ function initializeOptions(options) {
30113027
);
30123028
}
30133029

3030+
if (options.unknownProtocolTimeout !== undefined)
3031+
validateUint32(options.unknownProtocolTimeout, 'unknownProtocolTimeout');
3032+
else
3033+
// TODO(danbev): is this a good default value?
3034+
options.unknownProtocolTimeout = 10000;
3035+
3036+
30143037
// Used only with allowHTTP1
30153038
options.Http1IncomingMessage = options.Http1IncomingMessage ||
30163039
http.IncomingMessage;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
const common = require('../common');
3+
const fixtures = require('../common/fixtures');
4+
5+
// This test verifies that when a server receives an unknownProtocol it will
6+
// not leave the socket open if the client does not close it.
7+
8+
if (!common.hasCrypto)
9+
common.skip('missing crypto');
10+
11+
const h2 = require('http2');
12+
const tls = require('tls');
13+
14+
const server = h2.createSecureServer({
15+
key: fixtures.readKey('rsa_private.pem'),
16+
cert: fixtures.readKey('rsa_cert.crt'),
17+
unknownProtocolTimeout: 500,
18+
allowHalfOpen: true
19+
});
20+
21+
server.on('connection', (socket) => {
22+
socket.on('close', common.mustCall(() => {
23+
server.close();
24+
}));
25+
});
26+
27+
server.listen(0, function() {
28+
tls.connect({
29+
port: server.address().port,
30+
rejectUnauthorized: false,
31+
ALPNProtocols: ['bogus']
32+
});
33+
});

0 commit comments

Comments
 (0)