-
Notifications
You must be signed in to change notification settings - Fork 30k
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
buffer: fix segfaults when this
is not a Buffer.
#1486
Conversation
Won't this technically be a problem for any of the prototype methods, not just |
If don't overwrite |
@mscdex I would consider But, yes, it's a problem, none of the C++ functions typecheck |
I think that's all of them! It is not safe to check Also, there were a few instances where there was a redundant |
this
is not a Buffer.this
is not a Buffer.
Local<Object> obj = argT; \ | ||
Handle<Value> _obj = argT; \ | ||
if (!HasInstance(_obj)) { \ | ||
return env->ThrowTypeError("must be called on Buffer instance"); \ |
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.
I'm not a fan of control flow in macros and I think that the buffer code in general is overdue for an overhaul.
I want to suggest a more structured approach where the JS prototype methods typecheck the this
argument before passing it on to their C++ counterparts. I imagine it would look something like this (using CamelCase for dramatic effect):
const binding = process.binding('buffer');
const BindingIsBuffer = binding.IsBuffer;
const BindingToString = binding.ToString;
const BindingUtf8Slice = binding.Utf8Slice;
// ...
// keep a reference to Object#toString, the user may monkey-patch it
const ObjectProtoToString = Object.prototype.toString;
function IsBuffer(obj) {
return typeof(obj) === 'object' && obj !== null && BindingIsBuffer(obj);
}
// ...
Buffer.prototype.toString = function() {
if (!IsBuffer(this))
return ObjectProtoToString(this);
if (this.length === 0)
return '';
return BindingToString(this);
};
Buffer.prototype.utf8Slice = function(start, end) {
if (!IsBuffer(this))
throw new TypeError('this is not an instance of Buffer');
// validate and coerce start and end
if (this.length === 0 && start === 0 && end === 0)
return '';
return BindingUtf8Slice(this, start, end);
};
// etc.
In short, be paranoid and rigorous. It can be more paranoid still; for example, the user may have replaced this.length
with a getter so it would be prudent to read it only once.
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.
I think this is a good approach. setupBufferJS
needs to go if we do this. It's weird that part of the methods are added in C++ land and some are added in JS land anyway.
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.
@bnoordhuis' approach is the correct one. when I first rewrote Buffer I did it that way because the diff was already huge and using setupBufferJS
was less diff noise in the PR. Plus it maintained how things had been done before.
@bnoordhuis you have plans to rewrite Buffer, or want me to? actually, um. do we just want to hold off on this since Buffer has to be completely rewritten anyway since 4.4 will break everything?
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.
um. do we just want to hold off on this since Buffer has to be completely rewritten anyway since 4.4 will break everything?
If it's gona be re-written might as well re-write it to use ArrayBuffers TypedArrays by the sounds of it. :S
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.
@Fishrock123 problem is that's a massive break. Don't even think nan can deal with it in a backwards compatible way. Since class
won't be out until 2 (since we're increasing the major, i guess...). Are we going to require this be a 3 release?
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.
Since
class
won't be out until 2 (since we're increasing the major, i guess...).
That is actually about now. Class-in-strict will be live in 2.0 due to 4.2, iirc.
Are we going to require this be a 3 release?
Well, v8 4.3 is already going to breaking enough to warrant a 3.0, right? :/
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.
so stupid. whatever.
on a technical note, we'll need to wait until 2.0 until Buffers are implemented as Typed Arrays. That can be yet another breaking change added to the 3.x list.
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.
OK, so what should I do with this PR? It is no problem for me to rewrite this in @bnoordhuis' style, but if that would not be useful due to the V8 4.4 breakage I can also not bother.
If there is going to be another patch release before V8 4.4 & the rewrite I recommend at least taking my first two commits (the .toString()
fix and the .copy()
fix). They are small, don't touch the ARGS_THIS
macro, and fix problems that are easy to trigger. The copy bug is especially bad because anyBuffer.copy(obj.misspelledProperty)
segfaults.
I'm not 100% sure about what the current status is on LTS releases, but I would imagine that even though Buffer is going to be rewritten/heavily modified because of upcoming V8 changes, that these changes could at least land in 1.x? |
@iojs/tc Thoughts on making sure this change gets in to v1.x regardless of whether a v2 is coming out soon? |
@trevnorris land in master and backport? |
Fine by me. Will need the changes @bnoordhuis explained though. |
It looks like the |
@monsanto I'd like to apologize for the confusion. This PR popped up the same day I learned V8 was removing the API that Buffer currently uses, and so was heavily on my mind. And my comments about the changes @bnoordhuis suggested were mainly about the technique he was explaining. The reason the If there's a quick fix to prevent a user from being able to segfault from JS then it should land. The user should never be able to segfault from JS using blessed API. I'll finish doing my review now. |
@@ -302,6 +302,10 @@ Buffer.byteLength = byteLength; | |||
|
|||
// toString(encoding, start=0, end=buffer.length) | |||
Buffer.prototype.toString = function(encoding, start, end) { | |||
if (!(this instanceof Buffer)) { | |||
return Object.prototype.toString.call(this); | |||
} |
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.
style: no need for the { }
.
@trevnorris it's fine, and don't worry about reviewing the changes, it's changed up a lot locally due to moving the checks to JS land as requested. I didn't have time to finish up that work yesterday but I can try to get it out today. |
@trevnorris @bnoordhuis I have moved all type checks (and a few others) to JS land. I have less free time than I expected so the code is not as paranoid as requested. For example, it uses Hopefully this is useful, and the extra paranoia/etc can be added in a follow up PR. Otherwise I am going to have to bow out. Sorry! |
if (arguments.length === 0) | ||
return Object.prototype.toString.call(this); | ||
else | ||
throw new TypeError('this is not an instance of Buffer'); |
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.
Even though this case is insane, it inevitably will break someone in the wild. Because of that this is a sem-minor patch.
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.
which part will break? if this
is not a Buffer the process will crash
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.
hehe. okay true enough. in that case I'd consider this a bug fix patch. :)
Oops, I just realized my link didn't link to the actual CI run. :S https://jenkins-iojs.nodesource.com/view/iojs/job/iojs+any-pr+multi/590/ |
@@ -19,7 +19,7 @@ | |||
} while (0) | |||
|
|||
#define ARGS_THIS(argT) \ | |||
Local<Object> obj = argT; \ | |||
Local<Object> obj = argT.As<Object>(); \ |
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.
can't exclude the proper checks from C++. example:
function F() { }
F.prototype.__proto__ = Buffer.prototype;
var f = new F();
f.write(); // ABORT
The above passes the instanceof
check. The only think you can ensure is that it's an Object
, but not that it's been allocated external memory. Still run the Buffer::HasInstance()
check on all incoming objects.
ping @monsanto |
sorry @Fishrock123, this PR went from a 5 min fix to something much larger and I don't have the time for it anymore esp. considering this will all be moot for the Buffer rewrite. If someone would like to take the code I have here as their own feel free. |
@monsanto Thanks for all your work on this. You plan on picking it back up, or mind if I make a PR implementing some similar changes you have here? |
@trevnorris it's all yours! |
If an object's prototype is munged it's possible to bypass the instanceof check and cause the application to abort. Instead now use HasInstance() to verify that the object is a Buffer, and throw if not. This check will not work for JS only methods. So while the application won't abort, it also won't throw. In order to properly throw in all cases with toString() the JS optimization of checking that length is zero has been removed. In its place the native methods will now return early if a zero length string is detected. Ref: #1486 Ref: #1922 Fixes: #1485 PR-URL: #2012 Reviewed-By: Ben Noordhuis <[email protected]>
Done in 1cd9eeb. |
If an object's prototype is munged it's possible to bypass the instanceof check and cause the application to abort. Instead now use HasInstance() to verify that the object is a Buffer, and throw if not. This check will not work for JS only methods. So while the application won't abort, it also won't throw. In order to properly throw in all cases with toString() the JS optimization of checking that length is zero has been removed. In its place the native methods will now return early if a zero length string is detected. Ref: nodejs#1486 Ref: nodejs#1922 Fixes: nodejs#1485 PR-URL: nodejs#2012 Reviewed-By: Ben Noordhuis <[email protected]>
Return Object.prototype.toString(this) in such a case; this approach is inspired by Array.prototype.toString.
Fixes #1485.