CVE-2024-30253
Description
@solana/web3.js is the Solana JavaScript SDK. Using particular inputs with @solana/web3.js will result in memory exhaustion (OOM). If you have a server, client, mobile, or desktop product that accepts untrusted input for use with @solana/web3.js, your application/service may crash, resulting in a loss of availability. This vulnerability is fixed in 1.0.1, 1.10.2, 1.11.1, 1.12.1, 1.1.2, 1.13.1, 1.14.1, 1.15.1, 1.16.2, 1.17.1, 1.18.1, 1.19.1, 1.20.3, 1.21.1, 1.22.1, 1.23.1, 1.24.3, 1.25.1, 1.26.1, 1.27.1, 1.28.1, 1.2.8, 1.29.4, 1.30.3, 1.31.1, 1.3.1, 1.32.3, 1.33.1, 1.34.1, 1.35.2, 1.36.1, 1.37.3, 1.38.1, 1.39.2, 1.40.2, 1.41.11, 1.4.1, 1.42.1, 1.43.7, 1.44.4, 1.45.1, 1.46.1, 1.47.5, 1.48.1, 1.49.1, 1.50.2, 1.51.1, 1.5.1, 1.52.1, 1.53.1, 1.54.2, 1.55.1, 1.56.3, 1.57.1, 1.58.1, 1.59.2, 1.60.1, 1.61.2, 1.6.1, 1.62.2, 1.63.2, 1.64.1, 1.65.1, 1.66.6, 1.67.3, 1.68.2, 1.69.1, 1.70.4, 1.71.1, 1.72.1, 1.7.2, 1.73.5, 1.74.1, 1.75.1, 1.76.1, 1.77.4, 1.78.8, 1.79.1, 1.80.1, 1.81.1, 1.8.1, 1.82.1, 1.83.1, 1.84.1, 1.85.1, 1.86.1, 1.87.7, 1.88.1, 1.89.2, 1.90.2, 1.9.2, and 1.91.3.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@solana/web3.jsnpm | >= 1.91.0, < 1.91.3 | 1.91.3 |
@solana/web3.jsnpm | >= 1.90, < 1.90.2 | 1.90.2 |
@solana/web3.jsnpm | >= 1.89, < 1.89.2 | 1.89.2 |
@solana/web3.jsnpm | >= 1.88.0, < 1.88.1 | 1.88.1 |
@solana/web3.jsnpm | >= 1.87.0, < 1.87.7 | 1.87.7 |
@solana/web3.jsnpm | >= 1.86.0, < 1.86.1 | 1.86.1 |
@solana/web3.jsnpm | >= 1.85.0, < 1.85.1 | 1.85.1 |
@solana/web3.jsnpm | >= 1.84.0, < 1.84.1 | 1.84.1 |
@solana/web3.jsnpm | >= 1.83.0, < 1.83.1 | 1.83.1 |
@solana/web3.jsnpm | >= 1.82.0, < 1.82.1 | 1.82.1 |
@solana/web3.jsnpm | >= 1.81.0, < 1.81.1 | 1.81.1 |
@solana/web3.jsnpm | >= 1.80.0, < 1.80.1 | 1.80.1 |
@solana/web3.jsnpm | >= 1.79.0, < 1.79.1 | 1.79.1 |
@solana/web3.jsnpm | >= 1.78, < 1.78.8 | 1.78.8 |
@solana/web3.jsnpm | >= 1.77, < 1.77.4 | 1.77.4 |
@solana/web3.jsnpm | >= 1.76.0, < 1.76.1 | 1.76.1 |
@solana/web3.jsnpm | >= 1.75.0, < 1.75.1 | 1.75.1 |
@solana/web3.jsnpm | >= 1.74.0, < 1.74.1 | 1.74.1 |
@solana/web3.jsnpm | >= 1.73.0, < 1.73.5 | 1.73.5 |
@solana/web3.jsnpm | >= 1.72.0, < 1.72.1 | 1.72.1 |
@solana/web3.jsnpm | >= 1.71.0, < 1.71.1 | 1.71.1 |
@solana/web3.jsnpm | >= 1.70.0, < 1.70.4 | 1.70.4 |
@solana/web3.jsnpm | >= 1.69.0, < 1.69.1 | 1.69.1 |
@solana/web3.jsnpm | >= 1.68.0, < 1.68.2 | 1.68.2 |
@solana/web3.jsnpm | >= 1.67.0, < 1.67.3 | 1.67.3 |
@solana/web3.jsnpm | >= 1.66.0, < 1.66.6 | 1.66.6 |
@solana/web3.jsnpm | >= 1.65.0, < 1.65.1 | 1.65.1 |
@solana/web3.jsnpm | >= 1.64.0, < 1.64.1 | 1.64.1 |
@solana/web3.jsnpm | >= 1.63.0, < 1.63.2 | 1.63.2 |
@solana/web3.jsnpm | >= 1.62.0, < 1.62.2 | 1.62.2 |
@solana/web3.jsnpm | >= 1.61.0, < 1.61.2 | 1.61.2 |
@solana/web3.jsnpm | >= 1.60.0, < 1.60.1 | 1.60.1 |
@solana/web3.jsnpm | >= 1.59.0, < 1.59.2 | 1.59.2 |
@solana/web3.jsnpm | >= 1.58.0, < 1.58.1 | 1.58.1 |
@solana/web3.jsnpm | >= 1.57.0, < 1.57.1 | 1.57.1 |
@solana/web3.jsnpm | >= 1.56.0, < 1.56.3 | 1.56.3 |
@solana/web3.jsnpm | >= 1.55.0, < 1.55.1 | 1.55.1 |
@solana/web3.jsnpm | >= 1.54.0, < 1.54.2 | 1.54.2 |
@solana/web3.jsnpm | >= 1.53.0, < 1.53.1 | 1.53.1 |
@solana/web3.jsnpm | >= 1.52.0, < 1.52.1 | 1.52.1 |
@solana/web3.jsnpm | >= 1.51.0, < 1.51.1 | 1.51.1 |
@solana/web3.jsnpm | >= 1.50.0, < 1.50.2 | 1.50.2 |
@solana/web3.jsnpm | >= 1.49.0, < 1.49.1 | 1.49.1 |
@solana/web3.jsnpm | >= 1.48.0, < 1.48.1 | 1.48.1 |
@solana/web3.jsnpm | >= 1.47.0, < 1.47.5 | 1.47.5 |
@solana/web3.jsnpm | >= 1.46.0, < 1.46.1 | 1.46.1 |
@solana/web3.jsnpm | >= 1.45.0, < 1.45.1 | 1.45.1 |
@solana/web3.jsnpm | >= 1.44.0, < 1.44.4 | 1.44.4 |
@solana/web3.jsnpm | >= 1.43.0, < 1.43.7 | 1.43.7 |
@solana/web3.jsnpm | >= 1.42.0, < 1.42.1 | 1.42.1 |
@solana/web3.jsnpm | >= 1.41.0, < 1.41.11 | 1.41.11 |
@solana/web3.jsnpm | >= 1.40.0, < 1.40.2 | 1.40.2 |
@solana/web3.jsnpm | >= 1.39.0, < 1.39.2 | 1.39.2 |
@solana/web3.jsnpm | >= 1.38.0, < 1.38.1 | 1.38.1 |
@solana/web3.jsnpm | >= 1.37.0, < 1.37.3 | 1.37.3 |
@solana/web3.jsnpm | >= 1.36.0, < 1.36.1 | 1.36.1 |
@solana/web3.jsnpm | >= 1.35.0, < 1.35.2 | 1.35.2 |
@solana/web3.jsnpm | >= 1.34.0, < 1.34.1 | 1.34.1 |
@solana/web3.jsnpm | >= 1.33.0, < 1.33.1 | 1.33.1 |
@solana/web3.jsnpm | >= 1.32.0, < 1.32.2 | 1.32.2 |
@solana/web3.jsnpm | >= 1.31.0, < 1.31.1 | 1.31.1 |
@solana/web3.jsnpm | >= 1.30.0, < 1.30.3 | 1.30.3 |
@solana/web3.jsnpm | >= 1.29.0, < 1.29.4 | 1.29.4 |
@solana/web3.jsnpm | >= 1.28.0, < 1.28.1 | 1.28.1 |
@solana/web3.jsnpm | >= 1.27.0, < 1.27.1 | 1.27.1 |
@solana/web3.jsnpm | >= 1.26.0, < 1.26.1 | 1.26.1 |
@solana/web3.jsnpm | >= 1.25.0, < 1.25.1 | 1.25.1 |
@solana/web3.jsnpm | >= 1.24.0, < 1.24.3 | 1.24.3 |
@solana/web3.jsnpm | >= 1.23.0, < 1.23.1 | 1.23.1 |
@solana/web3.jsnpm | >= 1.22.0, < 1.22.1 | 1.22.1 |
@solana/web3.jsnpm | >= 1.21.0, < 1.21.1 | 1.21.1 |
@solana/web3.jsnpm | >= 1.20.0, < 1.20.3 | 1.20.3 |
@solana/web3.jsnpm | >= 1.19.0, < 1.19.1 | 1.19.1 |
@solana/web3.jsnpm | >= 1.18.0, < 1.18.1 | 1.18.1 |
@solana/web3.jsnpm | >= 1.17.0, < 1.17.1 | 1.17.1 |
@solana/web3.jsnpm | >= 1.16.0, < 1.16.2 | 1.16.2 |
@solana/web3.jsnpm | >= 1.15.0, < 1.15.1 | 1.15.1 |
@solana/web3.jsnpm | >= 1.14.0, < 1.14.1 | 1.14.1 |
@solana/web3.jsnpm | >= 1.13.0, < 1.13.1 | 1.13.1 |
@solana/web3.jsnpm | >= 1.12.0, < 1.12.1 | 1.12.1 |
@solana/web3.jsnpm | >= 1.11.0, < 1.11.1 | 1.11.1 |
@solana/web3.jsnpm | >= 1.10.0, < 1.10.2 | 1.10.2 |
@solana/web3.jsnpm | >= 1.9.0, < 1.9.2 | 1.9.2 |
@solana/web3.jsnpm | >= 1.8.0, < 1.8.1 | 1.8.1 |
@solana/web3.jsnpm | >= 1.7.0, < 1.7.2 | 1.7.2 |
@solana/web3.jsnpm | >= 1.6.0, < 1.6.1 | 1.6.1 |
@solana/web3.jsnpm | >= 1.5.0, < 1.5.1 | 1.5.1 |
@solana/web3.jsnpm | >= 1.4.0, < 1.4.1 | 1.4.1 |
@solana/web3.jsnpm | >= 1.3.0, < 1.3.1 | 1.3.1 |
@solana/web3.jsnpm | >= 1.2.0, < 1.2.8 | 1.2.8 |
@solana/web3.jsnpm | >= 1.1.0, < 1.1.2 | 1.1.2 |
@solana/web3.jsnpm | < 1.0.1 | 1.0.1 |
Patches
377d935221a48fix: bounds check
7 files changed · +133 −24
packages/library-legacy/src/message/legacy.ts+9 −8 modified@@ -16,6 +16,7 @@ import { import {TransactionInstruction} from '../transaction'; import {CompiledKeys} from './compiled-keys'; import {MessageAccountKeys} from './account-keys'; +import {guardedShift, guardedSplice} from '../utils/guarded-array-utils'; /** * An instruction to execute by a program @@ -268,7 +269,7 @@ export class Message { // Slice up wire data let byteArray = [...buffer]; - const numRequiredSignatures = byteArray.shift()!; + const numRequiredSignatures = guardedShift(byteArray); if ( numRequiredSignatures !== (numRequiredSignatures & VERSION_PREFIX_MASK) @@ -278,26 +279,26 @@ export class Message { ); } - const numReadonlySignedAccounts = byteArray.shift()!; - const numReadonlyUnsignedAccounts = byteArray.shift()!; + const numReadonlySignedAccounts = guardedShift(byteArray); + const numReadonlyUnsignedAccounts = guardedShift(byteArray); const accountCount = shortvec.decodeLength(byteArray); let accountKeys = []; for (let i = 0; i < accountCount; i++) { - const account = byteArray.splice(0, PUBLIC_KEY_LENGTH); + const account = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH); accountKeys.push(new PublicKey(Buffer.from(account))); } - const recentBlockhash = byteArray.splice(0, PUBLIC_KEY_LENGTH); + const recentBlockhash = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH); const instructionCount = shortvec.decodeLength(byteArray); let instructions: CompiledInstruction[] = []; for (let i = 0; i < instructionCount; i++) { - const programIdIndex = byteArray.shift()!; + const programIdIndex = guardedShift(byteArray); const accountCount = shortvec.decodeLength(byteArray); - const accounts = byteArray.splice(0, accountCount); + const accounts = guardedSplice(byteArray, 0, accountCount); const dataLength = shortvec.decodeLength(byteArray); - const dataSlice = byteArray.splice(0, dataLength); + const dataSlice = guardedSplice(byteArray, 0, dataLength); const data = bs58.encode(Buffer.from(dataSlice)); instructions.push({ programIdIndex,
packages/library-legacy/src/message/v0.ts+29 −12 modified@@ -16,6 +16,7 @@ import {TransactionInstruction} from '../transaction'; import {AddressLookupTableAccount} from '../programs'; import {CompiledKeys} from './compiled-keys'; import {AccountKeysFromLookups, MessageAccountKeys} from './account-keys'; +import {guardedShift, guardedSplice} from '../utils/guarded-array-utils'; /** * Message constructor arguments @@ -426,7 +427,7 @@ export class MessageV0 { static deserialize(serializedMessage: Uint8Array): MessageV0 { let byteArray = [...serializedMessage]; - const prefix = byteArray.shift() as number; + const prefix = guardedShift(byteArray); const maskedPrefix = prefix & VERSION_PREFIX_MASK; assert( prefix !== maskedPrefix, @@ -440,29 +441,35 @@ export class MessageV0 { ); const header: MessageHeader = { - numRequiredSignatures: byteArray.shift() as number, - numReadonlySignedAccounts: byteArray.shift() as number, - numReadonlyUnsignedAccounts: byteArray.shift() as number, + numRequiredSignatures: guardedShift(byteArray), + numReadonlySignedAccounts: guardedShift(byteArray), + numReadonlyUnsignedAccounts: guardedShift(byteArray), }; const staticAccountKeys = []; const staticAccountKeysLength = shortvec.decodeLength(byteArray); for (let i = 0; i < staticAccountKeysLength; i++) { staticAccountKeys.push( - new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)), + new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)), ); } - const recentBlockhash = bs58.encode(byteArray.splice(0, PUBLIC_KEY_LENGTH)); + const recentBlockhash = bs58.encode( + guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH), + ); const instructionCount = shortvec.decodeLength(byteArray); const compiledInstructions: MessageCompiledInstruction[] = []; for (let i = 0; i < instructionCount; i++) { - const programIdIndex = byteArray.shift() as number; + const programIdIndex = guardedShift(byteArray); const accountKeyIndexesLength = shortvec.decodeLength(byteArray); - const accountKeyIndexes = byteArray.splice(0, accountKeyIndexesLength); + const accountKeyIndexes = guardedSplice( + byteArray, + 0, + accountKeyIndexesLength, + ); const dataLength = shortvec.decodeLength(byteArray); - const data = new Uint8Array(byteArray.splice(0, dataLength)); + const data = new Uint8Array(guardedSplice(byteArray, 0, dataLength)); compiledInstructions.push({ programIdIndex, accountKeyIndexes, @@ -473,11 +480,21 @@ export class MessageV0 { const addressTableLookupsCount = shortvec.decodeLength(byteArray); const addressTableLookups: MessageAddressTableLookup[] = []; for (let i = 0; i < addressTableLookupsCount; i++) { - const accountKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)); + const accountKey = new PublicKey( + guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH), + ); const writableIndexesLength = shortvec.decodeLength(byteArray); - const writableIndexes = byteArray.splice(0, writableIndexesLength); + const writableIndexes = guardedSplice( + byteArray, + 0, + writableIndexesLength, + ); const readonlyIndexesLength = shortvec.decodeLength(byteArray); - const readonlyIndexes = byteArray.splice(0, readonlyIndexesLength); + const readonlyIndexes = guardedSplice( + byteArray, + 0, + readonlyIndexesLength, + ); addressTableLookups.push({ accountKey, writableIndexes,
packages/library-legacy/src/transaction/legacy.ts+2 −1 modified@@ -12,6 +12,7 @@ import type {Signer} from '../keypair'; import type {Blockhash} from '../blockhash'; import type {CompiledInstruction} from '../message'; import {sign, verify} from '../utils/ed25519'; +import {guardedSplice} from '../utils/guarded-array-utils'; /** @internal */ type MessageSignednessErrors = { @@ -904,7 +905,7 @@ export class Transaction { const signatureCount = shortvec.decodeLength(byteArray); let signatures = []; for (let i = 0; i < signatureCount; i++) { - const signature = byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES); + const signature = guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES); signatures.push(bs58.encode(Buffer.from(signature))); }
packages/library-legacy/src/transaction/versioned.ts+2 −1 modified@@ -8,6 +8,7 @@ import * as shortvec from '../utils/shortvec-encoding'; import * as Layout from '../layout'; import {sign} from '../utils/ed25519'; import {PublicKey} from '../publickey'; +import {guardedSplice} from '../utils/guarded-array-utils'; export type TransactionVersion = 'legacy' | 0; @@ -82,7 +83,7 @@ export class VersionedTransaction { const signaturesLength = shortvec.decodeLength(byteArray); for (let i = 0; i < signaturesLength; i++) { signatures.push( - new Uint8Array(byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES)), + new Uint8Array(guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES)), ); }
packages/library-legacy/src/utils/guarded-array-utils.ts+34 −0 added@@ -0,0 +1,34 @@ +const END_OF_BUFFER_ERROR_MESSAGE = 'Reached end of buffer unexpectedly'; + +/** + * Delegates to `Array#shift`, but throws if the array is zero-length. + */ +export function guardedShift<T>(byteArray: T[]): T { + if (byteArray.length === 0) { + throw new Error(END_OF_BUFFER_ERROR_MESSAGE); + } + return byteArray.shift() as T; +} + +/** + * Delegates to `Array#splice`, but throws if the section being spliced out extends past the end of + * the array. + */ +export function guardedSplice<T>( + byteArray: T[], + ...args: + | [start: number, deleteCount?: number] + | [start: number, deleteCount: number, ...items: T[]] +): T[] { + const [start] = args; + if ( + args.length === 2 // Implies that `deleteCount` was supplied + ? start + (args[1] ?? 0) > byteArray.length + : start >= byteArray.length + ) { + throw new Error(END_OF_BUFFER_ERROR_MESSAGE); + } + return byteArray.splice( + ...(args as Parameters<typeof Array.prototype.splice>), + ); +}
packages/library-legacy/src/validator-info.ts+5 −2 modified@@ -9,6 +9,7 @@ import { import * as Layout from './layout'; import * as shortvec from './utils/shortvec-encoding'; import {PublicKey, PUBLIC_KEY_LENGTH} from './publickey'; +import {guardedShift, guardedSplice} from './utils/guarded-array-utils'; export const VALIDATOR_INFO_KEY = new PublicKey( 'Va1idator1nfo111111111111111111111111111111', @@ -83,8 +84,10 @@ export class ValidatorInfo { const configKeys: Array<ConfigKey> = []; for (let i = 0; i < 2; i++) { - const publicKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)); - const isSigner = byteArray.splice(0, 1)[0] === 1; + const publicKey = new PublicKey( + guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH), + ); + const isSigner = guardedShift(byteArray) === 1; configKeys.push({publicKey, isSigner}); }
packages/library-legacy/test/guarded-array-utils.test.ts+52 −0 added@@ -0,0 +1,52 @@ +import {expect} from 'chai'; +import {spy} from 'sinon'; + +import {guardedShift, guardedSplice} from '../src/utils/guarded-array-utils'; + +describe('guardedShift', () => { + it('delegates to Array#shift', () => { + const arr = [1, 2, 3]; + const shiftSpy = spy(arr, 'shift'); + const result = guardedShift(arr); + expect(shiftSpy).is.calledWithExactly(); + expect(result).to.eq(shiftSpy.returnValues[0]); + }); + it('throws when the array is zero-length', () => { + const arr: number[] = []; + expect(() => guardedShift(arr)).to.throw(); + }); +}); + +describe('guardedSplice', () => { + it('delegates to Array#splice', () => { + const arr = [1, 2, 3]; + const spliceSpy = spy(arr, 'splice'); + const result = guardedSplice( + arr, + /* start */ 0, + /* deleteCount */ 3, + /* ...items */ + 100, + 101, + 102, + ); + expect(spliceSpy).is.calledWithExactly(0, 3, 100, 101, 102); + expect(result).to.eq(spliceSpy.returnValues[0]); + }); + it('allows zero-length splices', () => { + const arr: number[] = [1, 2, 3]; + expect(guardedSplice(arr, 0, 0)).to.be.an.empty('array'); + }); + it('allows zero-length splices via the `deleteCount` argument being the explicit value `undefined`', () => { + const arr: number[] = [1, 2, 3]; + expect(guardedSplice(arr, 0, undefined)).to.be.an.empty('array'); + }); + it('throws when the `start` would take you past the end of the array', () => { + const arr: number[] = [1, 2, 3]; + expect(() => guardedSplice(arr, 3)).to.throw(); + }); + it('throws when the `deleteCount` and `start` would take you past the end of the array', () => { + const arr: number[] = [1, 2, 3]; + expect(() => guardedSplice(arr, 1, 3)).to.throw(); + }); +});
77d935221a4821e29f044f53Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.