From 1a7302bd48593cd95473600b153022dda2b811a1 Mon Sep 17 00:00:00 2001
From: Matteo Collina <hello@matteocollina.com>
Date: Sat, 1 Dec 2018 16:29:13 +0100
Subject: [PATCH] http: prevent slowloris with keepalive connections

Fixes: https://github.com/nodejs-private/security/issues/214
PR-URL: https://github.com/nodejs-private/node-private/pull/158
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
---
 lib/_http_server.js                           | 15 ++++++
 .../test-http-slow-headers-keepalive.js       | 51 +++++++++++++++++++
 2 files changed, 66 insertions(+)
 create mode 100644 test/parallel/test-http-slow-headers-keepalive.js

diff --git a/lib/_http_server.js b/lib/_http_server.js
index 797597aa9f71..2c7c6c29b864 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -606,6 +606,10 @@ function resOnFinish(req, res, socket, state, server) {
 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;
 
@@ -730,6 +734,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,
diff --git a/test/parallel/test-http-slow-headers-keepalive.js b/test/parallel/test-http-slow-headers-keepalive.js
new file mode 100644
index 000000000000..5552f33f77e3
--- /dev/null
+++ b/test/parallel/test-http-slow-headers-keepalive.js
@@ -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();
+  }));
+});
