Skip to content

Commit ebcba82

Browse files
committed
buffer: use stricter range checks
This adds support to use offset and length arguments above uint32 and it 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. Fixes: nodejs#27043
1 parent 6eae414 commit ebcba82

File tree

6 files changed

+75
-70
lines changed

6 files changed

+75
-70
lines changed

doc/api/errors.md

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

1577-
<a id="ERR_NO_LONGER_SUPPORTED"></a>
1578-
### ERR_NO_LONGER_SUPPORTED
1579-
1580-
A Node.js API was called in an unsupported manner, such as
1581-
`Buffer.write(string, encoding, offset[, length])`.
1582-
15831577
<a id="ERR_OUT_OF_RANGE"></a>
15841578
### ERR_OUT_OF_RANGE
15851579

@@ -2095,6 +2089,12 @@ removed: v10.0.0
20952089

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

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

lib/buffer.js

+32-29
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ const {
6969
ERR_INVALID_ARG_VALUE,
7070
ERR_INVALID_BUFFER_SIZE,
7171
ERR_INVALID_OPT_VALUE,
72-
ERR_NO_LONGER_SUPPORTED,
7372
ERR_UNKNOWN_ENCODING
7473
},
7574
hideStackFrames
7675
} = require('internal/errors');
77-
const { validateString } = require('internal/validators');
76+
const {
77+
validateInteger,
78+
validateString
79+
} = require('internal/validators');
7880

