From e0ee90051ebd13475bbcff4d371330aa4f9bd1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=A5=95GaoYi?= Date: Sun, 5 Jan 2025 21:15:32 +0800 Subject: [PATCH] fix: Test for invalid byte array sizes and ranges in `v1()`, `v4()`, and `v7()` (#845) --------- Co-authored-by: Robert Kieffer --- src/test/v1.test.ts | 24 ++++++++++++++++++++++++ src/test/v4.test.ts | 24 ++++++++++++++++++++++++ src/test/v7.test.ts | 26 +++++++++++++++++++++++++- src/v1.ts | 11 ++++++++++- src/v4.ts | 10 ++++++++-- src/v7.ts | 10 +++++++++- 6 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/test/v1.test.ts b/src/test/v1.test.ts index 709b1680..45d73462 100644 --- a/src/test/v1.test.ts +++ b/src/test/v1.test.ts @@ -168,4 +168,28 @@ describe('v1', () => { assert.deepStrictEqual(updateV1State(state, now, RFC_RANDOM), expected, `Failed: ${title}`); } }); + + test('throws when option.random is too short', () => { + const random = Uint8Array.of(16); + const buffer = new Uint8Array(16).fill(0); + assert.throws(() => { + v1({ random }, buffer); + }); + }); + + test('throws when options.rng() is too short', () => { + const buffer = new Uint8Array(16); + const rng = () => Uint8Array.of(0); // length = 1 + assert.throws(() => { + v1({ rng }, buffer); + }); + }); + + test('throws RangeError for out-of-range indexes', () => { + const buf15 = new Uint8Array(15); + const buf30 = new Uint8Array(30); + assert.throws(() => v1({}, buf15)); + assert.throws(() => v1({}, buf30, -1)); + assert.throws(() => v1({}, buf30, 15)); + }); }); diff --git a/src/test/v4.test.ts b/src/test/v4.test.ts index b46ddd67..8a54aa84 100644 --- a/src/test/v4.test.ts +++ b/src/test/v4.test.ts @@ -114,4 +114,28 @@ describe('v4', () => { assert.deepEqual(buffer, expectedBuf); }); + + test('throws when option.random is too short', () => { + const random = Uint8Array.of(16); + const buffer = new Uint8Array(16).fill(0); + assert.throws(() => { + v4({ random }, buffer); + }); + }); + + test('throws when options.rng() is too short', () => { + const buffer = new Uint8Array(16); + const rng = () => Uint8Array.of(0); // length = 1 + assert.throws(() => { + v4({ rng }, buffer); + }); + }); + + test('throws RangeError for out-of-range indexes', () => { + const buf15 = new Uint8Array(15); + const buf30 = new Uint8Array(30); + assert.throws(() => v4({}, buf15)); + assert.throws(() => v4({}, buf30, -1)); + assert.throws(() => v4({}, buf30, 15)); + }); }); diff --git a/src/test/v7.test.ts b/src/test/v7.test.ts index a29600fb..6cccb15a 100644 --- a/src/test/v7.test.ts +++ b/src/test/v7.test.ts @@ -1,8 +1,8 @@ import * as assert from 'assert'; import test, { describe } from 'node:test'; -import { Version7Options } from '../types.js'; import parse from '../parse.js'; import stringify from '../stringify.js'; +import { Version7Options } from '../types.js'; import v7, { updateV7State } from '../v7.js'; // Fixture values for testing with the rfc v7 UUID example: @@ -267,4 +267,28 @@ describe('v7', () => { assert.notStrictEqual(stringify(buf), id); } }); + + test('throws when option.random is too short', () => { + const random = Uint8Array.of(16); + const buffer = new Uint8Array(16).fill(0); + assert.throws(() => { + v7({ random }, buffer); + }); + }); + + test('throws when options.rng() is too short', () => { + const buffer = new Uint8Array(16); + const rng = () => Uint8Array.of(0); // length = 1 + assert.throws(() => { + v7({ rng }, buffer); + }); + }); + + test('throws RangeError for out-of-range indexes', () => { + const buf15 = new Uint8Array(15); + const buf30 = new Uint8Array(30); + assert.throws(() => v7({}, buf15)); + assert.throws(() => v7({}, buf30, -1)); + assert.throws(() => v7({}, buf30, 15)); + }); }); diff --git a/src/v1.ts b/src/v1.ts index f96d4e33..24390517 100644 --- a/src/v1.ts +++ b/src/v1.ts @@ -1,6 +1,6 @@ -import { UUIDTypes, Version1Options } from './types.js'; import rng from './rng.js'; import { unsafeStringify } from './stringify.js'; +import { UUIDTypes, Version1Options } from './types.js'; // **`v1()` - Generate time-based UUID** // @@ -139,11 +139,20 @@ function v1Bytes( buf?: Uint8Array, offset = 0 ) { + if (rnds.length < 16) { + throw new Error('Random bytes length must be >= 16'); + } + // Defaults if (!buf) { buf = new Uint8Array(16); offset = 0; + } else { + if (offset < 0 || offset + 16 > buf.length) { + throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); + } } + msecs ??= Date.now(); nsecs ??= 0; clockseq ??= ((rnds[8] << 8) | rnds[9]) & 0x3fff; diff --git a/src/v4.ts b/src/v4.ts index b5fdaf64..0459b59b 100644 --- a/src/v4.ts +++ b/src/v4.ts @@ -1,7 +1,7 @@ -import { UUIDTypes, Version4Options } from './types.js'; import native from './native.js'; import rng from './rng.js'; import { unsafeStringify } from './stringify.js'; +import { UUIDTypes, Version4Options } from './types.js'; function v4(options?: Version4Options, buf?: undefined, offset?: number): string; function v4(options: Version4Options | undefined, buf: Uint8Array, offset?: number): Uint8Array; @@ -12,7 +12,10 @@ function v4(options?: Version4Options, buf?: Uint8Array, offset?: number): UUIDT options = options || {}; - const rnds = options.random || (options.rng || rng)(); + const rnds = options.random ?? options.rng?.() ?? rng(); + if (rnds.length < 16) { + throw new Error('Random bytes length must be >= 16'); + } // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` rnds[6] = (rnds[6] & 0x0f) | 0x40; @@ -21,6 +24,9 @@ function v4(options?: Version4Options, buf?: Uint8Array, offset?: number): UUIDT // Copy bytes to buffer, if provided if (buf) { offset = offset || 0; + if (offset < 0 || offset + 16 > buf.length) { + throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); + } for (let i = 0; i < 16; ++i) { buf[offset + i] = rnds[i]; diff --git a/src/v7.ts b/src/v7.ts index 33e6d2b2..95198788 100644 --- a/src/v7.ts +++ b/src/v7.ts @@ -1,6 +1,6 @@ -import { UUIDTypes, Version7Options } from './types.js'; import rng from './rng.js'; import { unsafeStringify } from './stringify.js'; +import { UUIDTypes, Version7Options } from './types.js'; type V7State = { msecs?: number; // time, milliseconds @@ -62,9 +62,17 @@ export function updateV7State(state: V7State, now: number, rnds: Uint8Array) { } function v7Bytes(rnds: Uint8Array, msecs?: number, seq?: number, buf?: Uint8Array, offset = 0) { + if (rnds.length < 16) { + throw new Error('Random bytes length must be >= 16'); + } + if (!buf) { buf = new Uint8Array(16); offset = 0; + } else { + if (offset < 0 || offset + 16 > buf.length) { + throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); + } } // Defaults