Skip to content

Commit 77d9352

Browse files
committed
fix: bounds check
1 parent 5b21c65 commit 77d9352

File tree

7 files changed

+133
-24
lines changed

7 files changed

+133
-24
lines changed

packages/library-legacy/src/message/legacy.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import {TransactionInstruction} from '../transaction';
1717
import {CompiledKeys} from './compiled-keys';
1818
import {MessageAccountKeys} from './account-keys';
19+
import {guardedShift, guardedSplice} from '../utils/guarded-array-utils';
1920

2021
/**
2122
* An instruction to execute by a program
@@ -268,7 +269,7 @@ export class Message {
268269
// Slice up wire data
269270
let byteArray = [...buffer];
270271

271-
const numRequiredSignatures = byteArray.shift()!;
272+
const numRequiredSignatures = guardedShift(byteArray);
272273
if (
273274
numRequiredSignatures !==
274275
(numRequiredSignatures & VERSION_PREFIX_MASK)
@@ -278,26 +279,26 @@ export class Message {
278279
);
279280
}
280281

281-
const numReadonlySignedAccounts = byteArray.shift()!;
282-
const numReadonlyUnsignedAccounts = byteArray.shift()!;
282+
const numReadonlySignedAccounts = guardedShift(byteArray);
283+
const numReadonlyUnsignedAccounts = guardedShift(byteArray);
283284

284285
const accountCount = shortvec.decodeLength(byteArray);
285286
let accountKeys = [];
286287
for (let i = 0; i < accountCount; i++) {
287-
const account = byteArray.splice(0, PUBLIC_KEY_LENGTH);
288+
const account = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH);
288289
accountKeys.push(new PublicKey(Buffer.from(account)));
289290
}
290291

291-
const recentBlockhash = byteArray.splice(0, PUBLIC_KEY_LENGTH);
292+
const recentBlockhash = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH);
292293

293294
const instructionCount = shortvec.decodeLength(byteArray);
294295
let instructions: CompiledInstruction[] = [];
295296
for (let i = 0; i < instructionCount; i++) {
296-
const programIdIndex = byteArray.shift()!;
297+
const programIdIndex = guardedShift(byteArray);
297298
const accountCount = shortvec.decodeLength(byteArray);
298-
const accounts = byteArray.splice(0, accountCount);
299+
const accounts = guardedSplice(byteArray, 0, accountCount);
299300
const dataLength = shortvec.decodeLength(byteArray);
300-
const dataSlice = byteArray.splice(0, dataLength);
301+
const dataSlice = guardedSplice(byteArray, 0, dataLength);
301302
const data = bs58.encode(Buffer.from(dataSlice));
302303
instructions.push({
303304
programIdIndex,

packages/library-legacy/src/message/v0.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {TransactionInstruction} from '../transaction';
1616
import {AddressLookupTableAccount} from '../programs';
1717
import {CompiledKeys} from './compiled-keys';
1818
import {AccountKeysFromLookups, MessageAccountKeys} from './account-keys';
19+
import {guardedShift, guardedSplice} from '../utils/guarded-array-utils';
1920

2021
/**
2122
* Message constructor arguments
@@ -426,7 +427,7 @@ export class MessageV0 {
426427
static deserialize(serializedMessage: Uint8Array): MessageV0 {
427428
let byteArray = [...serializedMessage];
428429

429-
const prefix = byteArray.shift() as number;
430+
const prefix = guardedShift(byteArray);
430431
const maskedPrefix = prefix & VERSION_PREFIX_MASK;
431432
assert(
432433
prefix !== maskedPrefix,
@@ -440,29 +441,35 @@ export class MessageV0 {
440441
);
441442

442443
const header: MessageHeader = {
443-
numRequiredSignatures: byteArray.shift() as number,
444-
numReadonlySignedAccounts: byteArray.shift() as number,
445-
numReadonlyUnsignedAccounts: byteArray.shift() as number,
444+
numRequiredSignatures: guardedShift(byteArray),
445+
numReadonlySignedAccounts: guardedShift(byteArray),
446+
numReadonlyUnsignedAccounts: guardedShift(byteArray),
446447
};
447448

448449
const staticAccountKeys = [];
449450
const staticAccountKeysLength = shortvec.decodeLength(byteArray);
450451
for (let i = 0; i < staticAccountKeysLength; i++) {
451452
staticAccountKeys.push(
452-
new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)),
453+
new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)),
453454
);
454455
}
455456

456-
const recentBlockhash = bs58.encode(byteArray.splice(0, PUBLIC_KEY_LENGTH));
457+
const recentBlockhash = bs58.encode(
458+
guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
459+
);
457460

458461
const instructionCount = shortvec.decodeLength(byteArray);
459462
const compiledInstructions: MessageCompiledInstruction[] = [];
460463
for (let i = 0; i < instructionCount; i++) {
461-
const programIdIndex = byteArray.shift() as number;
464+
const programIdIndex = guardedShift(byteArray);
462465
const accountKeyIndexesLength = shortvec.decodeLength(byteArray);
463-
const accountKeyIndexes = byteArray.splice(0, accountKeyIndexesLength);
466+
const accountKeyIndexes = guardedSplice(
467+
byteArray,
468+
0,
469+
accountKeyIndexesLength,
470+
);
464471
const dataLength = shortvec.decodeLength(byteArray);
465-
const data = new Uint8Array(byteArray.splice(0, dataLength));
472+
const data = new Uint8Array(guardedSplice(byteArray, 0, dataLength));
466473
compiledInstructions.push({
467474
programIdIndex,
468475
accountKeyIndexes,
@@ -473,11 +480,21 @@ export class MessageV0 {
473480
const addressTableLookupsCount = shortvec.decodeLength(byteArray);
474481
const addressTableLookups: MessageAddressTableLookup[] = [];
475482
for (let i = 0; i < addressTableLookupsCount; i++) {
476-
const accountKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH));
483+
const accountKey = new PublicKey(
484+
guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
485+
);
477486
const writableIndexesLength = shortvec.decodeLength(byteArray);
478-
const writableIndexes = byteArray.splice(0, writableIndexesLength);
487+
const writableIndexes = guardedSplice(
488+
byteArray,
489+
0,
490+
writableIndexesLength,
491+
);
479492
const readonlyIndexesLength = shortvec.decodeLength(byteArray);
480-
const readonlyIndexes = byteArray.splice(0, readonlyIndexesLength);
493+
const readonlyIndexes = guardedSplice(
494+
byteArray,
495+
0,
496+
readonlyIndexesLength,
497+
);
481498
addressTableLookups.push({
482499
accountKey,
483500
writableIndexes,

packages/library-legacy/src/transaction/legacy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {Signer} from '../keypair';
1212
import type {Blockhash} from '../blockhash';
1313
import type {CompiledInstruction} from '../message';
1414
import {sign, verify} from '../utils/ed25519';
15+
import {guardedSplice} from '../utils/guarded-array-utils';
1516

1617
/** @internal */
1718
type MessageSignednessErrors = {
@@ -904,7 +905,7 @@ export class Transaction {
904905
const signatureCount = shortvec.decodeLength(byteArray);
905906
let signatures = [];
906907
for (let i = 0; i < signatureCount; i++) {
907-
const signature = byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES);
908+
const signature = guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES);
908909
signatures.push(bs58.encode(Buffer.from(signature)));
909910
}
910911

