forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
http2: verify flood error and unsolicited frames
* verify protections against ping and settings flooding * Strictly handle and verify handling of unsolicited ping and settings frame acks. Backport-PR-URL: nodejs#18050 PR-URL: nodejs#17969 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
- Loading branch information
Showing
7 changed files
with
252 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
if (!common.hasCrypto) | ||
common.skip('missing crypto'); | ||
|
||
const http2 = require('http2'); | ||
const net = require('net'); | ||
const http2util = require('../common/http2'); | ||
|
||
// Test that ping flooding causes the session to be torn down | ||
|
||
const kSettings = new http2util.SettingsFrame(); | ||
const kPingAck = new http2util.PingFrame(true); | ||
|
||
const server = http2.createServer(); | ||
|
||
server.on('stream', common.mustNotCall()); | ||
server.on('session', common.mustCall((session) => { | ||
session.on('error', common.expectsError({ | ||
code: 'ERR_HTTP2_ERROR', | ||
message: 'Protocol error' | ||
})); | ||
session.on('close', common.mustCall(() => server.close())); | ||
})); | ||
|
||
server.listen(0, common.mustCall(() => { | ||
const client = net.connect(server.address().port); | ||
|
||
client.on('connect', common.mustCall(() => { | ||
client.write(http2util.kClientMagic, () => { | ||
client.write(kSettings.data); | ||
// Send an unsolicited ping ack | ||
client.write(kPingAck.data); | ||
}); | ||
})); | ||
|
||
// An error event may or may not be emitted, depending on operating system | ||
// and timing. We do not really care if one is emitted here or not, as the | ||
// error on the server side is what we are testing for. Do not make this | ||
// a common.mustCall() and there's no need to check the error details. | ||
client.on('error', () => {}); | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
if (!common.hasCrypto) | ||
common.skip('missing crypto'); | ||
|
||
const assert = require('assert'); | ||
const http2 = require('http2'); | ||
const net = require('net'); | ||
const http2util = require('../common/http2'); | ||
const Countdown = require('../common/countdown'); | ||
|
||
// Test that an unsolicited settings ack is ignored. | ||
|
||
const kSettings = new http2util.SettingsFrame(); | ||
const kSettingsAck = new http2util.SettingsFrame(true); | ||
|
||
const server = http2.createServer(); | ||
let client; | ||
|
||
const countdown = new Countdown(3, () => { | ||
client.destroy(); | ||
server.close(); | ||
}); | ||
|
||
server.on('stream', common.mustNotCall()); | ||
server.on('session', common.mustCall((session) => { | ||
session.on('remoteSettings', common.mustCall(() => countdown.dec())); | ||
})); | ||
|
||
server.listen(0, common.mustCall(() => { | ||
client = net.connect(server.address().port); | ||
|
||
// Ensures that the clients settings frames are not sent until the | ||
// servers are received, so that the first ack is actually expected. | ||
client.once('data', (chunk) => { | ||
// The very first chunk of data we get from the server should | ||
// be a settings frame. | ||
assert.deepStrictEqual(chunk.slice(0, 9), kSettings.data); | ||
// The first ack is expected. | ||
client.write(kSettingsAck.data, () => countdown.dec()); | ||
// The second one is not and will be ignored. | ||
client.write(kSettingsAck.data, () => countdown.dec()); | ||
}); | ||
|
||
client.on('connect', common.mustCall(() => { | ||
client.write(http2util.kClientMagic); | ||
client.write(kSettings.data); | ||
})); | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
if (!common.hasCrypto) | ||
common.skip('missing crypto'); | ||
|
||
const http2 = require('http2'); | ||
const net = require('net'); | ||
const http2util = require('../common/http2'); | ||
|
||
// Test that ping flooding causes the session to be torn down | ||
|
||
const kSettings = new http2util.SettingsFrame(); | ||
const kPing = new http2util.PingFrame(); | ||
|
||
const server = http2.createServer(); | ||
|
||
server.on('stream', common.mustNotCall()); | ||
server.on('session', common.mustCall((session) => { | ||
session.on('error', common.expectsError({ | ||
code: 'ERR_HTTP2_ERROR', | ||
message: | ||
'Flooding was detected in this HTTP/2 session, and it must be closed' | ||
})); | ||
session.on('close', common.mustCall(() => { | ||
server.close(); | ||
})); | ||
})); | ||
|
||
server.listen(0, common.mustCall(() => { | ||
const client = net.connect(server.address().port); | ||
|
||
// nghttp2 uses a limit of 10000 items in it's outbound queue. | ||
// If this number is exceeded, a flooding error is raised. Set | ||
// this lim higher to account for the ones that nghttp2 is | ||
// successfully able to respond to. | ||
// TODO(jasnell): Unfortunately, this test is inherently flaky because | ||
// it is entirely dependent on how quickly the server is able to handle | ||
// the inbound frames and whether those just happen to overflow nghttp2's | ||
// outbound queue. The threshold at which the flood error occurs can vary | ||
// from one system to another, and from one test run to another. | ||
client.on('connect', common.mustCall(() => { | ||
client.write(http2util.kClientMagic, () => { | ||
client.write(kSettings.data, () => { | ||
for (let n = 0; n < 35000; n++) | ||
client.write(kPing.data); | ||
}); | ||
}); | ||
})); | ||
|
||
// An error event may or may not be emitted, depending on operating system | ||
// and timing. We do not really care if one is emitted here or not, as the | ||
// error on the server side is what we are testing for. Do not make this | ||
// a common.mustCall() and there's no need to check the error details. | ||
client.on('error', () => {}); | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
if (!common.hasCrypto) | ||
common.skip('missing crypto'); | ||
|
||
const http2 = require('http2'); | ||
const net = require('net'); | ||
const http2util = require('../common/http2'); | ||
|
||
// Test that settings flooding causes the session to be torn down | ||
|
||
const kSettings = new http2util.SettingsFrame(); | ||
|
||
const server = http2.createServer(); | ||
|
||
server.on('stream', common.mustNotCall()); | ||
server.on('session', common.mustCall((session) => { | ||
session.on('error', common.expectsError({ | ||
code: 'ERR_HTTP2_ERROR', | ||
message: | ||
'Flooding was detected in this HTTP/2 session, and it must be closed' | ||
})); | ||
session.on('close', common.mustCall(() => { | ||
server.close(); | ||
})); | ||
})); | ||
|
||
server.listen(0, common.mustCall(() => { | ||
const client = net.connect(server.address().port); | ||
|
||
// nghttp2 uses a limit of 10000 items in it's outbound queue. | ||
// If this number is exceeded, a flooding error is raised. Set | ||
// this lim higher to account for the ones that nghttp2 is | ||
// successfully able to respond to. | ||
// TODO(jasnell): Unfortunately, this test is inherently flaky because | ||
// it is entirely dependent on how quickly the server is able to handle | ||
// the inbound frames and whether those just happen to overflow nghttp2's | ||
// outbound queue. The threshold at which the flood error occurs can vary | ||
// from one system to another, and from one test run to another. | ||
client.on('connect', common.mustCall(() => { | ||
client.write(http2util.kClientMagic, () => { | ||
for (let n = 0; n < 35000; n++) | ||
client.write(kSettings.data); | ||
}); | ||
})); | ||
|
||
// An error event may or may not be emitted, depending on operating system | ||
// and timing. We do not really care if one is emitted here or not, as the | ||
// error on the server side is what we are testing for. Do not make this | ||
// a common.mustCall() and there's no need to check the error details. | ||
client.on('error', () => {}); | ||
})); |