Skip to content

Commit 693401d

Browse files
committed
buffer: use stricter range checks
This validates the input to make sure the arguments do not overflow. Before, if the input would overflow, it would cause the write to be performt in the wrong spot / result in unexpected behavior. Instead, just use a strict number validation. PR-URL: #27045 Fixes: #27043 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 2fed83d commit 693401d

7 files changed

+98
-102
lines changed

doc/api/errors.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -1564,12 +1564,6 @@ OpenSSL crypto support.
15641564
An attempt was made to use features that require [ICU][], but Node.js was not
15651565
compiled with ICU support.
15661566

1567-
<a id="ERR_NO_LONGER_SUPPORTED"></a>
1568-
### ERR_NO_LONGER_SUPPORTED
1569-
1570-
A Node.js API was called in an unsupported manner, such as
1571-
`Buffer.write(string, encoding, offset[, length])`.
1572-
15731567
<a id="ERR_OUT_OF_RANGE"></a>
15741568
### ERR_OUT_OF_RANGE
15751569

@@ -2096,6 +2090,12 @@ removed: v10.0.0
20962090

20972091
Used by the `N-API` when `Constructor.prototype` is not an object.
20982092

2093+
<a id="ERR_NO_LONGER_SUPPORTED"></a>
2094+
### ERR_NO_LONGER_SUPPORTED
2095+
2096+
A Node.js API was called in an unsupported manner, such as
2097+
`Buffer.write(string, encoding, offset[, length])`.
2098+
20992099
<a id="ERR_OUTOFMEMORY"></a>
21002100
### ERR_OUTOFMEMORY
21012101
<!-- YAML

lib/buffer.js

+30-43
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,18 @@ const {
6565
const {
6666
codes: {
6767
ERR_BUFFER_OUT_OF_BOUNDS,
68-
ERR_OUT_OF_RANGE,
6968
ERR_INVALID_ARG_TYPE,
7069
ERR_INVALID_ARG_VALUE,
7170
ERR_INVALID_BUFFER_SIZE,
7271
ERR_INVALID_OPT_VALUE,
73-
ERR_NO_LONGER_SUPPORTED,
7472
ERR_UNKNOWN_ENCODING
7573
},
7674
hideStackFrames
7775
} = require('internal/errors');
78-
const { validateString } = require('internal/validators');
76+
const {
77+
validateInt32,
78+
validateString
79+
} = require('internal/validators');
7980

8081
const {
8182
FastBuffer,
@@ -445,7 +446,7 @@ Buffer.concat = function concat(list, length) {
445446
}
446447
}
447448
} else {
448-
length = length >>> 0;
449+
validateInt32(length, 'length', 0);
449450
}
450451

451452
const buffer = Buffer.allocUnsafe(length);
@@ -700,35 +701,27 @@ Buffer.prototype.compare = function compare(target,
700701

701702
if (targetStart === undefined)
702703
targetStart = 0;
703-
else if (targetStart < 0)
704-
throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart);
705704
else
706-
targetStart >>>= 0;
705+
validateInt32(targetStart, 'targetStart', 0);
707706

708707
if (targetEnd === undefined)
709708
targetEnd = target.length;
710-
else if (targetEnd > target.length)
711-
throw new ERR_OUT_OF_RANGE('targetEnd', `<= ${target.length}`, targetEnd);
712709
else
713-
targetEnd >>>= 0;
710+
validateInt32(targetEnd, 'targetEnd', 0, target.length);
714711

715712
if (sourceStart === undefined)
716713
sourceStart = 0;
717-
else if (sourceStart < 0)
718-
throw new ERR_OUT_OF_RANGE('sourceStart', '>= 0', sourceStart);
719714
else
720-
sourceStart >>>= 0;
715+
validateInt32(sourceStart, 'sourceStart', 0);
721716

722717
if (sourceEnd === undefined)
723718
sourceEnd = this.length;
724-
else if (sourceEnd > this.length)
725-
throw new ERR_OUT_OF_RANGE('sourceEnd', `<= ${this.length}`, sourceEnd);
726719
else
727-
sourceEnd >>>= 0;
720+
validateInt32(sourceEnd, 'sourceEnd', 0, this.length);
728721

729722
if (sourceStart >= sourceEnd)
730723
return (targetStart >= targetEnd ? 0 : -1);
731-
else if (targetStart >= targetEnd)
724+
if (targetStart >= targetEnd)
732725
return 1;
733726