packages/library-legacy/src/transaction/versioned.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as shortvec from '../utils/shortvec-encoding';
88
import * as Layout from '../layout';
99
import {sign} from '../utils/ed25519';
1010
import {PublicKey} from '../publickey';
11+
import {guardedSplice} from '../utils/guarded-array-utils';
1112

1213
export type TransactionVersion = 'legacy' | 0;
1314

@@ -82,7 +83,7 @@ export class VersionedTransaction {
8283
const signaturesLength = shortvec.decodeLength(byteArray);
8384
for (let i = 0; i < signaturesLength; i++) {
8485
signatures.push(
85-
new Uint8Array(byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES)),
86+
new Uint8Array(guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES)),
8687
);
8788
}
8889

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const END_OF_BUFFER_ERROR_MESSAGE = 'Reached end of buffer unexpectedly';
2+
3+
/**
4+
* Delegates to `Array#shift`, but throws if the array is zero-length.
5+
*/
6+
export function guardedShift<T>(byteArray: T[]): T {
7+
if (byteArray.length === 0) {
8+
throw new Error(END_OF_BUFFER_ERROR_MESSAGE);
9+
}
10+
return byteArray.shift() as T;
11+
}
12+
13+
/**
14+
* Delegates to `Array#splice`, but throws if the section being spliced out extends past the end of
15+
* the array.
16+
*/
17+
export function guardedSplice<T>(
18+
byteArray: T[],
19+
...args:
20+
| [start: number, deleteCount?: number]
21+
| [start: number, deleteCount: number, ...items: T[]]
22+
): T[] {
23+
const [start] = args;
24+
if (
25+
args.length === 2 // Implies that `deleteCount` was supplied
26+
? start + (args[1] ?? 0) > byteArray.length
27+
: start >= byteArray.length
28+
) {
29+
throw new Error(END_OF_BUFFER_ERROR_MESSAGE);
30+
}
31+
return byteArray.splice(
32+
...(args as Parameters<typeof Array.prototype.splice>),
33+
);
34+
}

