Skip to content

Commit

Permalink
fix: Test for invalid byte array sizes and ranges in v1(), v4(), …
Browse files Browse the repository at this point in the history
…and `v7()` (#845)

---------

Co-authored-by: Robert Kieffer <[email protected]>
  • Loading branch information
gaoyia and broofa authored Jan 5, 2025
1 parent 6e83b3a commit e0ee900
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 5 deletions.
24 changes: 24 additions & 0 deletions src/test/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
});
24 changes: 24 additions & 0 deletions src/test/v4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
});
26 changes: 25 additions & 1 deletion src/test/v7.test.ts
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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));
});
});
11 changes: 10 additions & 1 deletion src/v1.ts
Original file line number Diff line number Diff line change
@@ -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**
//
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 8 additions & 2 deletions src/v4.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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];
Expand Down
10 changes: 9 additions & 1 deletion src/v7.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit e0ee900

Please sign in to comment.