-
Notifications
You must be signed in to change notification settings - Fork 31.3k
buffer: workaround for v8 buffer.toString failure. fixes #4266 #4394
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
Conversation
Original commit message: api: introduce SealHandleScope When debugging Handle leaks in io.js we found it very convenient to be able to Seal some specific (root in our case) scope to prevent Handle allocations in it, and easily find leakage. R=yangguo BUG= Review URL: https://codereview.chromium.org/1079713002 Cr-Commit-Position: refs/heads/master@{nodejs#27766} Should help us identify and fix Handle leaks in core and user-space code. PR-URL: nodejs#3945 Reviewed-By: Fedor Indutny <fedor@indutny.com> Reviewed-By: James Snell <jasnell@gmail.com>
Helps to find Handle leaks in Debug mode. Ref: a5244d3 "deps: backport 1f8555 from v8's upstream" PR-URL: nodejs#3945 Reviewed-By: Fedor Indutny <fedor@indutny.com> Reviewed-By: James Snell <jasnell@gmail.com>
The call to node::Environment::GetCurrent(Isolate*) makes the call to v8::Isolate::GetCurrentContext(). Doing so creates a new handle that bubbled to the v8::SealHandleScope(). PR-URL: nodejs#3945 Reviewed-By: Fedor Indutny <fedor@indutny.com> Reviewed-By: James Snell <jasnell@gmail.com>
test-domain-exit-dispose-again had been written for node v0.10.x, and was using the fact that callbacks scheduled with `process.nextTick` wouldn't run if the domain attached to it was disposed. This is not longer the case, and as a result the test would not catch any regression: it would always pass. This change rewrites that test to check that the current domain is cleared properly when processing the rest of the timers list if a timer's callback throws an error. This makes the test fail without the original fix, and pass with the original fix, as expected. PR: nodejs#3991 PR-URL: nodejs#3991 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
* Include reference to CVE-2015-8027 * Fix "socket may no longer have a socket" reference * Expand on non-existent parser causing the error * Clarify that CVE-2015-3194 affects TLS servers using _client certificate authentication_ PR-URL: nodejs#4154 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Backport the tools/install.py changes from 628a3ab that were missed when 6fb0b92 backported the corresponding changes to the Makefile to build the headers only archive. PR-URL: nodejs#4149 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Johan Bergström <bugs@bergstroem.nu> Reviewed-By: Rod Vagg <rod@vagg.org>
PR nodejs#3890 [1] introduced the variable ALLOW_INSECURE_SERVER_DHPARAM defined in src/node_crypto.cc. However, if nodejs is built without OpenSSL support, the build fails: error: ‘ALLOW_INSECURE_SERVER_DHPARAM’ was not declared in this scope ALLOW_INSECURE_SERVER_DHPARAM = true; Fix this by using the preprocessor macro HAVE_OPENSSL to opt-out the use of ALLOW_INSECURE_SERVER_DHPARAM in non-OpenSSL builds. [1] nodejs#3890 PR-URL: nodejs#4201 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Rod Vagg <rod@vagg.org>
d1ba82a "fixed" test-domain-exit-dispose-again by changing its logic to test that process.domain was cleared properly in case an error was thrown from a timer's callback. However, it became clear when reviewing a recent change that refactors lib/timers.js that it was not quite the intention of the original test. Thus, this change adds the original implementation of test-domain-exit-dispose-again back, with comments that make its implementation easier to understand. It also preserves the changes made by d1ba82a, but it moves them to a new test file named test-timers-reset-process-domain-on-throw.js. PR: nodejs#4278 PR-URL: nodejs#4278 Reviewed-By: James M Snell <jasnell@gmail.com>
Fix node exiting due to an exception being thrown rather than emitting an 'uncaughtException' event on the process object when: 1. no error handler is set on the domain within which an error is thrown 2. an 'uncaughtException' event listener is set on the process Also fix an issue where the process would not abort in the proper function call if an error is thrown within a domain with no error handler and --abort-on-uncaught-exception is used. Fixes nodejs#3607 and nodejs#3653. PR: nodejs#3885 PR-URL: nodejs#3885 Reviewed-By: James M Snell <jasnell@gmail.com>
@skilledDeveloper Can you update the commit log messages to match the contribution guidelines. Specifically, use subsystem prefix ( |
@@ -87,6 +91,10 @@ class ExternString: public ResourceType { | |||
if (length == 0) | |||
return scope.Escape(String::Empty(isolate)); | |||
|
|||
// Workaround to avoid process crash caused by a possible V8 bug |
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.
This comment is too vague; I'd phrase it as // V8 aborts if we try to allocate a string that is too large.
(punctuation intentional.)
Shouldn't NewFromCopy() have this logic as well?
Thanks for this @skilledDeveloper, I think this is your first contribution to core, thanks for taking the time. Please let us know if you need any help sorting out the issues raised by @ofrobots and @bnoordhuis to get this ready for landing. |
@@ -62,6 +62,10 @@ class ExternString: public ResourceType { | |||
return length_; | |||
} | |||
|
|||
// Maximum length in bytes that V8 can handle. | |||
// This equals to 268435440 bytes | |||
static const size_t kMaxLength = (256 * 1024 * 1024) - 16; |
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.
Please use String::kMaxLength
instead. More forwards compatible.
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.
oh wait. missed that this targets v0.12
branch.
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.
That's right. There is no such thing in v0.12 and kStringMaxLength is not exposed anywhere.
@rvagg Not a problem at all! p.s. Jsut noticed that one of the commits messages is still more than 50 chars! |
This PR will fix the issue #4266. |
} | ||
assert(typeof gc === 'function', 'Run this test with --expose-gc'); | ||
|
||
try { |
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.
Should use an appropriate assert.throws
or assert.doesNotThrow
here instead of throwing the raw error
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.
This is how it's done in all other test-stringbytes-external-*.js
tests (on master). We're doing a check if the machine has enough memory available, and quitting the test without error if there's not. No assert
should be used.
LGTM. This was my last review to prevent myself from making the same irrelevant comments again. |
Need to squash the commits closer to ~1. Without having the time to dive into the details of this myself, @trevnorris or @ofrobots do either of you have suggestions for commit compaction here? |
Here's the summary of this PR so far:
|
@skilledDeveloper Below is a patch that fixes things up. Except for the fact that for some reason throwing from commit 62c5a08157825a5882aec35c5ab58e900464c303
Author: Trevor Norris <trev.norris@gmail.com>
Date: Thu Mar 3 14:29:50 2016 -0700
FIX IT UP
diff --git a/lib/buffer.js b/lib/buffer.js
index 1a9f7ca..31f721f 100644
--- a/lib/buffer.js
+++ b/lib/buffer.js
@@ -247,8 +247,6 @@ Buffer.byteLength = function(str, enc) {
// toString(encoding, start=0, end=buffer.length)
Buffer.prototype.toString = function(encoding, start, end) {
- var loweredCase = false;
-
start = start >>> 0;
end = util.isUndefined(end) || end === Infinity ? this.length : end >>> 0;
@@ -257,29 +255,41 @@ Buffer.prototype.toString = function(encoding, start, end) {
if (end > this.length) end = this.length;
if (end <= start) return '';
+ var ret = getSlice(this, start, end, encoding);
+ if (ret === undefined)
+ // XXX Throwing from toString causes:
+ // FATAL ERROR: invalid array length Allocation failed
+ throw new Error('"toString()" failed');
+ return ret;
+};
+
+
+function getSlice(self, start, end, encoding) {
+ var loweredCase = false;
+
while (true) {
switch (encoding) {
case 'hex':
- return this.hexSlice(start, end);
+ return self.hexSlice(start, end);
case 'utf8':
case 'utf-8':
- return this.utf8Slice(start, end);
+ return self.utf8Slice(start, end);
case 'ascii':
- return this.asciiSlice(start, end);
+ return self.asciiSlice(start, end);
case 'binary':
- return this.binarySlice(start, end);
+ return self.binarySlice(start, end);
case 'base64':
- return this.base64Slice(start, end);
+ return self.base64Slice(start, end);
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
- return this.ucs2Slice(start, end);
+ return self.ucs2Slice(start, end);
default:
if (loweredCase)
@@ -288,7 +298,7 @@ Buffer.prototype.toString = function(encoding, start, end) {
loweredCase = true;
}
}
-};
+}
Buffer.prototype.equals = function equals(b) {
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index cd66a8a..8fa1b70 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -262,8 +262,11 @@ void StringSlice(const FunctionCallbackInfo<Value>& args) {
ARGS_THIS(args.This())
SLICE_START_END(args[0], args[1], obj_length)
- args.GetReturnValue().Set(
- StringBytes::Encode(env->isolate(), obj_data + start, length, encoding));
+ Local<Value> ret =
+ StringBytes::Encode(env->isolate(), obj_data + start, length, encoding);
+
+ if (!ret.IsEmpty())
+ args.GetReturnValue().Set(ret);
}
diff --git a/src/string_bytes.cc b/src/string_bytes.cc
index 5805c7c..84c4b4c 100644
--- a/src/string_bytes.cc
+++ b/src/string_bytes.cc
@@ -733,10 +733,14 @@ Local<Value> StringBytes::Encode(Isolate* isolate,
break;
case UTF8:
- val = String::NewFromUtf8(isolate,
- buf,
- String::kNormalString,
- buflen);
+ if (buflen > ExternOneByteString::kMaxLength) {
+ val = Local<String>();
+ } else {
+ val = String::NewFromUtf8(isolate,
+ buf,
+ String::kNormalString,
+ buflen);
+ }
break;
case BINARY: |
@trevnorris That's exactly the fatal error I've been talking about. I don't believe it's caused by throwing though. It seems to be caused by variable assignment: |
@skilledDeveloper I'm at a lose. This has got to be some sort of v8 bug. How about we just throw from C++ if the returned handle is empty? |
@trevnorris @bnoordhuis I even tried throwing error first thing in the JS but I got the same error. if (this.length > 268435440) {
console.log("len:", this.length);
throw new Error('toString failed');
} Here's the result.
|
@skilledDeveloper The following patch may fix that problem. Try this out: diff --git a/src/node.cc b/src/node.cc
index 624865b..c12c993 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -177,21 +177,19 @@ ArrayBufferAllocator ArrayBufferAllocator::the_singleton;
void* ArrayBufferAllocator::Allocate(size_t length) {
if (length > kMaxLength)
return NULL;
- char* data = new char[length];
- memset(data, 0, length);
- return data;
+ return calloc(length, 1);
}
void* ArrayBufferAllocator::AllocateUninitialized(size_t length) {
if (length > kMaxLength)
return NULL;
- return new char[length];
+ return malloc(length);
}
void ArrayBufferAllocator::Free(void* data, size_t length) {
- delete[] static_cast<char*>(data);
+ free(data);
}
|
@trevnorris Did not help. Thanks though. |
@trevnorris @bnoordhuis @jasnell @ofrobots @thealphanerd @rvagg @targos @ChALkeR
There is not much progress on this PR and we are apparently blocked by a V8 (or some memory management) bug. Shall we close this unsolved? |
Let's leave it open and revisit. |
More than three months ago, @jasnell wrote:
Are we at a point where we can make a decision? Seems like the choices are (in order of degree of difficulty):
|
+1 on moving forward with something here. Options 2 or 3 sound best but On Saturday, August 6, 2016, Rich Trott notifications@github.com wrote:
|
* Workaround for Buffer.toString failure caused by exceeding the maximum supported length in V8. * See issue nodejs/node#4266 and PR nodejs/node#4394 Fixes #22
not gonna make it now, EOL around the corner for v0.12, thanks for the effort on this anyway! |
@rvagg ... should we go ahead and close? |
@jasnell I'm going to close this assuming you did not mean to repoen |
* Workaround for Buffer.toString failure caused by exceeding the maximum supported length in V8. * See issue nodejs/node#4266 and PR nodejs/node#4394 Fixes #22
if any ways to possible to get above 265 MB via nodejs. |
The limit is ~1024 MB from Node 8 if you are on a 64-bit system |
What would be the workaround to mitigate this issue? |
even when I give more memory to node i.e. What can be a potential work around? |
@nashid Can you open a new issue at https://github.com/nodejs/help/issues/? Whatever you’re seeing is unrelated to this PR, and commenting on a 4-year-old PR isn’t going to give you any helpful information either. |
A workaround to return
undefined
whenBuffer.toString
fails because of exceeding the maximum supported length in V8.@bnoordhuis Please review.