Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 36.4k
_stream_wrap: prevent use after free in TLS#1910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
c2aea938d46c7bb349032b27ff9494c12fe6cdda318375f7829b371395bc05aaff1b5fb6444ee3d3709bFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,23 @@ | ||
| 'use strict'; | ||
| const assert = require('assert'); | ||
| const util = require('util'); | ||
| const Socket = require('net').Socket; | ||
| const JSStream = process.binding('js_stream').JSStream; | ||
| const uv = process.binding('uv'); | ||
| const debug = util.debuglog('stream_wrap'); | ||
| function StreamWrap(stream) { | ||
| var handle = new JSStream(); | ||
| const handle = new JSStream(); | ||
| this.stream = stream; | ||
| var self = this; | ||
| this._queue = null; | ||
| const self = this; | ||
| handle.close = function(cb) { | ||
| cb(); | ||
| debug('close'); | ||
| self.doClose(cb); | ||
| }; | ||
| handle.isAlive = function() { | ||
| return self.isAlive(); | ||
| @@ -27,21 +32,29 @@ function StreamWrap(stream) { | ||
| return self.readStop(); | ||
| }; | ||
| handle.onshutdown = function(req) { | ||
| return self.shutdown(req); | ||
| return self.doShutdown(req); | ||
| }; | ||
| handle.onwrite = function(req, bufs) { | ||
| return self.write(req, bufs); | ||
| return self.doWrite(req, bufs); | ||
| }; | ||
| this.stream.pause(); | ||
| this.stream.on('error', function(err) { | ||
| self.emit('error', err); | ||
| }); | ||
| this.stream.on('data', function(chunk) { | ||
| self._handle.readBuffer(chunk); | ||
| setImmediate(function() { | ||
| debug('data', chunk.length); | ||
| if (self._handle) | ||
| self._handle.readBuffer(chunk); | ||
| }); | ||
| }); | ||
| this.stream.once('end', function() { | ||
| self._handle.emitEOF(); | ||
| }); | ||
| this.stream.on('error', function(err) { | ||
| self.emit('error', err); | ||
| setImmediate(function() { | ||
| debug('end'); | ||
| if (self._handle) | ||
| self._handle.emitEOF(); | ||
| }); | ||
| }); | ||
| Socket.call(this, { | ||
| @@ -55,11 +68,11 @@ module.exports = StreamWrap; | ||
| StreamWrap.StreamWrap = StreamWrap; | ||
| StreamWrap.prototype.isAlive = function isAlive() { | ||
| return this.readable && this.writable; | ||
| return true; | ||
| }; | ||
| StreamWrap.prototype.isClosing = function isClosing() { | ||
| return !this.isAlive(); | ||
| return !this.readable || !this.writable; | ||
| }; | ||
| StreamWrap.prototype.readStart = function readStart() { | ||
| @@ -72,21 +85,31 @@ StreamWrap.prototype.readStop = function readStop() { | ||
| return 0; | ||
| }; | ||
| StreamWrap.prototype.shutdown = function shutdown(req) { | ||
| var self = this; | ||
| StreamWrap.prototype.doShutdown = function doShutdown(req) { | ||
| const self = this; | ||
| const handle = this._handle; | ||
| const item = this._enqueue('shutdown', req); | ||
| this.stream.end(function() { | ||
| // Ensure that write was dispatched | ||
| setImmediate(function() { | ||
| self._handle.finishShutdown(req, 0); | ||
| if (!self._dequeue(item)) | ||
| return; | ||
| handle.finishShutdown(req, 0); | ||
| }); | ||
| }); | ||
| return 0; | ||
| }; | ||
| StreamWrap.prototype.write = function write(req, bufs) { | ||
| StreamWrap.prototype.doWrite = function doWrite(req, bufs) { | ||
| const self = this; | ||
| const handle = self._handle; | ||
| var pending = bufs.length; | ||
| var self = this; | ||
| // Queue the request to be able to cancel it | ||
| const item = self._enqueue('write', req); | ||
| self.stream.cork(); | ||
| bufs.forEach(function(buf) { | ||
| @@ -103,6 +126,10 @@ StreamWrap.prototype.write = function write(req, bufs) { | ||
| // Ensure that write was dispatched | ||
| setImmediate(function() { | ||
| // Do not invoke callback twice | ||
| if (!self._dequeue(item)) | ||
| return; | ||
| var errCode = 0; | ||
| if (err) { | ||
| if (err.code && uv['UV_' + err.code]) | ||
| @@ -111,10 +138,81 @@ StreamWrap.prototype.write = function write(req, bufs) { | ||
| errCode = uv.UV_EPIPE; | ||
| } | ||
| self._handle.doAfterWrite(req); | ||
| self._handle.finishWrite(req, errCode); | ||
| handle.doAfterWrite(req); | ||
| handle.finishWrite(req, errCode); | ||
| }); | ||
| } | ||
| return 0; | ||
| }; | ||
| function QueueItem(type, req) { | ||
| this.type = type; | ||
| this.req = req; | ||
| this.prev = this; | ||
| this.next = this; | ||
| } | ||
| StreamWrap.prototype._enqueue = function enqueue(type, req) { | ||
| const item = new QueueItem(type, req); | ||
| if (this._queue === null) { | ||
| this._queue = item; | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be changed to MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! Thanks for the suggestion! Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I had the exact same suggestion in mind when I reviewed it last week. +1 | ||
| return item; | ||
| } | ||
| item.next = this._queue.next; | ||
| item.prev = this._queue; | ||
| item.next.prev = item; | ||
| item.prev.next = item; | ||
| return item; | ||
| }; | ||
| StreamWrap.prototype._dequeue = function dequeue(item) { | ||
| var next = item.next; | ||
| var prev = item.prev; | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is when I wish we allowed macros. A simple | ||
| if (next === null && prev === null) | ||
| return false; | ||
| item.next = null; | ||
| item.prev = null; | ||
| if (next === item) { | ||
| prev = null; | ||
| next = null; | ||
| } else { | ||
| prev.next = next; | ||
| next.prev = prev; | ||
| } | ||
| if (this._queue === item) | ||
| this._queue = next; | ||
| return true; | ||
| }; | ||
| StreamWrap.prototype.doClose = function doClose(cb) { | ||
| const self = this; | ||
| const handle = self._handle; | ||
| setImmediate(function() { | ||
| while (self._queue !== null) { | ||
| const item = self._queue; | ||
| const req = item.req; | ||
| self._dequeue(item); | ||
| const errCode = uv.UV_ECANCELED; | ||
| if (item.type === 'write') { | ||
| handle.doAfterWrite(req); | ||
| handle.finishWrite(req, errCode); | ||
| } else if (item.type === 'shutdown') { | ||
| handle.finishShutdown(req, errCode); | ||
| } | ||
| } | ||
| // Should be already set by net.js | ||
| assert(self._handle === null); | ||
| cb(); | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| 'use strict'; | ||
| const common = require('../common'); | ||
| const assert = require('assert'); | ||
| const StreamWrap = require('_stream_wrap'); | ||
| const Duplex = require('stream').Duplex; | ||
| const ShutdownWrap = process.binding('stream_wrap').ShutdownWrap; | ||
| var done = false; | ||
| function testShutdown(callback) { | ||
| var stream = new Duplex({ | ||
| read: function() { | ||
| }, | ||
| write: function() { | ||
| } | ||
| }); | ||
| var wrap = new StreamWrap(stream); | ||
| var req = new ShutdownWrap(); | ||
| req.oncomplete = function(code) { | ||
| assert(code < 0); | ||
| callback(); | ||
| }; | ||
| req.handle = wrap._handle; | ||
| // Close the handle to simulate | ||
| wrap.destroy(); | ||
| req.handle.shutdown(req); | ||
| } | ||
| testShutdown(function() { | ||
| done = true; | ||
| }); | ||
| process.on('exit', function() { | ||
| assert(done); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: throw in a comment on why the
setImmediate()is necessary. future proofing for new devs. :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, let's drop it out, and revert it back only in case of any troubles. I no longer think that it might be reasonable. (@EricTheOne: I hope you don't mind)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@indutny I don't understand the code enough to comment on the code level, but I'll retest the final version and let you know if anything new comes up.
btw just to further my understanding, why would setImmediate be resolving possible troubles there?