Skip to content

Commit a21a1c0

Browse files
addaleaxtargos
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. PR-URL: #29122 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 4570ed1 commit a21a1c0

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

src/node_http2.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,10 @@ int Http2Session::OnInvalidFrame(nghttp2_session* handle,
10171017
Http2Session* session = static_cast<Http2Session*>(user_data);
10181018

10191019
Debug(session, "invalid frame received, code: %d", lib_error_code);
1020+
if (session->invalid_frame_count_++ > 1000 &&
1021+
!IsReverted(SECURITY_REVERT_CVE_2019_9514)) {
1022+
return 1;
1023+
}
10201024

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

src/node_http2.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,8 @@ class Http2Session : public AsyncWrap, public StreamListener {
10101010
// misbehaving peer. This counter is reset once new streams are being
10111011
// accepted again.
10121012
int32_t rejected_stream_count_ = 0;
1013+
// Also use the invalid frame count as a measure for rejecting input frames.
1014+
int32_t invalid_frame_count_ = 0;
10131015

10141016
void CopyDataIntoOutgoing(const uint8_t* src, size_t src_length);
10151017
void ClearOutgoing(int status);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (!common.hasCrypto)
4+
common.skip('missing crypto');
5+
6+
const http2 = require('http2');
7+
const net = require('net');
8+
const { Worker, parentPort } = require('worker_threads');
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 threads 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.env.HAS_STARTED_WORKER) {
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, () => parentPort.postMessage(server.address().port));
25+
return;
26+
}
27+
28+
process.env.HAS_STARTED_WORKER = 1;
29+
const worker = new Worker(__filename).on('message', common.mustCall((port) => {
30+
const h2header = Buffer.alloc(9);
31+
const conn = net.connect(port);
32+
33+
conn.write('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
34+
35+
h2header[3] = 4; // Send a settings frame.
36+
conn.write(Buffer.from(h2header));
37+
38+
let inbuf = Buffer.alloc(0);
39+
let state = 'settingsHeader';
40+
let settingsFrameLength;
41+
conn.on('data', (chunk) => {
42+
inbuf = Buffer.concat([inbuf, chunk]);
43+
switch (state) {
44+
case 'settingsHeader':
45+
if (inbuf.length < 9) return;
46+
settingsFrameLength = inbuf.readIntBE(0, 3);
47+
inbuf = inbuf.slice(9);
48+
state = 'readingSettings';
49+
// Fallthrough
50+
case 'readingSettings':
51+
if (inbuf.length < settingsFrameLength) return;
52+
inbuf = inbuf.slice(settingsFrameLength);
53+
h2header[3] = 4; // Send a settings ACK.
54+
h2header[4] = 1;
55+
conn.write(Buffer.from(h2header));
56+
state = 'ignoreInput';
57+
writeRequests();
58+
}
59+
});
60+
61+
let gotError = false;
62+
63+
function writeRequests() {
64+
for (let i = 1; !gotError; i += 2) {
65+
h2header[3] = 1; // HEADERS
66+
h2header[4] = 0x5; // END_HEADERS|END_STREAM
67+
h2header.writeIntBE(1, 0, 3); // Length: 1
68+
h2header.writeIntBE(i, 5, 4); // Stream ID
69+
// 0x88 = :status: 200
70+
conn.write(Buffer.concat([h2header, Buffer.from([0x88])]));
71+
}
72+
}
73+
74+
conn.once('error', common.mustCall(() => {
75+
gotError = true;
76+
worker.terminate();
77+
conn.destroy();
78+
}));
79+
}));

0 commit comments

Comments
 (0)