Skip to content

Commit

Permalink
http: prevent slowloris with keepalive connections
Browse files Browse the repository at this point in the history
Fixes: nodejs-private/security#214
PR-URL: nodejs-private/node-private#158
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
  • Loading branch information
mcollina authored and rvagg committed Feb 27, 2019
1 parent ddba47a commit 05534a2
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 0 deletions.
15 changes: 15 additions & 0 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@ function emitCloseNT(self) {
function parserOnIncoming(server, socket, state, req, keepAlive) {
resetSocketTimeout(server, socket, state);

if (server.keepAliveTimeout > 0) {
req.on('end', resetHeadersTimeoutOnReqEnd);
}

// Set to zero to communicate that we have finished parsing.
socket.parser.parsingHeadersStart = 0;

Expand Down Expand Up @@ -736,6 +740,17 @@ function socketOnWrap(ev, fn) {
return res;
}

function resetHeadersTimeoutOnReqEnd() {
debug('resetHeadersTimeoutOnReqEnd');

const parser = this.socket.parser;
// Parser can be null if the socket was destroyed
// in that case, there is nothing to do.
if (parser !== null) {
parser.parsingHeadersStart = nowDate();
}
}

module.exports = {
STATUS_CODES,
Server,
Expand Down
51 changes: 51 additions & 0 deletions test/parallel/test-http-slow-headers-keepalive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const common = require('../common');
const http = require('http');
const net = require('net');
const { finished } = require('stream');

const headers =
'GET / HTTP/1.1\r\n' +
'Host: localhost\r\n' +
'Connection: keep-alive' +
'Agent: node\r\n';

let sendCharEvery = 1000;

const server = http.createServer(common.mustCall((req, res) => {
res.writeHead(200);
res.end();
}));

// Pass a REAL env variable to shortening up the default
// value which is 40s otherwise this is useful for manual
// testing
if (!process.env.REAL) {
sendCharEvery = common.platformTimeout(10);
server.headersTimeout = 2 * sendCharEvery;
}

server.once('timeout', common.mustCall((socket) => {
socket.destroy();
}));

server.listen(0, () => {
const client = net.connect(server.address().port);
client.write(headers);
// finish the first request
client.write('\r\n');
// second request
client.write(headers);
client.write('X-CRASH: ');

const interval = setInterval(() => {
client.write('a');
}, sendCharEvery);

client.resume();
finished(client, common.mustCall((err) => {
clearInterval(interval);
server.close();
}));
});

0 comments on commit 05534a2

Please sign in to comment.