summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Bridgewater <ruben@bridgewater.de>2019-04-02 00:01:29 +0200
committerRuben Bridgewater <ruben@bridgewater.de>2019-04-15 17:21:34 +0200
commit693401d0ddd752e5fa47b882e56e252c42c94c0e (patch)
treefadabceddc0df7bd8d982bcd6c00cc11f3565e5d
parent2fed83dee884c3bddafa67bb53abf507db1a8ba3 (diff)
downloadandroid-node-v8-693401d0ddd752e5fa47b882e56e252c42c94c0e.tar.gz
android-node-v8-693401d0ddd752e5fa47b882e56e252c42c94c0e.tar.bz2
android-node-v8-693401d0ddd752e5fa47b882e56e252c42c94c0e.zip
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: https://github.com/nodejs/node/pull/27045 Fixes: https://github.com/nodejs/node/issues/27043 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
-rw-r--r--doc/api/errors.md12
-rw-r--r--lib/buffer.js73
-rw-r--r--lib/internal/errors.js1
-rw-r--r--test/parallel/test-buffer-alloc.js30
-rw-r--r--test/parallel/test-buffer-compare-offset.js42
-rw-r--r--test/parallel/test-buffer-fill.js23
-rw-r--r--test/parallel/test-buffer-write.js19
7 files changed, 98 insertions, 102 deletions
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 547d1ff8d2..e984487820 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1564,12 +1564,6 @@ OpenSSL crypto support.
An attempt was made to use features that require [ICU][], but Node.js was not
compiled with ICU support.
-<a id="ERR_NO_LONGER_SUPPORTED"></a>
-### ERR_NO_LONGER_SUPPORTED
-
-A Node.js API was called in an unsupported manner, such as
-`Buffer.write(string, encoding, offset[, length])`.
-
<a id="ERR_OUT_OF_RANGE"></a>
### ERR_OUT_OF_RANGE
@@ -2096,6 +2090,12 @@ removed: v10.0.0
Used by the `N-API` when `Constructor.prototype` is not an object.
+<a id="ERR_NO_LONGER_SUPPORTED"></a>
+### ERR_NO_LONGER_SUPPORTED
+
+A Node.js API was called in an unsupported manner, such as
+`Buffer.write(string, encoding, offset[, length])`.
+
<a id="ERR_OUTOFMEMORY"></a>
### ERR_OUTOFMEMORY
<!-- YAML
diff --git a/lib/buffer.js b/lib/buffer.js
index 005532bbee..1fac94d568 100644
--- a/lib/buffer.js
+++ b/lib/buffer.js
@@ -65,17 +65,18 @@ const {
const {
codes: {
ERR_BUFFER_OUT_OF_BOUNDS,
- ERR_OUT_OF_RANGE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_BUFFER_SIZE,
ERR_INVALID_OPT_VALUE,
- ERR_NO_LONGER_SUPPORTED,
ERR_UNKNOWN_ENCODING
},
hideStackFrames
} = require('internal/errors');
-const { validateString } = require('internal/validators');
+const {
+ validateInt32,
+ validateString
+} = require('internal/validators');
const {
FastBuffer,
@@ -445,7 +446,7 @@ Buffer.concat = function concat(list, length) {
}
}
} else {
- length = length >>> 0;
+ validateInt32(length, 'length', 0);
}
const buffer = Buffer.allocUnsafe(length);
@@ -700,35 +701,27 @@ Buffer.prototype.compare = function compare(target,
if (targetStart === undefined)
targetStart = 0;
- else if (targetStart < 0)
- throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart);
else
- targetStart >>>= 0;
+ validateInt32(targetStart, 'targetStart', 0);
if (targetEnd === undefined)
targetEnd = target.length;
- else if (targetEnd > target.length)
- throw new ERR_OUT_OF_RANGE('targetEnd', `<= ${target.length}`, targetEnd);
else
- targetEnd >>>= 0;
+ validateInt32(targetEnd, 'targetEnd', 0, target.length);
if (sourceStart === undefined)
sourceStart = 0;
- else if (sourceStart < 0)
- throw new ERR_OUT_OF_RANGE('sourceStart', '>= 0', sourceStart);
else
- sourceStart >>>= 0;
+ validateInt32(sourceStart, 'sourceStart', 0);
if (sourceEnd === undefined)
sourceEnd = this.length;
- else if (sourceEnd > this.length)
- throw new ERR_OUT_OF_RANGE('sourceEnd', `<= ${this.length}`, sourceEnd);
else
- sourceEnd >>>= 0;
+ validateInt32(sourceEnd, 'sourceEnd', 0, this.length);
if (sourceStart >= sourceEnd)
return (targetStart >= targetEnd ? 0 : -1);
- else if (targetStart >= targetEnd)
+ if (targetStart >= targetEnd)
return 1;
return compareOffset(this, target, targetStart, sourceStart, targetEnd,
@@ -867,17 +860,13 @@ function _fill(buf, value, offset, end, encoding) {
offset = 0;
end = buf.length;
} else {
+ validateInt32(offset, 'offset', 0);
// Invalid ranges are not set to a default, so can range check early.
- if (offset < 0)
- throw new ERR_OUT_OF_RANGE('offset', '>= 0', offset);
if (end === undefined) {
end = buf.length;
} else {
- if (end > buf.length || end < 0)
- throw new ERR_OUT_OF_RANGE('end', `>= 0 and <= ${buf.length}`, end);
- end = end >>> 0;
+ validateInt32(end, 'end', 0, buf.length);
}
- offset = offset >>> 0;
if (offset >= end)
return buf;
}
@@ -896,39 +885,37 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
// Buffer#write(string);
if (offset === undefined) {
return this.utf8Write(string, 0, this.length);
-
+ }
// Buffer#write(string, encoding)
- } else if (length === undefined && typeof offset === 'string') {
+ if (length === undefined && typeof offset === 'string') {
encoding = offset;
length = this.length;
offset = 0;
// Buffer#write(string, offset[, length][, encoding])
- } else if (isFinite(offset)) {
- offset = offset >>> 0;
- if (isFinite(length)) {
- length = length >>> 0;
+ } else {
+ if (offset === undefined) {
+ offset = 0;
} else {
- encoding = length;
- length = undefined;
+ validateInt32(offset, 'offset', 0, this.length);
}
const remaining = this.length - offset;
- if (length === undefined || length > remaining)
- length = remaining;
- if (string.length > 0 && (length < 0 || offset < 0))
- throw new ERR_BUFFER_OUT_OF_BOUNDS();
- } else {
- // If someone is still calling the obsolete form of write(), tell them.
- // we don't want eg buf.write("foo", "utf8", 10) to silently turn into
- // buf.write("foo", "utf8"), so we can't ignore extra args
- throw new ERR_NO_LONGER_SUPPORTED(
- 'Buffer.write(string, encoding, offset[, length])'
- );
+ if (length === undefined) {
+ length = remaining;
+ } else if (typeof length === 'string') {
+ encoding = length;
+ length = remaining;
+ } else {
+ validateInt32(length, 'length', 0, this.length);
+ if (length > remaining)
+ length = remaining;
+ }
}
- if (!encoding) return this.utf8Write(string, offset, length);
+ if (!encoding)
+ return this.utf8Write(string, offset, length);
encoding += '';
switch (encoding.length) {
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 6334626d3c..bc3f43653a 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -994,7 +994,6 @@ E('ERR_NO_CRYPTO',
'Node.js is not compiled with OpenSSL crypto support', Error);
E('ERR_NO_ICU',
'%s is not supported on Node.js compiled without ICU', TypeError);
-E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported', Error);
E('ERR_OUT_OF_RANGE',
(str, range, input, replaceDefaultBoolean = false) => {
assert(range, 'Missing "range" argument');
diff --git a/test/parallel/test-buffer-alloc.js b/test/parallel/test-buffer-alloc.js
index 8369936b4d..75c2cae2b0 100644
--- a/test/parallel/test-buffer-alloc.js
+++ b/test/parallel/test-buffer-alloc.js
@@ -5,6 +5,12 @@ const vm = require('vm');
const SlowBuffer = require('buffer').SlowBuffer;
+// Verify the maximum Uint8Array size. There is no concrete limit by spec. The
+// internal limits should be updated if this fails.
+assert.throws(
+ () => new Uint8Array(2 ** 31),
+ { message: 'Invalid typed array length: 2147483648' }
+);
const b = Buffer.allocUnsafe(1024);
assert.strictEqual(b.length, 1024);
@@ -59,8 +65,7 @@ assert.throws(() => b.write('test string', 0, 5, 'invalid'),
/Unknown encoding: invalid/);
// Unsupported arguments for Buffer.write
assert.throws(() => b.write('test', 'utf8', 0),
- /is no longer supported/);
-
+ { code: 'ERR_INVALID_ARG_TYPE' });
// Try to create 0-length buffers. Should not throw.
Buffer.from('');
@@ -74,27 +79,22 @@ new Buffer('', 'latin1');
new Buffer('', 'binary');
Buffer(0);
-const outOfBoundsError = {
- code: 'ERR_BUFFER_OUT_OF_BOUNDS',
- type: RangeError
-};
-
const outOfRangeError = {
code: 'ERR_OUT_OF_RANGE',
type: RangeError
};
// Try to write a 0-length string beyond the end of b
-common.expectsError(() => b.write('', 2048), outOfBoundsError);
+common.expectsError(() => b.write('', 2048), outOfRangeError);
// Throw when writing to negative offset
-common.expectsError(() => b.write('a', -1), outOfBoundsError);
+common.expectsError(() => b.write('a', -1), outOfRangeError);
// Throw when writing past bounds from the pool
-common.expectsError(() => b.write('a', 2048), outOfBoundsError);
+common.expectsError(() => b.write('a', 2048), outOfRangeError);
// Throw when writing to negative offset
-common.expectsError(() => b.write('a', -1), outOfBoundsError);
+common.expectsError(() => b.write('a', -1), outOfRangeError);
// Try to copy 0 bytes worth of data into an empty buffer
b.copy(Buffer.alloc(0), 0, 0, 0);
@@ -110,8 +110,12 @@ b.copy(Buffer.alloc(1), 0, 2048, 2048);
{
const writeTest = Buffer.from('abcdes');
writeTest.write('n', 'ascii');
- writeTest.write('o', '1', 'ascii');
- writeTest.write('d', '2', 'ascii');
+ assert.throws(
+ () => writeTest.write('o', '1', 'ascii'),
+ { code: 'ERR_INVALID_ARG_TYPE' }
+ );
+ writeTest.write('o', 1, 'ascii');
+ writeTest.write('d', 2, 'ascii');
writeTest.write('e', 3, 'ascii');
writeTest.write('j', 4, 'ascii');
assert.strictEqual(writeTest.toString(), 'nodejs');
diff --git a/test/parallel/test-buffer-compare-offset.js b/test/parallel/test-buffer-compare-offset.js
index 8c590f52ed..3769e4d41a 100644
--- a/test/parallel/test-buffer-compare-offset.js
+++ b/test/parallel/test-buffer-compare-offset.js
@@ -10,7 +10,7 @@ assert.strictEqual(a.compare(b), -1);
// Equivalent to a.compare(b).
assert.strictEqual(a.compare(b, 0), -1);
-assert.strictEqual(a.compare(b, '0'), -1);
+assert.throws(() => a.compare(b, '0'), { code: 'ERR_INVALID_ARG_TYPE' });
assert.strictEqual(a.compare(b, undefined), -1);
// Equivalent to a.compare(b).
@@ -18,7 +18,10 @@ assert.strictEqual(a.compare(b, 0, undefined, 0), -1);
// Zero-length target, return 1
assert.strictEqual(a.compare(b, 0, 0, 0), 1);
-assert.strictEqual(a.compare(b, '0', '0', '0'), 1);
+assert.throws(
+ () => a.compare(b, 0, '0', '0'),
+ { code: 'ERR_INVALID_ARG_TYPE' }
+);
// Equivalent to Buffer.compare(a, b.slice(6, 10))
assert.strictEqual(a.compare(b, 6, 10), 1);
@@ -45,24 +48,41 @@ assert.strictEqual(a.compare(b, 0, 7, 4), -1);
// Equivalent to Buffer.compare(a.slice(4, 6), b.slice(0, 7));
assert.strictEqual(a.compare(b, 0, 7, 4, 6), -1);
-// zero length target
-assert.strictEqual(a.compare(b, 0, null), 1);
+// Null is ambiguous.
+assert.throws(
+ () => a.compare(b, 0, null),
+ { code: 'ERR_INVALID_ARG_TYPE' }
+);
-// Coerces to targetEnd == 5
-assert.strictEqual(a.compare(b, 0, { valueOf: () => 5 }), -1);
+// Values do not get coerced.
+assert.throws(
+ () => a.compare(b, 0, { valueOf: () => 5 }),
+ { code: 'ERR_INVALID_ARG_TYPE' }
+);
-// zero length target
-assert.strictEqual(a.compare(b, Infinity, -Infinity), 1);
+// Infinity should not be coerced.
+assert.throws(
+ () => a.compare(b, Infinity, -Infinity),
+ { code: 'ERR_OUT_OF_RANGE' }
+);
// Zero length target because default for targetEnd <= targetSource
-assert.strictEqual(a.compare(b, '0xff'), 1);
+assert.strictEqual(a.compare(b, 0xff), 1);
-const oor = common.expectsError({ code: 'ERR_OUT_OF_RANGE' }, 7);
+assert.throws(
+ () => a.compare(b, '0xff'),
+ { code: 'ERR_INVALID_ARG_TYPE' }
+);
+assert.throws(
+ () => a.compare(b, 0, '0xff'),
+ { code: 'ERR_INVALID_ARG_TYPE' }
+);
+
+const oor = { code: 'ERR_OUT_OF_RANGE' };
assert.throws(() => a.compare(b, 0, 100, 0), oor);
assert.throws(() => a.compare(b, 0, 1, 0, 100), oor);
assert.throws(() => a.compare(b, -1), oor);
-assert.throws(() => a.compare(b, 0, '0xff'), oor);
assert.throws(() => a.compare(b, 0, Infinity), oor);
assert.throws(() => a.compare(b, 0, 1, -1), oor);
assert.throws(() => a.compare(b, -Infinity, Infinity), oor);
diff --git a/test/parallel/test-buffer-fill.js b/test/parallel/test-buffer-fill.js
index 61ff6bbb50..3dd11bce26 100644
--- a/test/parallel/test-buffer-fill.js
+++ b/test/parallel/test-buffer-fill.js
@@ -333,34 +333,17 @@ assert.strictEqual(
// Make sure "end" is properly checked, even if it's magically mangled using
// Symbol.toPrimitive.
{
- let elseWasLast = false;
- const expectedErrorMessage =
- 'The value of "end" is out of range. It must be >= 0 and <= 1. Received -1';
-
common.expectsError(() => {
- let ctr = 0;
const end = {
[Symbol.toPrimitive]() {
- // We use this condition to get around the check in lib/buffer.js
- if (ctr === 0) {
- elseWasLast = false;
- ctr++;
- return 1;
- }
- elseWasLast = true;
- // Once buffer.js calls the C++ implementation of fill, return -1
- return -1;
+ return 1;
}
};
Buffer.alloc(1).fill(Buffer.alloc(1), 0, end);
}, {
- code: 'ERR_OUT_OF_RANGE',
- type: RangeError,
- message: expectedErrorMessage
+ code: 'ERR_INVALID_ARG_TYPE',
+ message: 'The "end" argument must be of type number. Received type object'
});
- // Make sure -1 is making it to Buffer::Fill().
- assert.ok(elseWasLast,
- 'internal API changed, -1 no longer in correct location');
}
// Testing process.binding. Make sure "end" is properly checked for -1 wrap
diff --git a/test/parallel/test-buffer-write.js b/test/parallel/test-buffer-write.js
index a0b86844d4..e99cdb358a 100644
--- a/test/parallel/test-buffer-write.js
+++ b/test/parallel/test-buffer-write.js
@@ -3,14 +3,17 @@
const common = require('../common');
const assert = require('assert');
-const outsideBounds = common.expectsError({
- code: 'ERR_BUFFER_OUT_OF_BOUNDS',
- type: RangeError,
- message: 'Attempt to write outside buffer bounds'
-}, 2);
-
-assert.throws(() => Buffer.alloc(9).write('foo', -1), outsideBounds);
-assert.throws(() => Buffer.alloc(9).write('foo', 10), outsideBounds);
+[-1, 10].forEach((offset) => {
+ assert.throws(
+ () => Buffer.alloc(9).write('foo', offset),
+ {
+ code: 'ERR_OUT_OF_RANGE',
+ name: 'RangeError',
+ message: 'The value of "offset" is out of range. ' +
+ `It must be >= 0 && <= 9. Received ${offset}`
+ }
+ );
+});
const resultMap = new Map([
['utf8', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],