Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:languages:nodejs
nodejs4
CVE-2018-12122.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File CVE-2018-12122.patch of Package nodejs4
Ported from: From 618eebdd175b598a06bbc4d3d1efeb85e3fa1429 Mon Sep 17 00:00:00 2001 From: Matteo Collina <hello@matteocollina.com> Date: Thu, 23 Aug 2018 16:46:07 +0200 Subject: [PATCH] http,https: protect against slow headers attack CVE-2018-12122 An attacker can send a char/s within headers and exahust the resources (file descriptors) of a system even with a tight max header length protection. This PR destroys a socket if it has not received the headers in 40s. PR-URL: https://github.com/nodejs-private/node-private/pull/152 Ref: https://github.com/nodejs-private/node-private/pull/144 Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com> Index: node-v4.9.1/doc/api/http.md =================================================================== --- node-v4.9.1.orig/doc/api/http.md +++ node-v4.9.1/doc/api/http.md @@ -704,6 +704,26 @@ for handling socket timeouts. Returns `server`. +### server.headersTimeout +<!-- YAML +added: REPLACEME +--> + +* {number} **Default:** `40000` + +Limit the amount of time the parser will wait to receive the complete HTTP +headers. + +In case of inactivity, the rules defined in [server.timeout][] apply. However, +that inactivity based timeout would still allow the connection to be kept open +if the headers are being sent very slowly (by default, up to a byte per 2 +minutes). In order to prevent this, whenever header data arrives an additional +check is made that more than `server.headersTimeout` milliseconds has not +passed since the connection was established. If the check fails, a `'timeout'` +event is emitted on the server object, and (by default) the socket is destroyed. +See [server.timeout][] for more information on how timeout behaviour can be +customised. + ### server.timeout <!-- YAML added: v0.9.12 Index: node-v4.9.1/doc/api/https.md =================================================================== --- node-v4.9.1.orig/doc/api/https.md +++ node-v4.9.1/doc/api/https.md @@ -21,6 +21,12 @@ added: v0.3.4 This class is a subclass of `tls.Server` and emits events same as [`http.Server`][]. See [`http.Server`][] for more information. +### server.headersTimeout + +- {number} **Default:** `40000` + +See [`http.Server#headersTimeout`][]. + ### server.setTimeout(msecs, callback) <!-- YAML added: v0.11.2 @@ -257,6 +263,7 @@ var req = https.request(options, (res) = [`Buffer`]: buffer.html#buffer_buffer [`globalAgent`]: #https_https_globalagent [`http.Agent`]: http.html#http_class_http_agent +[`http.Server#headersTimeout`]: http.html#http_server_headerstimeout [`http.close()`]: http.html#http_server_close_callback [`http.get()`]: http.html#http_http_get_options_callback [`http.listen()`]: http.html#http_server_listen_port_hostname_backlog_callback Index: node-v4.9.1/lib/_http_outgoing.js =================================================================== --- node-v4.9.1.orig/lib/_http_outgoing.js +++ node-v4.9.1/lib/_http_outgoing.js @@ -31,20 +31,33 @@ const automaticHeaders = { }; -var dateCache; +var nowCache; +var utcCache; + +function nowDate() { + if (!nowCache) cache(); + return nowCache; +} + function utcDate() { - if (!dateCache) { - var d = new Date(); - dateCache = d.toUTCString(); - timers.enroll(utcDate, 1000 - d.getMilliseconds()); - timers._unrefActive(utcDate); - } - return dateCache; + if (!utcCache) cache(); + return utcCache; } -utcDate._onTimeout = function() { - dateCache = undefined; + +function cache() { + const d = new Date(); + nowCache = d.valueOf(); + utcCache = d.toUTCString(); + timers.enroll(cache, 1000 - d.getMilliseconds()); + timers._unrefActive(cache); +} + +cache._onTimeout = function() { + nowCache = undefined; + utcCache = undefined; }; +exports.nowDate = nowDate; function OutgoingMessage() { Stream.call(this); Index: node-v4.9.1/lib/_http_server.js =================================================================== --- node-v4.9.1.orig/lib/_http_server.js +++ node-v4.9.1/lib/_http_server.js @@ -14,6 +14,7 @@ const continueExpression = common.contin const chunkExpression = common.chunkExpression; const httpSocketSetup = common.httpSocketSetup; const OutgoingMessage = require('_http_outgoing').OutgoingMessage; +const nowDate = require('_http_outgoing').nowDate; const STATUS_CODES = exports.STATUS_CODES = { 100: 'Continue', @@ -251,6 +252,7 @@ function Server(requestListener) { this.timeout = 2 * 60 * 1000; this._pendingResponseData = 0; + this.headersTimeout = 40 * 1000; // 40 seconds } util.inherits(Server, net.Server); @@ -324,6 +326,9 @@ function connectionListener(socket) { var parser = parsers.alloc(); parser.reinitialize(HTTPParser.REQUEST); parser.socket = socket; + + // We are starting to wait for our headers. + parser.parsingHeadersStart = nowDate(); socket.parser = parser; parser.incoming = null; @@ -376,6 +381,20 @@ function connectionListener(socket) { function onParserExecute(ret, d) { socket._unrefTimer(); debug('SERVER socketOnParserExecute %d', ret); + + var start = parser.parsingHeadersStart; + + // If we have not parsed the headers, destroy the socket + // after server.headersTimeout to protect from DoS attacks. + // start === 0 means that we have parsed headers. + if (start !== 0 && nowDate() - start > self.headersTimeout) { + var serverTimeout = self.emit('timeout', socket); + + if (!serverTimeout) + socket.destroy(); + return; + } + onParserExecuteCommon(ret, undefined); } @@ -444,7 +463,6 @@ function connectionListener(socket) { } } - // The following callback is issued after the headers have been read on a // new message. In this callback we setup the response object and pass it // to the user. Index: node-v4.9.1/lib/https.js =================================================================== --- node-v4.9.1.orig/lib/https.js +++ node-v4.9.1/lib/https.js @@ -34,6 +34,8 @@ function Server(opts, requestListener) { }); this.timeout = 2 * 60 * 1000; + + this.headersTimeout = 40 * 1000; // 40 seconds } inherits(Server, tls.Server); exports.Server = Server; Index: node-v4.9.1/test/parallel/test-http-slow-headers.js =================================================================== --- /dev/null +++ node-v4.9.1/test/parallel/test-http-slow-headers.js @@ -0,0 +1,56 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const createServer = require('http').createServer; +const connect = require('net').connect; + +// This test validates that the 'timeout' event fires +// after server.headersTimeout. + +const headers = + 'GET / HTTP/1.1\r\n' + + 'Host: localhost\r\n' + + 'Agent: node\r\n'; + +const server = createServer(common.mustNotCall()); +let sendCharEvery = 1000; + +// 40 seconds is the default +assert.strictEqual(server.headersTimeout, 40 * 1000); + +// 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, common.mustCall(() => { + const client = connect(server.address().port); + client.write(headers); + client.write('X-CRASH: '); + + const interval = setInterval(() => { + client.write('a'); + }, sendCharEvery); + + client.resume(); + + const onClose = common.mustCall(() => { + client.removeListener('close', onClose); + client.removeListener('error', onClose); + client.removeListener('end', onClose); + clearInterval(interval); + server.close(); + }); + + client.on('error', onClose); + client.on('close', onClose); + client.on('end', onClose); +})); Index: node-v4.9.1/test/parallel/test-https-slow-headers.js =================================================================== --- /dev/null +++ node-v4.9.1/test/parallel/test-https-slow-headers.js @@ -0,0 +1,80 @@ +'use strict'; + +const common = require('../common'); +const path = require('path'); +const fs = require('fs'); + +const fixturesDir = path.join(__dirname, '..', 'fixtures'); + +function fixturesPath(p1, p2) { + return path.join(fixturesDir, p1, p2); +} + +function readKey(name, enc) { + return fs.readFileSync(fixturesPath('keys', name), enc); +} + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const createServer = require('https').createServer; +const connect = require('tls').connect; + +// This test validates that the 'timeout' event fires +// after server.headersTimeout. + +const headers = + 'GET / HTTP/1.1\r\n' + + 'Host: localhost\r\n' + + 'Agent: node\r\n'; + +const server = createServer({ + key: readKey('agent1-key.pem'), + cert: readKey('agent1-cert.pem'), + ca: readKey('ca1-cert.pem'), +}, common.mustNotCall()); + +let sendCharEvery = 1000; + +// 40 seconds is the default +assert.strictEqual(server.headersTimeout, 40 * 1000); + +// 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, common.mustCall(() => { + const client = connect({ + port: server.address().port, + rejectUnauthorized: false + }); + client.write(headers); + client.write('X-CRASH: '); + + const interval = setInterval(() => { + client.write('a'); + }, sendCharEvery); + + client.resume(); + + const onClose = common.mustCall(() => { + client.removeListener('close', onClose); + client.removeListener('error', onClose); + client.removeListener('end', onClose); + clearInterval(interval); + server.close(); + }); + + client.on('error', onClose); + client.on('close', onClose); + client.on('end', onClose); +}));
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor