crypto: fix error handling

This fixes multiple cases where the wrong error was returned in
case of e.g. a overflow / wrong type.

PR-URL: https://github.com/nodejs/node/pull/19445
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Ruben Bridgewater 2018-03-19 14:13:39 +01:00
parent c1278e5329
commit 333adf61eb
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
2 changed files with 116 additions and 274 deletions

View File

@ -13,37 +13,44 @@ const {
const { kMaxLength } = require('buffer'); const { kMaxLength } = require('buffer');
const kMaxUint32 = Math.pow(2, 32) - 1; const kMaxUint32 = Math.pow(2, 32) - 1;
const kMaxPossibleLength = Math.min(kMaxLength, kMaxUint32);
function assertOffset(offset, length) { function assertOffset(offset, elementSize, length) {
if (typeof offset !== 'number' || Number.isNaN(offset)) { if (typeof offset !== 'number') {
throw new ERR_INVALID_ARG_TYPE('offset', 'number'); throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);
} }
if (offset > kMaxUint32 || offset < 0) { offset *= elementSize;
throw new ERR_INVALID_ARG_TYPE('offset', 'uint32');
const maxLength = Math.min(length, kMaxPossibleLength);
if (Number.isNaN(offset) || offset > maxLength || offset < 0) {
throw new ERR_OUT_OF_RANGE('offset', `>= 0 && <= ${maxLength}`, offset);
} }
if (offset > kMaxLength || offset > length) { return offset;
throw new ERR_OUT_OF_RANGE('offset');
}
} }
function assertSize(size, offset = 0, length = Infinity) { function assertSize(size, elementSize, offset, length) {
if (typeof size !== 'number' || Number.isNaN(size)) { if (typeof size !== 'number') {
throw new ERR_INVALID_ARG_TYPE('size', 'number'); throw new ERR_INVALID_ARG_TYPE('size', 'number', size);
} }
if (size > kMaxUint32 || size < 0) { size *= elementSize;
throw new ERR_INVALID_ARG_TYPE('size', 'uint32');
if (Number.isNaN(size) || size > kMaxPossibleLength || size < 0) {
throw new ERR_OUT_OF_RANGE('size',
`>= 0 && <= ${kMaxPossibleLength}`, size);
} }
if (size + offset > length || size > kMaxLength) { if (size + offset > length) {
throw new ERR_OUT_OF_RANGE('size'); throw new ERR_OUT_OF_RANGE('size + offset', `<= ${length}`, size + offset);
} }
return size;
} }
function randomBytes(size, cb) { function randomBytes(size, cb) {
assertSize(size); assertSize(size, 1, 0, Infinity);
if (cb !== undefined && typeof cb !== 'function') if (cb !== undefined && typeof cb !== 'function')
throw new ERR_INVALID_CALLBACK(); throw new ERR_INVALID_CALLBACK();
return _randomBytes(size, cb); return _randomBytes(size, cb);
@ -56,17 +63,14 @@ function randomFillSync(buf, offset = 0, size) {
const elementSize = buf.BYTES_PER_ELEMENT || 1; const elementSize = buf.BYTES_PER_ELEMENT || 1;
offset *= elementSize; offset = assertOffset(offset, elementSize, buf.byteLength);
assertOffset(offset, buf.byteLength);
if (size === undefined) { if (size === undefined) {
size = buf.byteLength - offset; size = buf.byteLength - offset;
} else { } else {
size *= elementSize; size = assertSize(size, elementSize, offset, buf.byteLength);
} }
assertSize(size, offset, buf.byteLength);
return _randomFill(buf, offset, size); return _randomFill(buf, offset, size);
} }
@ -83,20 +87,19 @@ function randomFill(buf, offset, size, cb) {
size = buf.bytesLength; size = buf.bytesLength;
} else if (typeof size === 'function') { } else if (typeof size === 'function') {
cb = size; cb = size;
offset *= elementSize;
size = buf.byteLength - offset; size = buf.byteLength - offset;
} else if (typeof cb !== 'function') { } else if (typeof cb !== 'function') {
throw new ERR_INVALID_CALLBACK(); throw new ERR_INVALID_CALLBACK();
} }
offset = assertOffset(offset, elementSize, buf.byteLength);
if (size === undefined) { if (size === undefined) {
size = buf.byteLength - offset; size = buf.byteLength - offset;
} else { } else {
size *= elementSize; size = assertSize(size, elementSize, offset, buf.byteLength);
} }
assertOffset(offset, buf.byteLength);
assertSize(size, offset, buf.byteLength);
return _randomFill(buf, offset, size, cb); return _randomFill(buf, offset, size, cb);
} }

View File

