Skip to content

Commit ff1d61c

Browse files
trevnorrisMylesBorins
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 99749dc commit ff1d61c

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
@@ -135,10 +135,14 @@ class StreamResource {
135135
const uv_buf_t* buf,
136136
uv_handle_type pending,
137137
void* ctx);
138+
typedef void (*DestructCb)(void* ctx);
138139

139140
StreamResource() : bytes_read_(0) {
140141
}
141-
virtual ~StreamResource() = default;
142+
virtual ~StreamResource() {
143+
if (!destruct_cb_.is_empty())
144+
destruct_cb_.fn(destruct_cb_.ctx);
145+
}
142146

143147
virtual int DoShutdown(ShutdownWrap* req_wrap) = 0;
144148
virtual int DoTryWrite(uv_buf_t** bufs, size_t* count);
@@ -175,15 +179,18 @@ class StreamResource {
175179

176180
inline void set_alloc_cb(Callback<AllocCb> c) { alloc_cb_ = c; }
177181
inline void set_read_cb(Callback<ReadCb> c) { read_cb_ = c; }
182+
inline void set_destruct_cb(Callback<DestructCb> c) { destruct_cb_ = c; }
178183

179184
inline Callback<AfterWriteCb> after_write_cb() { return after_write_cb_; }
180185
inline Callback<AllocCb> alloc_cb() { return alloc_cb_; }
181186
inline Callback<ReadCb> read_cb() { return read_cb_; }
187+
inline Callback<DestructCb> destruct_cb() { return destruct_cb_; }
182188

183189
private:
184190
Callback<AfterWriteCb> after_write_cb_;
185191
Callback<AllocCb> alloc_cb_;
186192
Callback<ReadCb> read_cb_;
193+
Callback<DestructCb> destruct_cb_;
187194
uint64_t bytes_read_;
188195

189196
friend class StreamBase;

src/tls_wrap.cc

+7
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ TLSWrap::TLSWrap(Environment* env,
6666
stream_->set_after_write_cb({ OnAfterWriteImpl, this });
6767
stream_->set_alloc_cb({ OnAllocImpl, this });
6868
stream_->set_read_cb({ OnReadImpl, this });
69+
stream_->set_destruct_cb({ OnDestructImpl, this });
6970

7071
set_alloc_cb({ OnAllocSelf, this });
7172
set_read_cb({ OnReadSelf, this });
@@ -661,6 +662,12 @@ void TLSWrap::OnReadImpl(ssize_t nread,
661662
}
662663

663664

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

src/tls_wrap.h

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

5353
size_t self_size() const override { return sizeof(*this); }
5454

55+
void clear_stream() { stream_ = nullptr; }
56+
5557
protected:
5658
static const int kClearOutChunkSize = 16384;
5759

@@ -119,6 +121,7 @@ class TLSWrap : public AsyncWrap,
119121
const uv_buf_t* buf,
120122
uv_handle_type pending,
121123
void* ctx);
124+
static void OnDestructImpl(void* ctx);
122125

123126
void DoRead(ssize_t nread, const uv_buf_t* buf, uv_handle_type pending);
124127

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)