7981
const {
8082
FastBuffer,
@@ -437,15 +439,15 @@ Buffer.concat = function concat(list, length) {
437439
if (list.length === 0)
438440
return new FastBuffer();
439441

440-
if (length === undefined) {
442+
if (length == null) {
441443
length = 0;
442444
for (i = 0; i < list.length; i++) {
443445
if (list[i].length) {
444446
length += list[i].length;
445447
}
446448
}
447449
} else {
448-
length = length >>> 0;
450+
validateInteger(length, 'length');
449451
}
450452

451453
const buffer = Buffer.allocUnsafe(length);
@@ -691,32 +693,32 @@ Buffer.prototype.compare = function compare(target,
691693
else if (targetStart < 0)
692694
throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart);
693695
else
694-
targetStart >>>= 0;
696+
validateInteger(targetStart, 'targetStart');
695697

696698
if (targetEnd === undefined)
697699
targetEnd = target.length;
698700
else if (targetEnd > target.length)
699701
throw new ERR_OUT_OF_RANGE('targetEnd', `<= ${target.length}`, targetEnd);
700702
else
701-
targetEnd >>>= 0;
703+
validateInteger(targetEnd, 'targetEnd');
702704

703705
if (sourceStart === undefined)
704706
sourceStart = 0;
705707
else if (sourceStart < 0)
706708
throw new ERR_OUT_OF_RANGE('sourceStart', '>= 0', sourceStart);
707709
else
708-
sourceStart >>>= 0;
710+
validateInteger(sourceStart, 'sourceStart');
709711

710712
if (sourceEnd === undefined)
711713
sourceEnd = this.length;
712714
else if (sourceEnd > this.length)
713715
throw new ERR_OUT_OF_RANGE('sourceEnd', `<= ${this.length}`, sourceEnd);
714716
else
715-
sourceEnd >>>= 0;
717+
validateInteger(sourceEnd, 'sourceEnd');
716718

717719
if (sourceStart >= sourceEnd)
718720
return (targetStart >= targetEnd ? 0 : -1);
719-
else if (targetStart >= targetEnd)
721+
if (targetStart >= targetEnd)
720722
return 1;
721723

722724
return compareOffset(this, target, targetStart, sourceStart, targetEnd,
@@ -861,11 +863,11 @@ function _fill(buf, value, offset, end, encoding) {
861863
if (end === undefined) {
862864
end = buf.length;
863865
} else {
866+
validateInteger(end, 'end');
864867
if (end > buf.length || end < 0)
865868
throw new ERR_OUT_OF_RANGE('end', `>= 0 and <= ${buf.length}`, end);
866-
end = end >>> 0;
867869
}
868-
offset = offset >>> 0;
870+
validateInteger(offset, 'offset');
869871
if (offset >= end)
870872
return buf;
871873
}
@@ -884,39 +886,40 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
884886
// Buffer#write(string);
885887
if (offset === undefined) {
886888
return this.utf8Write(string, 0, this.length);
887-
889+
}
888890
// Buffer#write(string, encoding)
889-
} else if (length === undefined && typeof offset === 'string') {
891+
if (length === undefined && typeof offset === 'string') {
890892
encoding = offset;
891893
length = this.length;
892894
offset = 0;
893895

894896
// Buffer#write(string, offset[, length][, encoding])
895-
} else if (isFinite(offset)) {
896-
offset = offset >>> 0;
897-
if (isFinite(length)) {
898-
length = length >>> 0;
897+
} else {
898+
if (offset === undefined) {
899+
offset = 0;
899900
} else {
900-
encoding = length;
901-
length = undefined;
901+
validateInteger(offset, 'offset');
902902
}
903903

904904
const remaining = this.length - offset;
905-
if (length === undefined || length > remaining)
905+
906+
if (length === undefined) {
906907
length = remaining;
908+
} else if (typeof length === 'string') {
909+
encoding = length;
910+
length = remaining;
911+
} else {
912+
validateInteger(length, 'length');
913+
if (length > remaining)
914+
length = remaining;
915+
}
907916

908-
if (string.length > 0 && (length < 0 || offset < 0))
917+
if (length < 0 || offset < 0)
909918
throw new ERR_BUFFER_OUT_OF_BOUNDS();
910-
} else {
911-
// If someone is still calling the obsolete form of write(), tell them.
912-
// we don't want eg buf.write("foo", "utf8", 10) to silently turn into
913-
// buf.write("foo", "utf8"), so we can't ignore extra args
914-
throw new ERR_NO_LONGER_SUPPORTED(
915-
'Buffer.write(string, encoding, offset[, length])'
916-
);
917919
}
918920

919-
if (!encoding) return this.utf8Write(string, offset, length);
921+
if (!encoding)
922+
return this.utf8Write(string, offset, length);
920923

921924
encoding += '';
922925
switch (encoding.length) {

lib/internal/errors.js

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

test/parallel/test-buffer-alloc.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ assert.throws(() => b.write('test string', 0, 5, 'invalid'),
5959
/Unknown encoding: invalid/);
6060
// Unsupported arguments for Buffer.write
6161
assert.throws(() => b.write('test', 'utf8', 0),
62-
/is no longer supported/);
63-
62+
{ code: 'ERR_INVALID_ARG_TYPE' });
6463

6564
// Try to create 0-length buffers. Should not throw.
6665
Buffer.from('');
@@ -110,8 +109,12 @@ b.copy(Buffer.alloc(1), 0, 2048, 2048);
110109
{
111110
const writeTest = Buffer.from('abcdes');
112111
writeTest.write('n', 'ascii');
113-
writeTest.write('o', '1', 'ascii');
114-
writeTest.write('d', '2', 'ascii');
112+
assert.throws(
113+
() => writeTest.write('o', '1', 'ascii'),
114+
{ code: 'ERR_INVALID_ARG_TYPE' }
115+
);
116+
writeTest.write('o', 1, 'ascii');
117+
writeTest.write('d', 2, 'ascii');
115118
writeTest.write('e', 3, 'ascii');
116119
writeTest.write('j', 4, 'ascii');
117120
assert.strictEqual(writeTest.toString(), 'nodejs');

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

+27-10
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,19 +48,33 @@ 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+
77+
const oor = { code: 'ERR_OUT_OF_RANGE' };
6178

6279
assert.throws(() => a.compare(b, 0, 100, 0), oor);
6380
assert.throws(() => a.compare(b, 0, 1, 0, 100), 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

0 commit comments

Comments
 (0)