packages/library-legacy/src/validator-info.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import * as Layout from './layout';
1010
import * as shortvec from './utils/shortvec-encoding';
1111
import {PublicKey, PUBLIC_KEY_LENGTH} from './publickey';
12+
import {guardedShift, guardedSplice} from './utils/guarded-array-utils';
1213

1314
export const VALIDATOR_INFO_KEY = new PublicKey(
1415
'Va1idator1nfo111111111111111111111111111111',
@@ -83,8 +84,10 @@ export class ValidatorInfo {
8384

8485
const configKeys: Array<ConfigKey> = [];
8586
for (let i = 0; i < 2; i++) {
86-
const publicKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH));
87-
const isSigner = byteArray.splice(0, 1)[0] === 1;
87+
const publicKey = new PublicKey(
88+
guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH),
89+
);
90+
const isSigner = guardedShift(byteArray) === 1;
8891
configKeys.push({publicKey, isSigner});
8992
}
9093

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {expect} from 'chai';
2+
import {spy} from 'sinon';
3+
4+
import {guardedShift, guardedSplice} from '../src/utils/guarded-array-utils';
5+
6+
describe('guardedShift', () => {
7+
it('delegates to Array#shift', () => {
8+
const arr = [1, 2, 3];
9+
const shiftSpy = spy(arr, 'shift');
10+
const result = guardedShift(arr);
11+
expect(shiftSpy).is.calledWithExactly();
12+
expect(result).to.eq(shiftSpy.returnValues[0]);
13+
});
14+
it('throws when the array is zero-length', () => {
15+
const arr: number[] = [];
16+
expect(() => guardedShift(arr)).to.throw();
17+
});
18+
});
19+
20+
describe('guardedSplice', () => {
21+
it('delegates to Array#splice', () => {
22+
const arr = [1, 2, 3];
23+
const spliceSpy = spy(arr, 'splice');
24+
const result = guardedSplice(
25+
arr,
26+
/* start */ 0,
27+
/* deleteCount */ 3,
28+
/* ...items */
29+
100,
30+
101,
31+
102,
32+
);
33+
expect(spliceSpy).is.calledWithExactly(0, 3, 100, 101, 102);
34+
expect(result).to.eq(spliceSpy.returnValues[0]);
35+
});
36+
it('allows zero-length splices', () => {
37+
const arr: number[] = [1, 2, 3];
38+
expect(guardedSplice(arr, 0, 0)).to.be.an.empty('array');
39+
});
40+
it('allows zero-length splices via the `deleteCount` argument being the explicit value `undefined`', () => {
41+
const arr: number[] = [1, 2, 3];
42+
expect(guardedSplice(arr, 0, undefined)).to.be.an.empty('array');
43+
});
44+
it('throws when the `start` would take you past the end of the array', () => {
45+
const arr: number[] = [1, 2, 3];
46+
expect(() => guardedSplice(arr, 3)).to.throw();
47+
});
48+
it('throws when the `deleteCount` and `start` would take you past the end of the array', () => {
49+
const arr: number[] = [1, 2, 3];
50+
expect(() => guardedSplice(arr, 1, 3)).to.throw();
51+
});
52+
});

0 commit comments

Comments
 (0)