Skip to content

Commit ac28a62

Browse files
addaleaxBethGriggs
authored andcommitted
http2: limit number of invalid incoming frames
Limit the number of invalid input frames, as they may be pointing towards a misbehaving peer. The limit is currently set to 1000 but could be changed or made configurable. This is intended to mitigate CVE-2019-9514. [This commit differs from the v12.x one due to the lack of libuv/libuv@ee24ce900e5714c950b248da2b. See the comment in the test for more details.] Backport-PR-URL: #29124 PR-URL: #29122 Reviewed-By: Rich Trott <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 11b4e2c commit ac28a62

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

src/node_http2.cc

+4
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,10 @@ inline int Http2Session::OnInvalidFrame(nghttp2_session* handle,
10701070

10711071
DEBUG_HTTP2SESSION2(session, "invalid frame received, code: %d",
10721072
lib_error_code);
1073+
if (session->invalid_frame_count_++ > 1000 &&
1074+
!IsReverted(SECURITY_REVERT_CVE_2019_9514)) {
1075+
return 1;
1076+
}
10731077

10741078
// If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
10751079
if (nghttp2_is_fatal(lib_error_code) ||

src/node_http2.h

+2
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,8 @@ class Http2Session : public AsyncWrap {
10761076
// misbehaving peer. This counter is reset once new streams are being
10771077
// accepted again.
10781078
int32_t rejected_stream_count_ = 0;
1079+
// Also use the invalid frame count as a measure for rejecting input frames.
1080+
int32_t invalid_frame_count_ = 0;
10791081

10801082
void CopyDataIntoOutgoing(const uint8_t* src, size_t src_length);
10811083
void ClearOutgoing(int status);
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (!common.hasCrypto)
4+
common.skip('missing crypto');
5+
6+
const child_process = require('child_process');
7+
const http2 = require('http2');
8+
const net = require('net');
9+
10+
// Verify that creating a number of invalid HTTP/2 streams will eventually
11+
// result in the peer closing the session.
12+
// This test uses separate processes for client and server to avoid
13+
// the two event loops intermixing, as we are writing in a busy loop here.
14+
15+
if (process.argv[2] === 'child') {
16+
const server = http2.createServer();
17+
server.on('stream', (stream) => {
18+
stream.respond({
19+
'content-type': 'text/plain',
20+
':status': 200
21+
});
22+
stream.end('Hello, world!\n');
23+
});
24+
server.listen(0, () => {
25+
process.stdout.write(`${server.address().port}`);
26+
});
27+
return;
28+
}
29+
30+
const child = child_process.spawn(process.execPath, [__filename, 'child'], {
31+
stdio: ['inherit', 'pipe', 'inherit']
32+
});
33+
child.stdout.on('data', common.mustCall((port) => {
34+
const h2header = Buffer.alloc(9);
35+
const conn = net.connect(+port);
36+
37+
conn.write('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
38+
39+
h2header[3] = 4; // Send a settings frame.
40+
conn.write(Buffer.from(h2header));
41+
42+
let inbuf = Buffer.alloc(0);
43+
let state = 'settingsHeader';
44+
let settingsFrameLength;
45+
conn.on('data', (chunk) => {
46+
inbuf = Buffer.concat([inbuf, chunk]);
47+
switch (state) {
48+
case 'settingsHeader':
49+
if (inbuf.length < 9) return;
50+
settingsFrameLength = inbuf.readIntBE(0, 3);
51+
inbuf = inbuf.slice(9);
52+
state = 'readingSettings';
53+
// Fallthrough
54+
case 'readingSettings':
55+
if (inbuf.length < settingsFrameLength) return;
56+
inbuf = inbuf.slice(settingsFrameLength);
57+
h2header[3] = 4; // Send a settings ACK.
58+
h2header[4] = 1;
59+
conn.write(Buffer.from(h2header));
60+
state = 'ignoreInput';
61+
writeRequests();
62+
}
63+
});
64+
65+
let gotError = false;
66+
67+
let i = 1;
68+
function writeRequests() {
69+
for (; !gotError; i += 2) {
70+
h2header[3] = 1; // HEADERS
71+
h2header[4] = 0x5; // END_HEADERS|END_STREAM
72+
h2header.writeIntBE(1, 0, 3); // Length: 1
73+
h2header.writeIntBE(i, 5, 4); // Stream ID
74+
// 0x88 = :status: 200
75+
conn.write(Buffer.concat([h2header, Buffer.from([0x88])]));
76+
77+
if (i % 1000 === 1) {
78+
// Delay writing a bit so we get the chance to actually observe
79+
// an error. This is not necessary on master/v12.x, because there
80+
// conn.write() can fail directly when writing to a connection
81+
// that was closed by the remote peer due to
82+
// https://github.com/libuv/libuv/commit/ee24ce900e5714c950b248da2b
83+
i += 2;
84+
return setImmediate(writeRequests);
85+
}
86+
}
87+
}
88+
89+
conn.once('error', common.mustCall(() => {
90+
gotError = true;
91+
child.kill();
92+
conn.destroy();
93+
}));
94+
}));

0 commit comments

Comments
 (0)