From b34bc3ed80cde07547f78729bb23c002df97abe3 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Mon, 31 Oct 2016 16:48:14 -0600 Subject: [PATCH] stream_base,tls_wrap: notify on destruct The TLSWrap constructor is passed a StreamBase* which it stores as TLSWrap::stream_, and is used to receive/send data along the pipeline (e.g. tls -> tcp). Problem is the lifetime of the instance that stream_ points to is independent of the lifetime of the TLSWrap instance. So it's possible for stream_ to be delete'd while the TLSWrap instance is still alive, allowing potential access to a then invalid pointer. Fix by having the StreamBase destructor null out TLSWrap::stream_; allowing all TLSWrap methods that rely on stream_ to do a check to see if it's available. While the test provided is fixed by this commit, it was also previously fixed by 478fabf. Regardless, leave the test in for better testing. PR-URL: https://github.com/nodejs/node/pull/11947 Reviewed-By: Franziska Hinkelmann Reviewed-By: James M Snell Reviewed-By: Anna Henningsen --- src/stream_base.h | 9 +++- src/tls_wrap.cc | 7 ++++ src/tls_wrap.h | 3 ++ .../test-tls-retain-handle-no-abort.js | 42 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-tls-retain-handle-no-abort.js diff --git a/src/stream_base.h b/src/stream_base.h index faddee88c82..35929750bfb 100644 --- a/src/stream_base.h +++ b/src/stream_base.h @@ -146,10 +146,14 @@ class StreamResource { const uv_buf_t* buf, uv_handle_type pending, void* ctx); + typedef void (*DestructCb)(void* ctx); StreamResource() : bytes_read_(0) { } - virtual ~StreamResource() = default; + virtual ~StreamResource() { + if (!destruct_cb_.is_empty()) + destruct_cb_.fn(destruct_cb_.ctx); + } virtual int DoShutdown(ShutdownWrap* req_wrap) = 0; virtual int DoTryWrite(uv_buf_t** bufs, size_t* count); @@ -186,15 +190,18 @@ class StreamResource { inline void set_alloc_cb(Callback c) { alloc_cb_ = c; } inline void set_read_cb(Callback c) { read_cb_ = c; } + inline void set_destruct_cb(Callback c) { destruct_cb_ = c; } inline Callback after_write_cb() { return after_write_cb_; } inline Callback alloc_cb() { return alloc_cb_; } inline Callback read_cb() { return read_cb_; } + inline Callback destruct_cb() { return destruct_cb_; } private: Callback after_write_cb_; Callback alloc_cb_; Callback read_cb_; + Callback destruct_cb_; uint64_t bytes_read_; friend class StreamBase; diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 6a838c610b0..7e2f86e10e8 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -64,6 +64,7 @@ TLSWrap::TLSWrap(Environment* env, stream_->set_after_write_cb({ OnAfterWriteImpl, this }); stream_->set_alloc_cb({ OnAllocImpl, this }); stream_->set_read_cb({ OnReadImpl, this }); + stream_->set_destruct_cb({ OnDestructImpl, this }); set_alloc_cb({ OnAllocSelf, this }); set_read_cb({ OnReadSelf, this }); @@ -660,6 +661,12 @@ void TLSWrap::OnReadImpl(ssize_t nread, } +void TLSWrap::OnDestructImpl(void* ctx) { + TLSWrap* wrap = static_cast(ctx); + wrap->clear_stream(); +} + + void TLSWrap::OnAllocSelf(size_t suggested_size, uv_buf_t* buf, void* ctx) { buf->base = static_cast(node::Malloc(suggested_size)); CHECK_NE(buf->base, nullptr); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 0efdbf0e399..8132d66876f 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -54,6 +54,8 @@ class TLSWrap : public AsyncWrap, size_t self_size() const override { return sizeof(*this); } + void clear_stream() { stream_ = nullptr; } + protected: static const int kClearOutChunkSize = 16384; @@ -121,6 +123,7 @@ class TLSWrap : public AsyncWrap, const uv_buf_t* buf, uv_handle_type pending, void* ctx); + static void OnDestructImpl(void* ctx); void DoRead(ssize_t nread, const uv_buf_t* buf, uv_handle_type pending); diff --git a/test/parallel/test-tls-retain-handle-no-abort.js b/test/parallel/test-tls-retain-handle-no-abort.js new file mode 100644 index 00000000000..43b3709fd5f --- /dev/null +++ b/test/parallel/test-tls-retain-handle-no-abort.js @@ -0,0 +1,42 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +if (!common.hasCrypto) { + common.skip('missing crypto'); + return; +} +const tls = require('tls'); +const fs = require('fs'); +const util = require('util'); + +const sent = 'hello world'; +const serverOptions = { + isServer: true, + key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem') +}; + +let ssl = null; + +process.on('exit', function() { + assert.ok(ssl !== null); + // If the internal pointer to stream_ isn't cleared properly then this + // will abort. + util.inspect(ssl); +}); + +const server = tls.createServer(serverOptions, function(s) { + s.on('data', function() { }); + s.on('end', function() { + server.close(); + s.destroy(); + }); +}).listen(0, function() { + const c = new tls.TLSSocket(); + ssl = c.ssl; + c.connect(this.address().port, function() { + c.end(sent); + }); +});