Skip to content

Commit 2a4ec45

Browse files
committed
buffer: prevent abort on bad proto
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.
1 parent 8350f3a commit 2a4ec45

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

lib/buffer.js

-2
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,6 @@ function slowToString(encoding, start, end) {
380380

381381
Buffer.prototype.toString = function() {
382382
const length = this.length | 0;
383-
if (length === 0)
384-
return '';
385383
if (arguments.length === 0)
386384
return this.utf8Slice(0, length);
387385
return slowToString.apply(this, arguments);

src/node_buffer.cc

+32-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
if (!(r)) return env->ThrowRangeError("out of range index"); \
1919
} while (0)
2020

21+
#define THROW_AND_RETURN_UNLESS_BUFFER(env, obj) \
22+
do { \
23+
if (!HasInstance(obj)) \
24+
return env->ThrowTypeError("argument should be a Buffer"); \
25+
} while (0)
26+
2127
#define ARGS_THIS(argT) \
2228
Local<Object> obj = argT; \
2329
size_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); \
@@ -223,7 +229,12 @@ template <encoding encoding>
223229
void StringSlice(const FunctionCallbackInfo<Value>& args) {
224230
Environment* env = Environment::GetCurrent(args);
225231

232+
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
226233
ARGS_THIS(args.This())
234+
235+
if (obj_length == 0)
236+
return args.GetReturnValue().SetEmptyString();
237+
227238
SLICE_START_END(args[0], args[1], obj_length)
228239

229240
args.GetReturnValue().Set(
@@ -235,7 +246,12 @@ template <>
235246
void StringSlice<UCS2>(const FunctionCallbackInfo<Value>& args) {
236247
Environment* env = Environment::GetCurrent(args);
237248

249+
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
238250
ARGS_THIS(args.This())
251+
252+
if (obj_length == 0)
253+
return args.GetReturnValue().SetEmptyString();
254+
239255
SLICE_START_END(args[0], args[1], obj_length)
240256
length /= 2;
241257

@@ -306,8 +322,9 @@ void Copy(const FunctionCallbackInfo<Value> &args) {
306322
if (!HasInstance(args[0]))
307323
return env->ThrowTypeError("first arg should be a Buffer");
308324

309-
Local<Object> target = args[0]->ToObject(env->isolate());
325+
Local<Object> target = args[0].As<Object>();
310326

327+
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
311328
ARGS_THIS(args.This())
312329
size_t target_length = target->GetIndexedPropertiesExternalArrayDataLength();
313330
char* target_data = static_cast<char*>(
@@ -340,6 +357,7 @@ void Copy(const FunctionCallbackInfo<Value> &args) {
340357

341358

342359
void Fill(const FunctionCallbackInfo<Value>& args) {
360+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
343361
ARGS_THIS(args[0].As<Object>())
344362

345363
size_t start = args[2]->Uint32Value();
@@ -383,6 +401,7 @@ template <encoding encoding>
383401
void StringWrite(const FunctionCallbackInfo<Value>& args) {
384402
Environment* env = Environment::GetCurrent(args);
385403

404+
THROW_AND_RETURN_UNLESS_BUFFER(env, args.This());
386405
ARGS_THIS(args.This())
387406

388407
if (!args[0]->IsString())
@@ -459,6 +478,7 @@ static inline void Swizzle(char* start, unsigned int len) {
459478

460479
template <typename T, enum Endianness endianness>
461480
void ReadFloatGeneric(const FunctionCallbackInfo<Value>& args) {
481+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
462482
ARGS_THIS(args[0].As<Object>());
463483

464484
uint32_t offset = args[1]->Uint32Value();
@@ -522,21 +542,25 @@ uint32_t WriteFloatGeneric(const FunctionCallbackInfo<Value>& args) {
522542

523543

524544
void WriteFloatLE(const FunctionCallbackInfo<Value>& args) {
545+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
525546
args.GetReturnValue().Set(WriteFloatGeneric<float, kLittleEndian>(args));
526547
}
527548

528549

529550
void WriteFloatBE(const FunctionCallbackInfo<Value>& args) {
551+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
530552
args.GetReturnValue().Set(WriteFloatGeneric<float, kBigEndian>(args));
531553
}
532554

533555

534556
void WriteDoubleLE(const FunctionCallbackInfo<Value>& args) {
557+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
535558
args.GetReturnValue().Set(WriteFloatGeneric<double, kLittleEndian>(args));
536559
}
537560

538561

539562
void WriteDoubleBE(const FunctionCallbackInfo<Value>& args) {
563+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
540564
args.GetReturnValue().Set(WriteFloatGeneric<double, kBigEndian>(args));
541565
}
542566

@@ -550,6 +574,10 @@ void ByteLengthUtf8(const FunctionCallbackInfo<Value> &args) {
550574

551575

552576
void Compare(const FunctionCallbackInfo<Value> &args) {
577+
Environment* env = Environment::GetCurrent(args);
578+
THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]);
579+
THROW_AND_RETURN_UNLESS_BUFFER(env, args[1]);
580+
553581
Local<Object> obj_a = args[0].As<Object>();
554582
char* obj_a_data =
555583
static_cast<char*>(obj_a->GetIndexedPropertiesExternalArrayData());
@@ -599,10 +627,10 @@ int32_t IndexOf(const char* haystack,
599627

600628

601629
void IndexOfString(const FunctionCallbackInfo<Value>& args) {
602-
ASSERT(args[0]->IsObject());
603630
ASSERT(args[1]->IsString());
604631
ASSERT(args[2]->IsNumber());
605632

633+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
606634
ARGS_THIS(args[0].As<Object>());
607635
node::Utf8Value str(args.GetIsolate(), args[1]);
608636
int32_t offset_i32 = args[2]->Int32Value();
@@ -630,10 +658,10 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {
630658

631659

632660
void IndexOfBuffer(const FunctionCallbackInfo<Value>& args) {
633-
ASSERT(args[0]->IsObject());
634661
ASSERT(args[1]->IsObject());
635662
ASSERT(args[2]->IsNumber());
636663

664+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
637665
ARGS_THIS(args[0].As<Object>());
638666
Local<Object> buf = args[1].As<Object>();
639667
int32_t offset_i32 = args[2]->Int32Value();
@@ -667,10 +695,10 @@ void IndexOfBuffer(const FunctionCallbackInfo<Value>& args) {
667695

668696

669697
void IndexOfNumber(const FunctionCallbackInfo<Value>& args) {
670-
ASSERT(args[0]->IsObject());
671698
ASSERT(args[1]->IsNumber());
672699
ASSERT(args[2]->IsNumber());
673700

701+
THROW_AND_RETURN_UNLESS_BUFFER(Environment::GetCurrent(args), args[0]);
674702
ARGS_THIS(args[0].As<Object>());
675703
uint32_t needle = args[1]->Uint32Value();
676704
int32_t offset_i32 = args[2]->Int32Value();

test/parallel/test-buffer-fakes.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const Buffer = require('buffer').Buffer;
6+
const Bp = Buffer.prototype;
7+
8+
function FakeBuffer() { }
9+
FakeBuffer.__proto__ = Buffer;
10+
FakeBuffer.prototype.__proto__ = Buffer.prototype;
11+
12+
const fb = new FakeBuffer();
13+
14+
assert.throws(function() {
15+
new Buffer(fb);
16+
}, TypeError);
17+
18+
assert.throws(function() {
19+
+Buffer.prototype;
20+
}, TypeError);
21+
22+
assert.throws(function() {
23+
Buffer.compare(fb, new Buffer(0));
24+
}, TypeError);
25+
26+
assert.throws(function() {
27+
fb.write('foo');
28+
}, TypeError);
29+
30+
assert.throws(function() {
31+
Buffer.concat([fb, fb]);
32+
}, TypeError);
33+
34+
assert.throws(function() {
35+
fb.toString();
36+
}, TypeError);
37+
38+
assert.throws(function() {
39+
fb.equals(new Buffer(0));
40+
}, TypeError);
41+
42+
assert.throws(function() {
43+
fb.indexOf(5);
44+
}, TypeError);
45+
46+
assert.throws(function() {
47+
fb.readFloatLE(0);
48+
}, TypeError);
49+
50+
assert.throws(function() {
51+
fb.writeFloatLE(0);
52+
}, TypeError);
53+
54+
assert.throws(function() {
55+
fb.fill(0);
56+
}, TypeError);

0 commit comments

Comments
 (0)