734727
return compareOffset(this, target, targetStart, sourceStart, targetEnd,
@@ -867,17 +860,13 @@ function _fill(buf, value, offset, end, encoding) {
867860
offset = 0;
868861
end = buf.length;
869862
} else {
863+
validateInt32(offset, 'offset', 0);
870864
// Invalid ranges are not set to a default, so can range check early.
871-
if (offset < 0)
872-
throw new ERR_OUT_OF_RANGE('offset', '>= 0', offset);
873865
if (end === undefined) {
874866
end = buf.length;
875867
} else {
876-
if (end > buf.length || end < 0)
877-
throw new ERR_OUT_OF_RANGE('end', `>= 0 and <= ${buf.length}`, end);
878-
end = end >>> 0;
868+
validateInt32(end, 'end', 0, buf.length);
879869
}
880-
offset = offset >>> 0;
881870
if (offset >= end)
882871
return buf;
883872
}
@@ -896,39 +885,37 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
896885
// Buffer#write(string);
897886
if (offset === undefined) {
898887
return this.utf8Write(string, 0, this.length);
899-
888+
}
900889
// Buffer#write(string, encoding)
901-
} else if (length === undefined && typeof offset === 'string') {
890+
if (length === undefined && typeof offset === 'string') {
902891
encoding = offset;
903892
length = this.length;
904893
offset = 0;
905894

906895
// Buffer#write(string, offset[, length][, encoding])
907-
} else if (isFinite(offset)) {
908-
offset = offset >>> 0;
909-
if (isFinite(length)) {
910-
length = length >>> 0;
896+
} else {
897+
if (offset === undefined) {
898+
offset = 0;
911899
} else {
912-
encoding = length;
913-
length = undefined;
900+
validateInt32(offset, 'offset', 0, this.length);
914901
}
915902

916903
const remaining = this.length - offset;
917-
if (length === undefined || length > remaining)
918-
length = remaining;
919904

920-
if (string.length > 0 && (length < 0 || offset < 0))
921-
throw new ERR_BUFFER_OUT_OF_BOUNDS();
922-
} else {
923-
// If someone is still calling the obsolete form of write(), tell them.
924-
// we don't want eg buf.write("foo", "utf8", 10) to silently turn into
925-
// buf.write("foo", "utf8"), so we can't ignore extra args
926-
throw new ERR_NO_LONGER_SUPPORTED(
927-
'Buffer.write(string, encoding, offset[, length])'
928-
);
905+
if (length === undefined) {
906+
length = remaining;
907+
} else if (typeof length === 'string') {
908+
encoding = length;
909+
length = remaining;
910+
} else {
911+
validateInt32(length, 'length', 0, this.length);
912+
if (length > remaining)
913+
length = remaining;
914+
}
929915
}
930916

931-
if (!encoding) return this.utf8Write(string, offset, length);
917+
if (!encoding)
918+
return this.utf8Write(string, offset, length);
932919

933920
encoding += '';
934921
switch (encoding.length) {

lib/internal/errors.js

-1
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,6 @@ E('ERR_NO_CRYPTO',
994994
'Node.js is not compiled with OpenSSL crypto support', Error);
995995
E('ERR_NO_ICU',
996996
'%s is not supported on Node.js compiled without ICU', TypeError);
997-
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported', Error);
998997
E('ERR_OUT_OF_RANGE',
999998
(str, range, input, replaceDefaultBoolean = false) => {
1000999
assert(range, 'Missing "range" argument');

test/parallel/test-buffer-alloc.js

+17-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ const vm = require('vm');
55

66
const SlowBuffer = require('buffer').SlowBuffer;
77

8+
// Verify the maximum Uint8Array size. There is no concrete limit by spec. The
9+
// internal limits should be updated if this fails.
10+
assert.throws(
11+
() => new Uint8Array(2 ** 31),
12+
{ message: 'Invalid typed array length: 2147483648' }
13+
);
814

915
const b = Buffer.allocUnsafe(1024);
1016
assert.strictEqual(b.length, 1024);
@@ -59,8 +65,7 @@ assert.throws(() => b.write('test string', 0, 5, 'invalid'),
5965
/Unknown encoding: invalid/);
6066
// Unsupported arguments for Buffer.write
6167
assert.throws(() => b.write('test', 'utf8', 0),
62-
/is no longer supported/);
63-
68+
{ code: 'ERR_INVALID_ARG_TYPE' });
6469

6570
// Try to create 0-length buffers. Should not throw.
6671
Buffer.from('');
@@ -74,27 +79,22 @@ new Buffer('', 'latin1');
7479
new Buffer('', 'binary');
7580
Buffer(0);
7681

77-
const outOfBoundsError = {
78-
code: 'ERR_BUFFER_OUT_OF_BOUNDS',
79-
type: RangeError
80-
};
81-
8282
const outOfRangeError = {
8383
code: 'ERR_OUT_OF_RANGE',
8484
type: RangeError
8585
};
8686

8787
// Try to write a 0-length string beyond the end of b
88-
common.expectsError(() => b.write('', 2048), outOfBoundsError);
88+
common.expectsError(() => b.write('', 2048), outOfRangeError);
8989

9090
// Throw when writing to negative offset
91-
common.expectsError(() => b.write('a', -1), outOfBoundsError);
91+
common.expectsError(() => b.write('a', -1), outOfRangeError);
9292

9393
// Throw when writing past bounds from the pool
94-
common.expectsError(() => b.write('a', 2048), outOfBoundsError);
94+
common.expectsError(() => b.write('a', 2048), outOfRangeError);
9595

9696
// Throw when writing to negative offset
97-
common.expectsError(() => b.write('a', -1), outOfBoundsError);
97+
common.expectsError(() => b.write('a', -1), outOfRangeError);
9898

9999
// Try to copy 0 bytes worth of data into an empty buffer
100100
b.copy(Buffer.alloc(0), 0, 0, 0);
@@ -110,8 +110,12 @@ b.copy(Buffer.alloc(1), 0, 2048, 2048);
110110
{
111111
const writeTest = Buffer.from('abcdes');
112112
writeTest.write('n', 'ascii');
113-
writeTest.write('o', '1', 'ascii');
114-
writeTest.write('d', '2', 'ascii');
113+
assert.throws(
114+
() => writeTest.write('o', '1', 'ascii'),
115+
{ code: 'ERR_INVALID_ARG_TYPE' }
116+
);
117+
writeTest.write('o', 1, 'ascii');
118+
writeTest.write('d', 2, 'ascii');
115119
writeTest.write('e', 3, 'ascii');
116120
writeTest.write('j', 4, 'ascii');
117121
assert.strictEqual(writeTest.toString(), 'nodejs');

test/parallel/test-buffer-compare-offset.js

+31-11
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ assert.strictEqual(a.compare(b), -1);
1010

1111
// Equivalent to a.compare(b).
1212
assert.strictEqual(a.compare(b, 0), -1);
13-
assert.strictEqual(a.compare(b, '0'), -1);
13+
assert.throws(() => a.compare(b, '0'), { code: 'ERR_INVALID_ARG_TYPE' });
1414
assert.strictEqual(a.compare(b, undefined), -1);
1515

1616
// Equivalent to a.compare(b).
1717
assert.strictEqual(a.compare(b, 0, undefined, 0), -1);
1818

1919
// Zero-length target, return 1
2020
assert.strictEqual(a.compare(b, 0, 0, 0), 1);
21-
assert.strictEqual(a.compare(b, '0', '0', '0'), 1);
21+
assert.throws(
22+
() => a.compare(b, 0, '0', '0'),
23+
{ code: 'ERR_INVALID_ARG_TYPE' }
24+
);
2225

2326
// Equivalent to Buffer.compare(a, b.slice(6, 10))
2427
assert.strictEqual(a.compare(b, 6, 10), 1);
@@ -45,24 +48,41 @@ assert.strictEqual(a.compare(b, 0, 7, 4), -1);
4548
// Equivalent to Buffer.compare(a.slice(4, 6), b.slice(0, 7));
4649
assert.strictEqual(a.compare(b, 0, 7, 4, 6), -1);
4750

48-
// zero length target
49-
assert.strictEqual(a.compare(b, 0, null), 1);
51+
// Null is ambiguous.
52+
assert.throws(
53+
() => a.compare(b, 0, null),
54+
{ code: 'ERR_INVALID_ARG_TYPE' }
55+
);
5056

51-
// Coerces to targetEnd == 5
52-
assert.strictEqual(a.compare(b, 0, { valueOf: () => 5 }), -1);
57+
// Values do not get coerced.
58+
assert.throws(
59+
() => a.compare(b, 0, { valueOf: () => 5 }),
60+
{ code: 'ERR_INVALID_ARG_TYPE' }
61+
);
5362

54-
// zero length target
55-
assert.strictEqual(a.compare(b, Infinity, -Infinity), 1);
63+
// Infinity should not be coerced.
64+
assert.throws(
65+
() => a.compare(b, Infinity, -Infinity),
66+
{ code: 'ERR_OUT_OF_RANGE' }
67+
);
5668

5769
// Zero length target because default for targetEnd <= targetSource
58-
assert.strictEqual(a.compare(b, '0xff'), 1);
70+
assert.strictEqual(a.compare(b, 0xff), 1);
5971

60-
const oor = common.expectsError({ code: 'ERR_OUT_OF_RANGE' }, 7);
72+
assert.throws(
73+
() => a.compare(b, '0xff'),
74+
{ code: 'ERR_INVALID_ARG_TYPE' }
75+
);
76+
assert.throws(
77+
() => a.compare(b, 0, '0xff'),
78+
{ code: 'ERR_INVALID_ARG_TYPE' }
79+
);
80+
81+
const oor = { code: 'ERR_OUT_OF_RANGE' };
6182

6283
assert.throws(() => a.compare(b, 0, 100, 0), oor);
6384
assert.throws(() => a.compare(b, 0, 1, 0, 100), oor);
6485
assert.throws(() => a.compare(b, -1), oor);
65-
assert.throws(() => a.compare(b, 0, '0xff'), oor);
6686
assert.throws(() => a.compare(b, 0, Infinity), oor);
6787
assert.throws(() => a.compare(b, 0, 1, -1), oor);
6888
assert.throws(() => a.compare(b, -Infinity, Infinity), oor);

test/parallel/test-buffer-fill.js

+3-20
Original file line numberDiff line numberDiff line change
@@ -333,34 +333,17 @@ assert.strictEqual(
333333
// Make sure "end" is properly checked, even if it's magically mangled using
334334
// Symbol.toPrimitive.
335335
{
336-
let elseWasLast = false;
337-
const expectedErrorMessage =
338-
'The value of "end" is out of range. It must be >= 0 and <= 1. Received -1';
339-
340336
common.expectsError(() => {
341-
let ctr = 0;
342337
const end = {
343338
[Symbol.toPrimitive]() {
344-
// We use this condition to get around the check in lib/buffer.js
345-
if (ctr === 0) {
346-
elseWasLast = false;
347-
ctr++;
348-
return 1;
349-
}
350-
elseWasLast = true;
351-
// Once buffer.js calls the C++ implementation of fill, return -1
352-
return -1;
339+
return 1;
353340
}
354341
};
355342
Buffer.alloc(1).fill(Buffer.alloc(1), 0, end);
356343
}, {
357-
code: 'ERR_OUT_OF_RANGE',
358-
type: RangeError,
359-
message: expectedErrorMessage
344+
code: 'ERR_INVALID_ARG_TYPE',
345+
message: 'The "end" argument must be of type number. Received type object'
360346
});
361-
// Make sure -1 is making it to Buffer::Fill().
362-
assert.ok(elseWasLast,
363-
'internal API changed, -1 no longer in correct location');
364347
}
365348

366349
// Testing process.binding. Make sure "end" is properly checked for -1 wrap

test/parallel/test-buffer-write.js

+11-8
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
const common = require('../common');
44
const assert = require('assert');
55

6-
const outsideBounds = common.expectsError({
7-
code: 'ERR_BUFFER_OUT_OF_BOUNDS',
8-
type: RangeError,
9-
message: 'Attempt to write outside buffer bounds'
10-
}, 2);
11-
12-
assert.throws(() => Buffer.alloc(9).write('foo', -1), outsideBounds);
13-
assert.throws(() => Buffer.alloc(9).write('foo', 10), outsideBounds);
6+
[-1, 10].forEach((offset) => {
7+
assert.throws(
8+
() => Buffer.alloc(9).write('foo', offset),
9+
{
10+
code: 'ERR_OUT_OF_RANGE',
11+
name: 'RangeError',
12+
message: 'The value of "offset" is out of range. ' +
13+
`It must be >= 0 && <= 9. Received ${offset}`
14+
}
15+
);
16+
});
1417

1518
const resultMap = new Map([
1619
['utf8', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],

0 commit comments

Comments
 (0)