Skip to content

Commit ee463d3

Browse files
trevnorrisaddaleax
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: #11947 Reviewed-By: Franziska Hinkelmann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 0db49fe commit ee463d3

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
@@ -85,6 +85,7 @@ TLSWrap::TLSWrap(Environment* env,
8585
stream_->set_after_write_cb({ OnAfterWriteImpl, this });
8686
stream_->set_alloc_cb({ OnAllocImpl, this });
8787
stream_->set_read_cb({ OnReadImpl, this });
88+
stream_->set_destruct_cb({ OnDestructImpl, this });
8889

8990
set_alloc_cb({ OnAllocSelf, this });
9091
set_read_cb({ OnReadSelf, this });
@@ -687,6 +688,12 @@ void TLSWrap::OnReadImpl(ssize_t nread,
687688
}
688689

689690

691+
void TLSWrap::OnDestructImpl(void* ctx) {
692+
TLSWrap* wrap = static_cast<TLSWrap*>(ctx);
693+
wrap->clear_stream();
694+
}
695+
696+
690697
void TLSWrap::OnAllocSelf(size_t suggested_size, uv_buf_t* buf, void* ctx) {
691698
buf->base = node::Malloc(suggested_size);
692699
buf->len = suggested_size;

src/tls_wrap.h

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

7676
size_t self_size() const override { return sizeof(*this); }
7777

78+
void clear_stream() { stream_ = nullptr; }
79+
7880
protected:
7981
static const int kClearOutChunkSize = 16384;
8082

@@ -142,6 +144,7 @@ class TLSWrap : public AsyncWrap,
142144
const uv_buf_t* buf,
143145
uv_handle_type pending,
144146
void* ctx);
147+
static void OnDestructImpl(void* ctx);
145148

146149
void DoRead(ssize_t nread, const uv_buf_t* buf, uv_handle_type pending);
147150

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)