Skip to content

Commit c2aea93

Browse files
committed
_stream_wrap: prevent use after free in TLS
Queued write requests should be invoked on handle close, otherwise the "consumer" might be already destroyed when the write callbacks of the "consumed" handle will be invoked. Fix: nodejs#1696
1 parent 58e914f commit c2aea93

File tree

4 files changed

+103
-7
lines changed

4 files changed

+103
-7
lines changed

lib/_stream_wrap.js

+69-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ function StreamWrap(stream) {
1010

1111
this.stream = stream;
1212

13+
this.queue = null;
14+
1315
var self = this;
1416
handle.close = function(cb) {
15-
cb();
17+
self.close(cb);
1618
};
1719
handle.isAlive = function() {
1820
return self.isAlive();
@@ -35,10 +37,12 @@ function StreamWrap(stream) {
3537

3638
this.stream.pause();
3739
this.stream.on('data', function(chunk) {
38-
self._handle.readBuffer(chunk);
40+
if (self._handle)
41+
self._handle.readBuffer(chunk);
3942
});
4043
this.stream.once('end', function() {
41-
self._handle.emitEOF();
44+
if (self._handle)
45+
self._handle.emitEOF();
4246
});
4347
this.stream.on('error', function(err) {
4448
self.emit('error', err);
@@ -88,6 +92,9 @@ StreamWrap.prototype.write = function write(req, bufs) {
8892
var pending = bufs.length;
8993
var self = this;
9094

95+
// Queue the request to be able to cancel it
96+
self.enqueue(req);
97+
9198
self.stream.cork();
9299
bufs.forEach(function(buf) {
93100
self.stream.write(buf, done);
@@ -103,6 +110,10 @@ StreamWrap.prototype.write = function write(req, bufs) {
103110

104111
// Ensure that write was dispatched
105112
setImmediate(function() {
113+
// Do not invoke callback twice
114+
if (!self.dequeue(req))
115+
return;
116+
106117
var errCode = 0;
107118
if (err) {
108119
if (err.code && uv['UV_' + err.code])
@@ -118,3 +129,58 @@ StreamWrap.prototype.write = function write(req, bufs) {
118129

119130
return 0;
120131
};
132+
133+
StreamWrap.prototype.enqueue = function enqueue(req) {
134+
if (this.queue === null) {
135+
this.queue = req;
136+
req._prev = req;
137+
req._next = req;
138+
return;
139+
}
140+
141+
req._next = this.queue._next;
142+
this.queue._next._prev = req;
143+
req._prev = this.queue;
144+
this.queue._next = req;
145+
};
146+
147+
StreamWrap.prototype.dequeue = function dequeue(req) {
148+
var next = req._next;
149+
var prev = req._prev;
150+
151+
if (next === null && prev === null)
152+
return false;
153+
154+
req._next = null;
155+
req._prev = null;
156+
157+
if (next === prev) {
158+
this.queue = null;
159+
} else {
160+
prev._next = next;
161+
next._prev = prev;
162+
163+
if (this.queue === req)
164+
this.queue = next;
165+
}
166+
167+
return true;
168+
};
169+
170+
StreamWrap.prototype.close = function close(cb) {
171+
var self = this;
172+
173+
setImmediate(function() {
174+
while (self.queue !== null) {
175+
var req = self.queue;
176+
self.dequeue(req);
177+
178+
var errCode = uv.UV_ECANCELED;
179+
self._handle.doAfterWrite(req);
180+
self._handle.finishWrite(req, errCode);
181+
}
182+
183+
self._handle = null;
184+
cb();
185+
});
186+
};

src/js_stream.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ template <class Wrap>
163163
void JSStream::Finish(const FunctionCallbackInfo<Value>& args) {
164164
Wrap* w = Unwrap<Wrap>(args[0].As<Object>());
165165

166-
w->Done(args[0]->Int32Value());
166+
w->Done(args[1]->Int32Value());
167167
}
168168

169169

src/tls_wrap.cc

+4-3
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ void TLSWrap::EncOutCb(WriteWrap* req_wrap, int status) {
320320
TLSWrap* wrap = req_wrap->wrap()->Cast<TLSWrap>();
321321
req_wrap->Dispose();
322322

323+
// We should not be getting here after `DestroySSL`, because all queued writes
324+
// must be invoked with UV_ECANCELED
325+
CHECK_NE(wrap->ssl_, nullptr);
326+
323327
// Handle error
324328
if (status) {
325329
// Ignore errors after shutdown
@@ -331,9 +335,6 @@ void TLSWrap::EncOutCb(WriteWrap* req_wrap, int status) {
331335
return;
332336
}
333337

334-
if (wrap->ssl_ == nullptr)
335-
return;
336-
337338
// Commit
338339
NodeBIO::FromBIO(wrap->enc_out_)->Read(nullptr, wrap->write_size_);
339340

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
var assert = require('assert');
3+
var common = require('../common');
4+
5+
if (!common.hasCrypto) {
6+
console.log('1..0 # Skipped: missing crypto');
7+
process.exit();
8+
}
9+
var tls = require('tls');
10+
var stream = require('stream');
11+
12+
var delay = new stream.Duplex({
13+
read: function read() {
14+
},
15+
write: function write(data, enc, cb) {
16+
console.log('pending');
17+
setTimeout(function() {
18+
console.log('done');
19+
cb();
20+
}, 200);
21+
}
22+
});
23+
24+
var secure = tls.connect({
25+
socket: delay
26+
});
27+
setImmediate(function() {
28+
secure.destroy();
29+
});

0 commit comments

Comments
 (0)