Showing with 250 additions and 1 deletion.
  1. +35 −1 lib/http.js
  2. +10 −0 src/node_crypto.cc
  3. +11 −0 src/node_http_parser.cc
  4. +79 −0 test/simple/test-crypto-verify-failure.js
  5. +115 −0 test/simple/test-http-pipeline-flood.js
@@ -150,7 +150,7 @@ function parserOnMessageComplete() {
}
}

if (parser.socket.readable) {
if (parser.socket.readable && !parser.socket._drain_paused) {
// force to read the next incoming message
parser.socket.resume();
}
@@ -1828,6 +1828,7 @@ function connectionListener(socket) {
});

socket.ondata = function(d, start, end) {
assert(!socket._drain_paused);
var ret = parser.execute(d, start, end - start);
if (ret instanceof Error) {
debug('parse error');
@@ -1854,6 +1855,12 @@ function connectionListener(socket) {
socket.destroy();
}
}

if (socket._drain_paused) {
// onIncoming paused the socket, we should pause the parser as well
debug('pause parser');
socket.parser.pause();
}
};

socket.onend = function() {
@@ -1882,9 +1889,36 @@ 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.

socket._drain_paused = false;
function socketOnDrain() {
// If we previously paused, then start reading again.
if (socket._drain_paused) {
socket._drain_paused = false;
socket.parser.resume();
socket.resume();
}
}
socket.on('drain', socketOnDrain);

parser.onIncoming = function(req, shouldKeepAlive) {
incoming.push(req);

// If the writable end isn't consuming, then stop reading
// so that we don't become overwhelmed by a flood of
// pipelined requests that may never be resolved.

if (!socket._drain_paused && socket._handle) {
var needPause = socket._handle.writeQueueSize > 0;
if (needPause) {
socket._drain_paused = true;
// We also need to pause the parser, but don't do that until after
// the call to execute, because we may still be processing the last
// chunk.
socket.pause();
}
}

var res = new ServerResponse(req);
debug('server response shouldKeepAlive: ' + shouldKeepAlive);
res.shouldKeepAlive = shouldKeepAlive;
@@ -69,6 +69,14 @@ namespace crypto {

using namespace v8;

// Forcibly clear OpenSSL's error stack on return. This stops stale errors
// from popping up later in the lifecycle of crypto operations where they
// would cause spurious failures. It's a rather blunt method, though.
// ERR_clear_error() isn't necessarily cheap either.
struct ClearErrorOnReturn {
~ClearErrorOnReturn() { ERR_clear_error(); }
};

static Persistent<String> errno_symbol;
static Persistent<String> syscall_symbol;
static Persistent<String> subject_symbol;
@@ -3406,6 +3414,8 @@ class Verify : public ObjectWrap {
int VerifyFinal(char* key_pem, int key_pemLen, unsigned char* sig, int siglen) {
if (!initialised_) return 0;

ClearErrorOnReturn clear_error_on_return;

EVP_PKEY* pkey = NULL;
BIO *bp = NULL;
X509 *x509 = NULL;
@@ -489,6 +489,15 @@ class Parser : public ObjectWrap {
}


template <bool should_pause>
static Handle<Value> Pause(const Arguments& args) {
HandleScope scope;
Parser* parser = ObjectWrap::Unwrap<Parser>(args.This());
http_parser_pause(&parser->parser_, should_pause);
return Undefined();
}


private:

Local<Array> CreateHeaders() {
@@ -564,6 +573,8 @@ void InitHttpParser(Handle<Object> target) {
NODE_SET_PROTOTYPE_METHOD(t, "execute", Parser::Execute);
NODE_SET_PROTOTYPE_METHOD(t, "finish", Parser::Finish);
NODE_SET_PROTOTYPE_METHOD(t, "reinitialize", Parser::Reinitialize);
NODE_SET_PROTOTYPE_METHOD(t, "pause", Parser::Pause<true>);
NODE_SET_PROTOTYPE_METHOD(t, "resume", Parser::Pause<false>);

target->Set(String::NewSymbol("HTTPParser"), t->GetFunction());

@@ -0,0 +1,79 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.




var common = require('../common');
var assert = require('assert');

try {
var crypto = require('crypto');
var tls = require('tls');
} catch (e) {
console.log('Not compiled with OPENSSL support.');
process.exit();
}

crypto.DEFAULT_ENCODING = 'buffer';

var fs = require('fs');

var certPem = fs.readFileSync(common.fixturesDir + '/test_cert.pem', 'ascii');

var options = {
key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem')
};

var canSend = true;

var server = tls.Server(options, function(socket) {
process.nextTick(function() {
console.log('sending');
socket.destroy();
verify();
});
});

var client;

function verify() {
console.log('verify');
var verified = crypto.createVerify('RSA-SHA1')
.update('Test')
.verify(certPem, 'asdfasdfas', 'base64');
}

server.listen(common.PORT, function() {
client = tls.connect({
port: common.PORT,
rejectUnauthorized: false
}, function() {
verify();
}).on('data', function(data) {
console.log(data);
}).on('error', function(err) {
throw err;
}).on('close', function() {
server.close();
}).resume();
});
@@ -0,0 +1,115 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var common = require('../common');
var assert = require('assert');

switch (process.argv[2]) {
case undefined:
return parent();
case 'child':
return child();
default:
throw new Error('wtf');
}

function parent() {
var http = require('http');
var bigResponse = new Buffer(10240)
bigResponse.fill('x');
var gotTimeout = false;
var childClosed = false;
var requests = 0;
var connections = 0;

var server = http.createServer(function(req, res) {
requests++;
res.setHeader('content-length', bigResponse.length);
res.end(bigResponse);
});

server.on('connection', function(conn) {
connections++;
// kill the connection after a bit, verifying that the
// flood of requests was eventually halted.
console.log('got connection');
setTimeout(function() {
gotTimeout = true;
conn.destroy();
}, 200);
});


server.listen(common.PORT, function() {
var spawn = require('child_process').spawn;
var args = [__filename, 'child'];
var child = spawn(process.execPath, args, { stdio: 'inherit' });
child.on('exit', function(code) {
assert(!code);
childClosed = true;
server.close();
});
});

process.on('exit', function() {
assert(gotTimeout);
assert(childClosed);
assert.equal(connections, 1);
// 1213 works out to be the number of requests we end up processing
// before the outgoing connection backs up and requires a drain.
// however, to avoid being unnecessarily tied to a specific magic number,
// and making the test brittle, just assert that it's "a lot", which we
// can safely assume is more than 500.
assert(requests >= 500);
console.log('ok');
});
}

function child() {
var net = require('net');

var gotEpipe = false;
var conn = net.connect({ port: common.PORT });

var req = 'GET / HTTP/1.1\r\nHost: localhost:' +
common.PORT + '\r\nAccept: */*\r\n\r\n';

req = new Array(10241).join(req);

conn.on('connect', function() {
write();
});

conn.on('drain', write);

conn.on('error', function(er) {
gotEpipe = true;
});

process.on('exit', function() {
assert(gotEpipe);
console.log('ok - child');
});

function write() {
while (false !== conn.write(req, 'ascii'));
}
}