Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
src: fix memory leak in ExternString
v8 will silently return an empty handle
which doesn't delete our data if string length is
above String::kMaxLength

Fixes: #1374
PR-URL: #2402
Reviewed-By: trevnorris - Trevor Norris <trev.norris@gmail.com>
Reviewed-By: indutny - Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: bnoordhuis - Ben Noordhuis <info@bnoordhuis.nl>
  • Loading branch information
skomski authored and trevnorris committed Sep 2, 2015
commit 617ee324d29abedc95507d5f9d16b2f18778a484
12 changes: 8 additions & 4 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,14 @@ function slowToString(encoding, start, end) {


Buffer.prototype.toString = function() {
const length = this.length | 0;
if (arguments.length === 0)
return this.utf8Slice(0, length);
return slowToString.apply(this, arguments);
if (arguments.length === 0) {
var result = this.utf8Slice(0, this.length);
} else {
var result = slowToString.apply(this, arguments);
}
if (result === undefined)
throw new Error('toString failed');
return result;
};


Expand Down
4 changes: 4 additions & 0 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,10 @@ void Initialize(Handle<Object> target,
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "kMaxLength"),
Integer::NewFromUnsigned(env->isolate(), kMaxLength)).FromJust();

target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "kStringMaxLength"),
Integer::New(env->isolate(), String::kMaxLength)).FromJust();
}


Expand Down
39 changes: 29 additions & 10 deletions src/string_bytes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
using v8::MaybeLocal;


template <typename ResourceType, typename TypeName>
class ExternString: public ResourceType {
public:
~ExternString() override {
delete[] data_;
free(const_cast<TypeName*>(data_));
isolate()->AdjustAmountOfExternalAllocatedMemory(-byte_length());
}

Expand All @@ -52,7 +53,11 @@ class ExternString: public ResourceType {
if (length == 0)
return scope.Escape(String::Empty(isolate));

TypeName* new_data = new TypeName[length];
TypeName* new_data =
static_cast<TypeName*>(malloc(length * sizeof(*new_data)));
if (new_data == nullptr) {
return Local<String>();
}
memcpy(new_data, data, length * sizeof(*new_data));

return scope.Escape(ExternString<ResourceType, TypeName>::New(isolate,
Expand All @@ -72,10 +77,15 @@ class ExternString: public ResourceType {
ExternString* h_str = new ExternString<ResourceType, TypeName>(isolate,
data,
length);
Local<String> str = String::NewExternal(isolate, h_str);
MaybeLocal<String> str = String::NewExternal(isolate, h_str);
isolate->AdjustAmountOfExternalAllocatedMemory(h_str->byte_length());

return scope.Escape(str);
if (str.IsEmpty()) {
delete h_str;
return Local<String>();
}

return scope.Escape(str.ToLocalChecked());
}

inline Isolate* isolate() const { return isolate_; }
Expand Down Expand Up @@ -765,11 +775,14 @@ Local<Value> StringBytes::Encode(Isolate* isolate,

case ASCII:
if (contains_non_ascii(buf, buflen)) {
char* out = new char[buflen];
char* out = static_cast<char*>(malloc(buflen));
if (out == nullptr) {
return Local<String>();
}
force_ascii(buf, out, buflen);
if (buflen < EXTERN_APEX) {
val = OneByteString(isolate, out, buflen);
delete[] out;
free(out);
} else {
val = ExternOneByteString::New(isolate, out, buflen);
}
Expand Down Expand Up @@ -797,14 +810,17 @@ Local<Value> StringBytes::Encode(Isolate* isolate,

case BASE64: {
size_t dlen = base64_encoded_size(buflen);
char* dst = new char[dlen];
char* dst = static_cast<char*>(malloc(dlen));
if (dst == nullptr) {
return Local<String>();
}

size_t written = base64_encode(buf, buflen, dst, dlen);
CHECK_EQ(written, dlen);

if (dlen < EXTERN_APEX) {
val = OneByteString(isolate, dst, dlen);
delete[] dst;
free(dst);
} else {
val = ExternOneByteString::New(isolate, dst, dlen);
}
Expand All @@ -813,13 +829,16 @@ Local<Value> StringBytes::Encode(Isolate* isolate,

case HEX: {
size_t dlen = buflen * 2;
char* dst = new char[dlen];
char* dst = static_cast<char*>(malloc(dlen));
if (dst == nullptr) {
return Local<String>();
}
size_t written = hex_encode(buf, buflen, dst, dlen);
CHECK_EQ(written, dlen);

if (dlen < EXTERN_APEX) {
val = OneByteString(isolate, dst, dlen);
delete[] dst;
free(dst);
} else {
val = ExternOneByteString::New(isolate, dst, dlen);
}
Expand Down
66 changes: 66 additions & 0 deletions test/parallel/test-stringbytes-external.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,69 @@ var PRE_3OF4_APEX = Math.ceil((EXTERN_APEX / 4) * 3) - RADIOS;
assert.equal(a, b);
assert.equal(b, c);
})();

// v8 fails silently if string length > v8::String::kMaxLength
(function() {
// v8::String::kMaxLength defined in v8.h
const kStringMaxLength = process.binding('buffer').kStringMaxLength;

assert.throws(function() {
new Buffer(kStringMaxLength + 1).toString();
}, /toString failed|Buffer allocation failed/);

try {
new Buffer(kStringMaxLength * 4);
} catch(e) {
assert.equal(e.message, 'Buffer allocation failed - process out of memory');
console.log(
'1..0 # Skipped: intensive toString tests due to memory confinements');
return;
}

assert.throws(function() {
new Buffer(kStringMaxLength + 1).toString('ascii');
}, /toString failed/);

assert.throws(function() {
new Buffer(kStringMaxLength + 1).toString('utf8');
}, /toString failed/);

assert.throws(function() {
new Buffer(kStringMaxLength * 2 + 2).toString('utf16le');
}, /toString failed/);

assert.throws(function() {
new Buffer(kStringMaxLength + 1).toString('binary');
}, /toString failed/);

assert.throws(function() {
new Buffer(kStringMaxLength + 1).toString('base64');
}, /toString failed/);

assert.throws(function() {
new Buffer(kStringMaxLength + 1).toString('hex');
}, /toString failed/);

var maxString = new Buffer(kStringMaxLength).toString();
assert.equal(maxString.length, kStringMaxLength);
// Free the memory early instead of at the end of the next assignment
maxString = undefined;

maxString = new Buffer(kStringMaxLength).toString('binary');
assert.equal(maxString.length, kStringMaxLength);
maxString = undefined;

maxString =
new Buffer(kStringMaxLength + 1).toString('binary', 1);
assert.equal(maxString.length, kStringMaxLength);
maxString = undefined;

maxString =
new Buffer(kStringMaxLength + 1).toString('binary', 0, kStringMaxLength);
assert.equal(maxString.length, kStringMaxLength);
maxString = undefined;

maxString = new Buffer(kStringMaxLength + 2).toString('utf16le');
assert.equal(maxString.length, (kStringMaxLength + 2) / 2);
maxString = undefined;
})();