Skip to content

Commit b34bc3e

Browse files
trevnorrisandrew749
authored andcommitted
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: nodejs/node#11947 Reviewed-By: Franziska Hinkelmann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 1c6e372 commit b34bc3e

File tree

4 files changed

+60
-1
lines changed

4 files changed

+60
-1
lines changed

src/stream_base.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,14 @@ class StreamResource {
146146
const uv_buf_t* buf,
147147
uv_handle_type pending,
148148
void* ctx);
149+
typedef void (*DestructCb)(void* ctx);
149150

150151
StreamResource() : bytes_read_(0) {
151152
}
152-
virtual ~StreamResource() = default;
153+
virtual ~StreamResource() {
154+
if (!destruct_cb_.is_empty())
155+
destruct_cb_.fn(destruct_cb_.ctx);
156+
}
153157

154158
virtual int DoShutdown(ShutdownWrap* req_wrap) = 0;
155159
virtual int DoTryWrite(uv_buf_t** bufs, size_t* count);
@@ -186,15 +190,18 @@ class StreamResource {
186190

187191
inline void set_alloc_cb(Callback<AllocCb> c) { alloc_cb_ = c; }
188192
inline void set_read_cb(Callback<ReadCb> c) { read_cb_ = c; }
193+
inline void set_destruct_cb(Callback<DestructCb> c) { destruct_cb_ = c; }
189194

190195
inline Callback<AfterWriteCb> after_write_cb() { return after_write_cb_; }
191196
inline Callback<AllocCb> alloc_cb() { return alloc_cb_; }
192197
inline Callback<ReadCb> read_cb() { return read_cb_; }
198+
inline Callback<DestructCb> destruct_cb() { return destruct_cb_; }
193199

194200
private:
195201
Callback<AfterWriteCb> after_write_cb_;
196202
Callback<AllocCb> alloc_cb_;
197203
Callback<ReadCb> read_cb_;
204+
Callback<DestructCb> destruct_cb_;
198205
uint64_t bytes_read_;
199206

200207
friend class StreamBase;

src/tls_wrap.cc

+7
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ TLSWrap::TLSWrap(Environment* env,
6464
stream_->set_after_write_cb({ OnAfterWriteImpl, this });
6565
stream_->set_alloc_cb({ OnAllocImpl, this });
6666
stream_->set_read_cb({ OnReadImpl, this });
67+
stream_->set_destruct_cb({ OnDestructImpl, this });
6768

6869
set_alloc_cb({ OnAllocSelf, this });
6970
set_read_cb({ OnReadSelf, this });
@@ -660,6 +661,12 @@ void TLSWrap::OnReadImpl(ssize_t nread,
660661
}
661662

662663

664+
void TLSWrap::OnDestructImpl(void* ctx) {
665+
TLSWrap* wrap = static_cast<TLSWrap*>(ctx);
666+
wrap->clear_stream();
667+
}
668+
669+
663670
void TLSWrap::OnAllocSelf(size_t suggested_size, uv_buf_t* buf, void* ctx) {
664671
buf->base = static_cast<char*>(node::Malloc(suggested_size));
665672
CHECK_NE(buf->base, nullptr);

src/tls_wrap.h

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class TLSWrap : public AsyncWrap,
5454

5555
size_t self_size() const override { return sizeof(*this); }
5656

57+
void clear_stream() { stream_ = nullptr; }
58+
5759
protected:
5860
static const int kClearOutChunkSize = 16384;
5961

@@ -121,6 +123,7 @@ class TLSWrap : public AsyncWrap,
121123
const uv_buf_t* buf,
122124
uv_handle_type pending,
123125
void* ctx);
126+
static void OnDestructImpl(void* ctx);
124127

125128
void DoRead(ssize_t nread, const uv_buf_t* buf, uv_handle_type pending);
126129

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
if (!common.hasCrypto) {
7+
common.skip('missing crypto');
8+
return;
9+
}
10+
const tls = require('tls');
11+
const fs = require('fs');
12+
const util = require('util');
13+
14+
const sent = 'hello world';
15+
const serverOptions = {
16+
isServer: true,
17+
key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
18+
cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem')
19+
};
20+
21+
let ssl = null;
22+
23+
process.on('exit', function() {
24+
assert.ok(ssl !== null);
25+
// If the internal pointer to stream_ isn't cleared properly then this
26+
// will abort.
27+
util.inspect(ssl);
28+
});
29+
30+
const server = tls.createServer(serverOptions, function(s) {
31+
s.on('data', function() { });
32+
s.on('end', function() {
33+
server.close();
34+
s.destroy();
35+
});
36+
}).listen(0, function() {
37+
const c = new tls.TLSSocket();
38+
ssl = c.ssl;
39+
c.connect(this.address().port, function() {
40+
c.end(sent);
41+
});
42+
});

0 commit comments

Comments
 (0)