Skip to content

Commit 922ada7

Browse files
danbevrichardlau
authored andcommittedFeb 22, 2021
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 Backport-PR-URL: nodejs-private/node-private#250 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 1564752 commit 922ada7

File tree

3 files changed

+84
-5
lines changed

3 files changed

+84
-5
lines changed
 

Diff for: ‎doc/api/http2.md

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

19751977
#### `server.close([callback])`
19761978
<!-- YAML
@@ -2010,6 +2012,9 @@ error will be thrown.
20102012
<!-- YAML
20112013
added: v8.4.0
20122014
changes:
2015+
- version: REPLACEME
2016+
pr-url: https://github.com/nodejs-private/node-private/pull/250
2017+
description: Added `unknownProtocolTimeout` option with a default of 10000.
20132018
- version:
20142019
- v12.18.0
20152020
pr-url: https://github.com/nodejs-private/node-private/pull/206
@@ -2112,6 +2117,10 @@ changes:
21122117
`Http2ServerResponse` class to use.
21132118
Useful for extending the original `Http2ServerResponse`.
21142119
**Default:** `Http2ServerResponse`.
2120+
* `unknownProtocolTimeout` {number} Specifies a timeout in milliseconds that
2121+
a server should wait when an [`'unknownProtocol'`][] is emitted. If the
2122+
socket has not been destroyed by that time the server will destroy it.
2123+
**Default:** `10000`.
21152124
* ...: Any [`net.createServer()`][] option can be provided.
21162125
* `onRequestHandler` {Function} See [Compatibility API][]
21172126
* Returns: {Http2Server}
@@ -2148,6 +2157,9 @@ server.listen(80);
21482157
<!-- YAML
21492158
added: v8.4.0
21502159
changes:
2160+
- version: REPLACEME
2161+
pr-url: https://github.com/nodejs-private/node-private/pull/250
2162+
description: Added `unknownProtocolTimeout` option with a default of 10000.
21512163
- version:
21522164
- v12.18.0
21532165
pr-url: https://github.com/nodejs-private/node-private/pull/206
@@ -2240,6 +2252,10 @@ changes:
22402252
servers, the identity options (`pfx` or `key`/`cert`) are usually required.
22412253
* `origins` {string[]} An array of origin strings to send within an `ORIGIN`
22422254
frame immediately following creation of a new server `Http2Session`.
2255+
* `unknownProtocolTimeout` {number} Specifies a timeout in milliseconds that
2256+
a server should wait when an [`'unknownProtocol'`][] event is emitted. If
2257+
the socket has not been destroyed by that time the server will destroy it.
2258+
**Default:** `10000`.
22432259
* `onRequestHandler` {Function} See [Compatibility API][]
22442260
* Returns: {Http2SecureServer}
22452261

@@ -2273,6 +2289,9 @@ server.listen(80);
22732289
<!-- YAML
22742290
added: v8.4.0
22752291
changes:
2292+
- version: REPLACEME
2293+
pr-url: https://github.com/nodejs-private/node-private/pull/250
2294+
description: Added `unknownProtocolTimeout` option with a default of 10000.
22762295
- version:
22772296
- v12.18.0
22782297
pr-url: https://github.com/nodejs-private/node-private/pull/206
@@ -2356,6 +2375,10 @@ changes:
23562375
instance passed to `connect` and the `options` object, and returns any
23572376
[`Duplex`][] stream that is to be used as the connection for this session.
23582377
* ...: Any [`net.connect()`][] or [`tls.connect()`][] options can be provided.
2378+
* `unknownProtocolTimeout` {number} Specifies a timeout in milliseconds that
2379+
a server should wait when an [`'unknownProtocol'`][] event is emitted. If
2380+
the socket has not been destroyed by that time the server will destroy it.
2381+
**Default:** `10000`.
23592382
* `listener` {Function} Will be registered as a one-time listener of the
23602383
[`'connect'`][] event.
23612384
* Returns: {ClientHttp2Session}

Diff for: ‎lib/internal/http2/core.js

+27-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const net = require('net');
3535
const { Duplex } = require('stream');
3636
const tls = require('tls');
3737
const { URL } = require('url');
38-
const { setImmediate } = require('timers');
38+
const { setImmediate, setTimeout, clearTimeout } = require('timers');
3939

4040
const { kIncomingMessage } = require('_http_common');
4141
const { kServerResponse } = require('_http_server');
@@ -2829,14 +2829,14 @@ function handleHeaderContinue(headers) {
28292829
this.emit('continue');
28302830
}
28312831

2832-
const setTimeout = {
2832+
const setTimeoutValue = {
28332833
configurable: true,
28342834
enumerable: true,
28352835
writable: true,
28362836
value: setStreamTimeout
28372837
};
2838-
ObjectDefineProperty(Http2Stream.prototype, 'setTimeout', setTimeout);
2839-
ObjectDefineProperty(Http2Session.prototype, 'setTimeout', setTimeout);
2838+
ObjectDefineProperty(Http2Stream.prototype, 'setTimeout', setTimeoutValue);
2839+
ObjectDefineProperty(Http2Session.prototype, 'setTimeout', setTimeoutValue);
28402840

28412841

28422842
// When the socket emits an error, destroy the associated Http2Session and
@@ -2896,6 +2896,22 @@ function connectionListener(socket) {
28962896
debug('Unknown protocol from %s:%s',
28972897
socket.remoteAddress, socket.remotePort);
28982898
if (!this.emit('unknownProtocol', socket)) {
2899+
debug('Unknown protocol timeout: %s', options.unknownProtocolTimeout);
2900+
// Install a timeout if the socket was not successfully closed, then
2901+
// destroy the socket to ensure that the underlying resources are
2902+
// released.
2903+
const timer = setTimeout(() => {
2904+
if (!socket.destroyed) {
2905+
debug('UnknownProtocol socket timeout, destroy socket');
2906+
socket.destroy();
2907+
}
2908+
}, options.unknownProtocolTimeout);
2909+
// Un-reference the timer to avoid blocking of application shutdown and
2910+
// clear the timeout if the socket was successfully closed.
2911+
timer.unref();
2912+
2913+
socket.once('close', () => clearTimeout(timer));
2914+
28992915
// We don't know what to do, so let's just tell the other side what's
29002916
// going on in a format that they *might* understand.
29012917
socket.end('HTTP/1.0 403 Forbidden\r\n' +
@@ -2944,6 +2960,13 @@ function initializeOptions(options) {
29442960
);
29452961
}
29462962

2963+
if (options.unknownProtocolTimeout !== undefined)
2964+
validateUint32(options.unknownProtocolTimeout, 'unknownProtocolTimeout');
2965+
else
2966+
// TODO(danbev): is this a good default value?
2967+
options.unknownProtocolTimeout = 10000;
2968+
2969+
29472970
// Used only with allowHTTP1
29482971
options.Http1IncomingMessage = options.Http1IncomingMessage ||
29492972
http.IncomingMessage;

Diff for: ‎test/parallel/test-http2-server-unknown-protocol.js

+33
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)
Please sign in to comment.