@ -27,42 +27,49 @@ if (!common.hasCrypto)
const assert = require('assert'); const assert = require('assert');
const crypto = require('crypto'); const crypto = require('crypto');
const { kMaxLength } = require('buffer');
const kMaxUint32 = Math.pow(2, 32) - 1;
const kMaxPossibleLength = Math.min(kMaxLength, kMaxUint32);
crypto.DEFAULT_ENCODING = 'buffer'; crypto.DEFAULT_ENCODING = 'buffer';
// bump, we register a lot of exit listeners // bump, we register a lot of exit listeners
process.setMaxListeners(256); process.setMaxListeners(256);
[crypto.randomBytes, crypto.pseudoRandomBytes].forEach(function(f) { {
[-1, undefined, null, false, true, {}, []].forEach(function(value) { [crypto.randomBytes, crypto.pseudoRandomBytes].forEach((f) => {
[undefined, null, false, true, {}, []].forEach((value) => {
common.expectsError( const errObj = {
() => f(value),
{
code: 'ERR_INVALID_ARG_TYPE', code: 'ERR_INVALID_ARG_TYPE',
type: TypeError, name: 'TypeError [ERR_INVALID_ARG_TYPE]',
message: /^The "size" argument must be of type (number|uint32)$/ message: 'The "size" argument must be of type number. ' +
} `Received type ${typeof value}`
); };
assert.throws(() => f(value), errObj);
assert.throws(() => f(value, common.mustNotCall()), errObj);
});
common.expectsError( [-1, NaN, 2 ** 32].forEach((value) => {
() => f(value, common.mustNotCall()), const errObj = {
{ code: 'ERR_OUT_OF_RANGE',
code: 'ERR_INVALID_ARG_TYPE', name: 'RangeError [ERR_OUT_OF_RANGE]',
type: TypeError, message: 'The value of "size" is out of range. It must be >= 0 && <= ' +
message: /^The "size" argument must be of type (number|uint32)$/ `${kMaxPossibleLength}. Received ${value}`
} };
); assert.throws(() => f(value), errObj);
}); assert.throws(() => f(value, common.mustNotCall()), errObj);
});
[0, 1, 2, 4, 16, 256, 1024, 101.2].forEach(function(len) { [0, 1, 2, 4, 16, 256, 1024, 101.2].forEach((len) => {
f(len, common.mustCall(function(ex, buf) { f(len, common.mustCall((ex, buf) => {
assert.strictEqual(ex, null); assert.strictEqual(ex, null);
assert.strictEqual(buf.length, Math.floor(len)); assert.strictEqual(buf.length, Math.floor(len));
assert.ok(Buffer.isBuffer(buf)); assert.ok(Buffer.isBuffer(buf));
})); }));
});
}); });
}); }
{ {
const buf = Buffer.alloc(10); const buf = Buffer.alloc(10);
@ -181,252 +188,84 @@ process.setMaxListeners(256);
} }
{ {
const bufs = [ [
Buffer.alloc(10), Buffer.alloc(10),
new Uint8Array(new Array(10).fill(0)) new Uint8Array(new Array(10).fill(0))
]; ].forEach((buf) => {
const max = require('buffer').kMaxLength + 1;
for (const buf of bufs) {
const len = Buffer.byteLength(buf); const len = Buffer.byteLength(buf);
assert.strictEqual(len, 10, `Expected byteLength of 10, got ${len}`); assert.strictEqual(len, 10, `Expected byteLength of 10, got ${len}`);
common.expectsError( const typeErrObj = {
() => crypto.randomFillSync(buf, 'test'), code: 'ERR_INVALID_ARG_TYPE',
{ name: 'TypeError [ERR_INVALID_ARG_TYPE]',
code: 'ERR_INVALID_ARG_TYPE', message: 'The "offset" argument must be of type number. ' +
type: TypeError, 'Received type string'
message: 'The "offset" argument must be of type number' };
}
);
common.expectsError( assert.throws(() => crypto.randomFillSync(buf, 'test'), typeErrObj);
() => crypto.randomFillSync(buf, NaN),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type number'
}
);
common.expectsError( assert.throws(
() => crypto.randomFill(buf, 'test', common.mustNotCall()), () => crypto.randomFill(buf, 'test', common.mustNotCall()),
{ typeErrObj);
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type number'
}
);
common.expectsError( typeErrObj.message = 'The "size" argument must be of type number. ' +
() => crypto.randomFill(buf, NaN, common.mustNotCall()), 'Received type string';
{ assert.throws(() => crypto.randomFillSync(buf, 0, 'test'), typeErrObj);
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type number'
}
);
common.expectsError( assert.throws(
() => crypto.randomFillSync(buf, 11),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The value of "offset" is out of range.'
}
);
common.expectsError(
() => crypto.randomFillSync(buf, max),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The value of "offset" is out of range.'
}
);
common.expectsError(
() => crypto.randomFill(buf, 11, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The value of "offset" is out of range.'
}
);
common.expectsError(
() => crypto.randomFill(buf, max, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The value of "offset" is out of range.'
}
);
common.expectsError(
() => crypto.randomFillSync(buf, 0, 'test'),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
);
common.expectsError(
() => crypto.randomFillSync(buf, 0, NaN),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
);
common.expectsError(
() => crypto.randomFill(buf, 0, 'test', common.mustNotCall()), () => crypto.randomFill(buf, 0, 'test', common.mustNotCall()),
{ typeErrObj
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
); );
common.expectsError( [NaN, kMaxPossibleLength + 1, -10, (-1 >>> 0) + 1].forEach((offsetSize) => {
() => crypto.randomFill(buf, 0, NaN, common.mustNotCall()), const errObj = {
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type number'
}
);
{
const size = (-1 >>> 0) + 1;
common.expectsError(
() => crypto.randomFillSync(buf, 0, -10),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
common.expectsError(
() => crypto.randomFillSync(buf, 0, size),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
common.expectsError(
() => crypto.randomFill(buf, 0, -10, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
common.expectsError(
() => crypto.randomFill(buf, 0, size, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "size" argument must be of type uint32'
}
);
}
common.expectsError(
() => crypto.randomFillSync(buf, -10),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
common.expectsError(
() => crypto.randomFill(buf, -10, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
common.expectsError(
() => crypto.randomFillSync(buf, 1, 10),
{
code: 'ERR_OUT_OF_RANGE', code: 'ERR_OUT_OF_RANGE',
type: RangeError, name: 'RangeError [ERR_OUT_OF_RANGE]',
message: 'The value of "size" is out of range.' message: 'The value of "offset" is out of range. ' +
} `It must be >= 0 && <= 10. Received ${offsetSize}`
); };
common.expectsError( assert.throws(() => crypto.randomFillSync(buf, offsetSize), errObj);
assert.throws(
() => crypto.randomFill(buf, offsetSize, common.mustNotCall()),
errObj);
errObj.message = 'The value of "size" is out of range. It must be >= ' +
`0 && <= ${kMaxPossibleLength}. Received ${offsetSize}`;
assert.throws(() => crypto.randomFillSync(buf, 1, offsetSize), errObj);
assert.throws(
() => crypto.randomFill(buf, 1, offsetSize, common.mustNotCall()),
errObj
);
});
const rangeErrObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError [ERR_OUT_OF_RANGE]',
message: 'The value of "size + offset" is out of range. ' +
'It must be <= 10. Received 11'
};
assert.throws(() => crypto.randomFillSync(buf, 1, 10), rangeErrObj);
assert.throws(
() => crypto.randomFill(buf, 1, 10, common.mustNotCall()), () => crypto.randomFill(buf, 1, 10, common.mustNotCall()),
{ rangeErrObj
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The value of "size" is out of range.'
}
); );
});
common.expectsError(
() => crypto.randomFillSync(buf, 0, 12),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The value of "size" is out of range.'
}
);
common.expectsError(
() => crypto.randomFill(buf, 0, 12, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
type: RangeError,
message: 'The value of "size" is out of range.'
}
);
{
// Offset is too big
const offset = (-1 >>> 0) + 1;
common.expectsError(
() => crypto.randomFillSync(buf, offset, 10),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
common.expectsError(
() => crypto.randomFill(buf, offset, 10, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "offset" argument must be of type uint32'
}
);
}
}
} }
// https://github.com/nodejs/node-v0.x-archive/issues/5126, // https://github.com/nodejs/node-v0.x-archive/issues/5126,
// "FATAL ERROR: v8::Object::SetIndexedPropertiesToExternalArrayData() length // "FATAL ERROR: v8::Object::SetIndexedPropertiesToExternalArrayData() length
// exceeds max acceptable value" // exceeds max acceptable value"
common.expectsError( assert.throws(
() => crypto.randomBytes((-1 >>> 0) + 1), () => crypto.randomBytes((-1 >>> 0) + 1),
{ {
code: 'ERR_INVALID_ARG_TYPE', code: 'ERR_OUT_OF_RANGE',
type: TypeError, name: 'RangeError [ERR_OUT_OF_RANGE]',
message: 'The "size" argument must be of type uint32' message: 'The value of "size" is out of range. ' +
`It must be >= 0 && <= ${kMaxPossibleLength}. Received 4294967296`
} }
); );