From 994b779bee5688084e410df8b7db583277e66ec0 Mon Sep 17 00:00:00 2001 From: Michal Rzeszutko Date: Wed, 7 Jan 2026 13:14:48 +0000 Subject: [PATCH] Adding lenght validation on deserialization --- .../src/serialize/buffer_reader.test.ts | 101 ++++++++++++++++++ .../foundation/src/serialize/buffer_reader.ts | 30 ++++-- .../p2p/src/services/reqresp/constants.ts | 14 +++ .../protocols/block_txs/bitvector.test.ts | 23 ++++ .../reqresp/protocols/block_txs/bitvector.ts | 9 ++ .../src/services/reqresp/protocols/status.ts | 8 +- yarn-project/stdlib/package.json | 1 + yarn-project/stdlib/src/block/body.ts | 3 +- .../stdlib/src/block/checkpointed_l2_block.ts | 9 +- .../stdlib/src/block/validate_block_result.ts | 9 +- .../stdlib/src/checkpoint/checkpoint.ts | 3 +- .../src/checkpoint/published_checkpoint.ts | 7 +- .../stdlib/src/deserialization/index.ts | 21 ++++ yarn-project/stdlib/src/p2p/block_proposal.ts | 7 +- .../stdlib/src/p2p/checkpoint_proposal.ts | 7 +- yarn-project/stdlib/src/p2p/signed_txs.ts | 7 +- 16 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 yarn-project/p2p/src/services/reqresp/constants.ts create mode 100644 yarn-project/stdlib/src/deserialization/index.ts diff --git a/yarn-project/foundation/src/serialize/buffer_reader.test.ts b/yarn-project/foundation/src/serialize/buffer_reader.test.ts index 324b83ec7445..cb10f1a253ee 100644 --- a/yarn-project/foundation/src/serialize/buffer_reader.test.ts +++ b/yarn-project/foundation/src/serialize/buffer_reader.test.ts @@ -295,4 +295,105 @@ describe('buffer reader', () => { ); }); }); + + describe('maxSize bounds checking', () => { + describe('readVector with maxSize', () => { + it('should read vector when size is within bounds', () => { + const items = [1, 2, 3]; + const buffer = serializeToBuffer(items.length, items); + const reader = new BufferReader(buffer); + + const result = reader.readVector({ fromBuffer: (r: BufferReader) => r.readNumber() }, 10); + + expect(result).toEqual(items); + }); + + it('should throw when vector size exceeds maxSize', () => { + const items = [1, 2, 3, 4, 5]; + const buffer = serializeToBuffer(items.length, items); + const reader = new BufferReader(buffer); + + expect(() => { + reader.readVector({ fromBuffer: (r: BufferReader) => r.readNumber() }, 3); + }).toThrow('Vector size 5 exceeds maximum allowed 3'); + }); + + it('should allow any size when maxSize is not provided', () => { + const items = [1, 2, 3, 4, 5]; + const buffer = serializeToBuffer(items.length, items); + const reader = new BufferReader(buffer); + + const result = reader.readVector({ fromBuffer: (r: BufferReader) => r.readNumber() }); + + expect(result).toEqual(items); + }); + }); + + describe('readBuffer with maxSize', () => { + it('should read buffer when size is within bounds', () => { + const data = Buffer.from('hello'); + // readBuffer expects length prefix + data + const buffer = serializeToBuffer(data.length, data); + const reader = new BufferReader(buffer); + + const result = reader.readBuffer(10); + + expect(result).toEqual(data); + }); + + it('should throw when buffer size exceeds maxSize', () => { + const data = Buffer.from('hello world'); + // readBuffer expects length prefix + data + const buffer = serializeToBuffer(data.length, data); + const reader = new BufferReader(buffer); + + expect(() => { + reader.readBuffer(5); + }).toThrow('Buffer size 11 exceeds maximum allowed 5'); + }); + + it('should allow any size when maxSize is not provided', () => { + const data = Buffer.from('hello world'); + // readBuffer expects length prefix + data + const buffer = serializeToBuffer(data.length, data); + const reader = new BufferReader(buffer); + + const result = reader.readBuffer(); + + expect(result).toEqual(data); + }); + }); + + describe('readString with maxSize', () => { + it('should read string when size is within bounds', () => { + const str = 'hello'; + const buffer = serializeToBuffer(str); + const reader = new BufferReader(buffer); + + const result = reader.readString(10); + + expect(result).toEqual(str); + }); + + it('should throw when string size exceeds maxSize', () => { + const str = 'hello world'; + const buffer = serializeToBuffer(str); + const reader = new BufferReader(buffer); + + expect(() => { + reader.readString(5); + }).toThrow('Buffer size 11 exceeds maximum allowed 5'); + }); + + it('should allow any size when maxSize is not provided', () => { + const str = 'hello world'; + const buffer = serializeToBuffer(str); + const reader = new BufferReader(buffer); + + const result = reader.readString(); + + expect(result).toEqual(str); + }); + }); + }); }); diff --git a/yarn-project/foundation/src/serialize/buffer_reader.ts b/yarn-project/foundation/src/serialize/buffer_reader.ts index 8ed44f6c56fe..8887573effe8 100644 --- a/yarn-project/foundation/src/serialize/buffer_reader.ts +++ b/yarn-project/foundation/src/serialize/buffer_reader.ts @@ -224,15 +224,22 @@ export class BufferReader { * deserializing each one using the 'fromBuffer' method of 'itemDeserializer'. * * @param itemDeserializer - Object with 'fromBuffer' method to deserialize vector elements. + * @param maxSize - Optional maximum allowed size for the vector. If the size exceeds this, an error is thrown. * @returns An array of deserialized elements of type T. */ - public readVector(itemDeserializer: { - /** - * A method to deserialize data from a buffer. - */ - fromBuffer: (reader: BufferReader) => T; - }): T[] { + public readVector( + itemDeserializer: { + /** + * A method to deserialize data from a buffer. + */ + fromBuffer: (reader: BufferReader) => T; + }, + maxSize?: number, + ): T[] { const size = this.readNumber(); + if (maxSize !== undefined && size > maxSize) { + throw new Error(`Vector size ${size} exceeds maximum allowed ${maxSize}`); + } const result = new Array(size); for (let i = 0; i < size; i++) { result[i] = itemDeserializer.fromBuffer(this); @@ -344,10 +351,11 @@ export class BufferReader { * The method first reads the size of the string, then reads the corresponding * number of bytes from the buffer and converts them to a string. * + * @param maxSize - Optional maximum allowed size for the string buffer. If the size exceeds this, an error is thrown. * @returns The read string from the buffer. */ - public readString(): string { - return this.readBuffer().toString(); + public readString(maxSize?: number): string { + return this.readBuffer(maxSize).toString(); } /** @@ -356,10 +364,14 @@ export class BufferReader { * a Buffer with that size containing the bytes. Useful for reading variable-length * binary data encoded as (size, data) format. * + * @param maxSize - Optional maximum allowed size for the buffer. If the size exceeds this, an error is thrown. * @returns A Buffer containing the read bytes. */ - public readBuffer(): Buffer { + public readBuffer(maxSize?: number): Buffer { const size = this.readNumber(); + if (maxSize !== undefined && size > maxSize) { + throw new Error(`Buffer size ${size} exceeds maximum allowed ${maxSize}`); + } this.#rangeCheck(size); return this.readBytes(size); } diff --git a/yarn-project/p2p/src/services/reqresp/constants.ts b/yarn-project/p2p/src/services/reqresp/constants.ts new file mode 100644 index 000000000000..103a5e6c06c7 --- /dev/null +++ b/yarn-project/p2p/src/services/reqresp/constants.ts @@ -0,0 +1,14 @@ +/** + * Constants for P2P message deserialization bounds checking. + * These constants define maximum allowed sizes during deserialization + * to prevent DoS attacks via maliciously crafted messages. + */ + +/** Max transactions per block for deserialization validation (~300x default of 32) */ +export { MAX_TXS_PER_BLOCK } from '@aztec/stdlib/deserialization'; + +/** Max version string length (e.g., "1.0.0-alpha.123") */ +export const MAX_VERSION_STRING_LENGTH = 64; + +/** Max block hash string length (hex: 0x + 64 chars, with generous headroom) */ +export const MAX_BLOCK_HASH_STRING_LENGTH = 128; diff --git a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.test.ts b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.test.ts index 5aba38e0b5fd..ccc099cfc1dc 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.test.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.test.ts @@ -1,3 +1,4 @@ +import { MAX_TXS_PER_BLOCK } from '../../constants.js'; import { BitVector } from './bitvector.js'; describe('BitVector', () => { @@ -82,4 +83,26 @@ describe('BitVector', () => { expect(bitVector.isSet(100)).toBe(false); }); }); + + describe('fromBuffer validation', () => { + it('should throw when length exceeds MAX_TXS_PER_BLOCK', () => { + const length = MAX_TXS_PER_BLOCK + 1; + const buffer = Buffer.alloc(4 + Math.ceil(length / 8)); + buffer.writeUInt32BE(length, 0); + + expect(() => BitVector.fromBuffer(buffer)).toThrow( + `BitVector length ${length} exceeds maximum ${MAX_TXS_PER_BLOCK}`, + ); + }); + + it('should accept length at MAX_TXS_PER_BLOCK boundary', () => { + const length = MAX_TXS_PER_BLOCK; + const byteLength = Math.ceil(length / 8); + const buffer = Buffer.alloc(4 + byteLength); + buffer.writeUInt32BE(length, 0); + + const bitVector = BitVector.fromBuffer(buffer); + expect(bitVector.getLength()).toBe(length); + }); + }); }); diff --git a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.ts b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.ts index b91dc1482fc8..a6a99b72ae0d 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/bitvector.ts @@ -1,5 +1,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { MAX_TXS_PER_BLOCK } from '../../constants.js'; + /** * BitVector helper class for representing and serializing bit vectors */ @@ -80,6 +82,13 @@ export class BitVector { const reader = BufferReader.asReader(buffer); const length = reader.readNumber(); + if (length < 0) { + throw new Error(`BitVector length ${length} cannot be negative`); + } + if (length > MAX_TXS_PER_BLOCK) { + throw new Error(`BitVector length ${length} exceeds maximum ${MAX_TXS_PER_BLOCK}`); + } + const bitBuffer = reader.readBytes(BitVector.byteLength(length)); return new BitVector(bitBuffer, length); } diff --git a/yarn-project/p2p/src/services/reqresp/protocols/status.ts b/yarn-project/p2p/src/services/reqresp/protocols/status.ts index 1fbddd68e4a5..4872373bb48d 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/status.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/status.ts @@ -7,6 +7,8 @@ import type { WorldStateSyncStatus, WorldStateSynchronizer } from '@aztec/stdlib import type { PeerId } from '@libp2p/interface'; +import { MAX_BLOCK_HASH_STRING_LENGTH, MAX_VERSION_STRING_LENGTH } from '../constants.js'; + /* * P2P Status Message * It is used to establish Status handshake between to peers @@ -32,12 +34,12 @@ export class StatusMessage { static fromBuffer(buffer: Buffer | BufferReader): StatusMessage { const reader = BufferReader.asReader(buffer); return new StatusMessage( - reader.readString(), // compressedComponentsVersion + reader.readString(MAX_VERSION_STRING_LENGTH), // compressedComponentsVersion BlockNumber(reader.readNumber()), // latestBlockNumber - reader.readString(), // latestBlockHash + reader.readString(MAX_BLOCK_HASH_STRING_LENGTH), // latestBlockHash BlockNumber(reader.readNumber()), // finalizedBlockNumber //TODO: add finalizedBlockHash - //reader.readString(), // finalizedBlockHash + //reader.readString(MAX_BLOCK_HASH_STRING_LENGTH), // finalizedBlockHash ); } diff --git a/yarn-project/stdlib/package.json b/yarn-project/stdlib/package.json index 0c7c8b5dc242..bf16690f6055 100644 --- a/yarn-project/stdlib/package.json +++ b/yarn-project/stdlib/package.json @@ -7,6 +7,7 @@ "./package.local.json" ], "exports": { + "./deserialization": "./dest/deserialization/index.js", "./aztec-address": "./dest/aztec-address/index.js", "./abi": "./dest/abi/index.js", "./abi/function-selector": "./dest/abi/function_selector.js", diff --git a/yarn-project/stdlib/src/block/body.ts b/yarn-project/stdlib/src/block/body.ts index 6dc7aa2fbf71..73ffa7ba89d3 100644 --- a/yarn-project/stdlib/src/block/body.ts +++ b/yarn-project/stdlib/src/block/body.ts @@ -5,6 +5,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { inspect } from 'util'; import { z } from 'zod'; +import { MAX_TX_EFFECTS_PER_BODY } from '../deserialization/index.js'; import type { ZodFor } from '../schemas/index.js'; import { TxEffect } from '../tx/tx_effect.js'; @@ -40,7 +41,7 @@ export class Body { static fromBuffer(buf: Buffer | BufferReader) { const reader = BufferReader.asReader(buf); - return new this(reader.readVector(TxEffect)); + return new this(reader.readVector(TxEffect, MAX_TX_EFFECTS_PER_BODY)); } /** diff --git a/yarn-project/stdlib/src/block/checkpointed_l2_block.ts b/yarn-project/stdlib/src/block/checkpointed_l2_block.ts index 04ffc4e46fec..5fc308295c6d 100644 --- a/yarn-project/stdlib/src/block/checkpointed_l2_block.ts +++ b/yarn-project/stdlib/src/block/checkpointed_l2_block.ts @@ -6,6 +6,7 @@ import type { FieldsOf } from '@aztec/foundation/types'; import { z } from 'zod'; import { L1PublishedData, PublishedCheckpoint } from '../checkpoint/published_checkpoint.js'; +import { MAX_BLOCK_HASH_STRING_LENGTH, MAX_COMMITTEE_SIZE } from '../deserialization/index.js'; import { L2Block } from './l2_block.js'; import { L2BlockNew } from './l2_block_new.js'; import { CommitteeAttestation } from './proposal/committee_attestation.js'; @@ -36,9 +37,9 @@ export class CheckpointedL2Block { const checkpointNumber = reader.readNumber(); const block = reader.readObject(L2BlockNew); const l1BlockNumber = reader.readBigInt(); - const l1BlockHash = reader.readString(); + const l1BlockHash = reader.readString(MAX_BLOCK_HASH_STRING_LENGTH); const l1Timestamp = reader.readBigInt(); - const attestations = reader.readVector(CommitteeAttestation); + const attestations = reader.readVector(CommitteeAttestation, MAX_COMMITTEE_SIZE); return new CheckpointedL2Block( CheckpointNumber(checkpointNumber), block, @@ -89,9 +90,9 @@ export class PublishedL2Block { const reader = BufferReader.asReader(bufferOrReader); const block = reader.readObject(L2Block); const l1BlockNumber = reader.readBigInt(); - const l1BlockHash = reader.readString(); + const l1BlockHash = reader.readString(MAX_BLOCK_HASH_STRING_LENGTH); const l1Timestamp = reader.readBigInt(); - const attestations = reader.readVector(CommitteeAttestation); + const attestations = reader.readVector(CommitteeAttestation, MAX_COMMITTEE_SIZE); return new PublishedL2Block(block, new L1PublishedData(l1BlockNumber, l1Timestamp, l1BlockHash), attestations); } diff --git a/yarn-project/stdlib/src/block/validate_block_result.ts b/yarn-project/stdlib/src/block/validate_block_result.ts index 4aa88bbe20c0..62b760ebac7a 100644 --- a/yarn-project/stdlib/src/block/validate_block_result.ts +++ b/yarn-project/stdlib/src/block/validate_block_result.ts @@ -11,6 +11,7 @@ import { deserializeCheckpointInfo, serializeCheckpointInfo, } from '../checkpoint/checkpoint_info.js'; +import { MAX_COMMITTEE_SIZE } from '../deserialization/index.js'; import { CommitteeAttestation } from './proposal/committee_attestation.js'; /** Subtype for invalid checkpoint validation results */ @@ -109,13 +110,13 @@ export function deserializeValidateCheckpointResult(bufferOrReader: Buffer | Buf if (valid) { return { valid }; } - const reason = reader.readString() as 'insufficient-attestations' | 'invalid-attestation'; + const reason = reader.readString(64) as 'insufficient-attestations' | 'invalid-attestation'; const checkpoint = deserializeCheckpointInfo(reader.readBuffer()); - const committee = reader.readVector(EthAddress); + const committee = reader.readVector(EthAddress, MAX_COMMITTEE_SIZE); const epoch = EpochNumber(reader.readNumber()); const seed = reader.readBigInt(); - const attestors = reader.readVector(EthAddress); - const attestations = reader.readVector(CommitteeAttestation); + const attestors = reader.readVector(EthAddress, MAX_COMMITTEE_SIZE); + const attestations = reader.readVector(CommitteeAttestation, MAX_COMMITTEE_SIZE); const invalidIndex = reader.readNumber(); if (reason === 'insufficient-attestations') { return { valid, reason, checkpoint, committee, epoch, seed, attestors, attestations }; diff --git a/yarn-project/stdlib/src/checkpoint/checkpoint.ts b/yarn-project/stdlib/src/checkpoint/checkpoint.ts index 9568b3e97c45..84f0970bbb37 100644 --- a/yarn-project/stdlib/src/checkpoint/checkpoint.ts +++ b/yarn-project/stdlib/src/checkpoint/checkpoint.ts @@ -8,6 +8,7 @@ import type { FieldsOf } from '@aztec/foundation/types'; import { z } from 'zod'; import { L2BlockNew } from '../block/l2_block_new.js'; +import { MAX_BLOCKS_PER_CHECKPOINT } from '../deserialization/index.js'; import { CheckpointHeader } from '../rollup/checkpoint_header.js'; import { AppendOnlyTreeSnapshot } from '../trees/append_only_tree_snapshot.js'; import type { CheckpointInfo } from './checkpoint_info.js'; @@ -48,7 +49,7 @@ export class Checkpoint { return new Checkpoint( reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(CheckpointHeader), - reader.readVector(L2BlockNew), + reader.readVector(L2BlockNew, MAX_BLOCKS_PER_CHECKPOINT), CheckpointNumber(reader.readNumber()), ); } diff --git a/yarn-project/stdlib/src/checkpoint/published_checkpoint.ts b/yarn-project/stdlib/src/checkpoint/published_checkpoint.ts index 6e9d0df7827e..d5afc5c2e3e0 100644 --- a/yarn-project/stdlib/src/checkpoint/published_checkpoint.ts +++ b/yarn-project/stdlib/src/checkpoint/published_checkpoint.ts @@ -8,6 +8,7 @@ import type { FieldsOf } from '@aztec/foundation/types'; import { z } from 'zod'; import { CommitteeAttestation } from '../block/proposal/committee_attestation.js'; +import { MAX_BLOCK_HASH_STRING_LENGTH, MAX_COMMITTEE_SIZE } from '../deserialization/index.js'; import { Checkpoint } from './checkpoint.js'; export class L1PublishedData { @@ -42,7 +43,7 @@ export class L1PublishedData { static fromBuffer(bufferOrReader: Buffer | BufferReader): L1PublishedData { const reader = BufferReader.asReader(bufferOrReader); const l1BlockNumber = reader.readBigInt(); - const l1BlockHash = reader.readString(); + const l1BlockHash = reader.readString(MAX_BLOCK_HASH_STRING_LENGTH); const l1Timestamp = reader.readBigInt(); return new L1PublishedData(l1BlockNumber, l1Timestamp, l1BlockHash); } @@ -82,9 +83,9 @@ export class PublishedCheckpoint { const reader = BufferReader.asReader(bufferOrReader); const checkpoint = reader.readObject(Checkpoint); const l1BlockNumber = reader.readBigInt(); - const l1BlockHash = reader.readString(); + const l1BlockHash = reader.readString(MAX_BLOCK_HASH_STRING_LENGTH); const l1Timestamp = reader.readBigInt(); - const attestations = reader.readVector(CommitteeAttestation); + const attestations = reader.readVector(CommitteeAttestation, MAX_COMMITTEE_SIZE); return new PublishedCheckpoint( checkpoint, new L1PublishedData(l1BlockNumber, l1Timestamp, l1BlockHash), diff --git a/yarn-project/stdlib/src/deserialization/index.ts b/yarn-project/stdlib/src/deserialization/index.ts new file mode 100644 index 000000000000..f633bb3a2199 --- /dev/null +++ b/yarn-project/stdlib/src/deserialization/index.ts @@ -0,0 +1,21 @@ +/** + * Constants for deserialization bounds checking. + * These constants define maximum allowed sizes during deserialization + * to prevent DoS attacks via maliciously crafted messages. + */ +import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants'; + +/** Max transactions per block for deserialization validation */ +export const MAX_TXS_PER_BLOCK = 2 ** 16; + +/** Max committee size - theoretical max from bitmap design (256 bytes × 8 bits) */ +export const MAX_COMMITTEE_SIZE = 2048; + +/** Max blocks per checkpoint */ +export const MAX_BLOCKS_PER_CHECKPOINT = 72; + +/** Max tx effects per body (based on blob capacity per checkpoint) */ +export const MAX_TX_EFFECTS_PER_BODY = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB; + +/** Max block hash string length (hex: 0x + 64 chars, with generous headroom) */ +export const MAX_BLOCK_HASH_STRING_LENGTH = 128; diff --git a/yarn-project/stdlib/src/p2p/block_proposal.ts b/yarn-project/stdlib/src/p2p/block_proposal.ts index ddad389c9ade..c9aa9f21fbd2 100644 --- a/yarn-project/stdlib/src/p2p/block_proposal.ts +++ b/yarn-project/stdlib/src/p2p/block_proposal.ts @@ -9,6 +9,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import type { L2Block } from '../block/l2_block.js'; import type { L2BlockInfo } from '../block/l2_block_info.js'; +import { MAX_TXS_PER_BLOCK } from '../deserialization/index.js'; import { BlockHeader } from '../tx/block_header.js'; import { TxHash } from '../tx/index.js'; import type { Tx } from '../tx/tx.js'; @@ -204,7 +205,11 @@ export class BlockProposal extends Gossipable { const inHash = reader.readObject(Fr); const archiveRoot = reader.readObject(Fr); const signature = reader.readObject(Signature); - const txHashes = reader.readArray(reader.readNumber(), TxHash); + const txHashCount = reader.readNumber(); + if (txHashCount > MAX_TXS_PER_BLOCK) { + throw new Error(`txHashes count ${txHashCount} exceeds maximum ${MAX_TXS_PER_BLOCK}`); + } + const txHashes = reader.readArray(txHashCount, TxHash); if (!reader.isEmpty()) { const hasSignedTxs = reader.readNumber(); diff --git a/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts b/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts index d978a1342b29..bf2f1ac80581 100644 --- a/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts +++ b/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts @@ -8,6 +8,7 @@ import { Signature } from '@aztec/foundation/eth-signature'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import type { L2BlockInfo } from '../block/l2_block_info.js'; +import { MAX_TXS_PER_BLOCK } from '../deserialization/index.js'; import { CheckpointHeader } from '../rollup/checkpoint_header.js'; import { BlockHeader } from '../tx/block_header.js'; import { TxHash } from '../tx/index.js'; @@ -248,7 +249,11 @@ export class CheckpointProposal extends Gossipable { const blockHeader = reader.readObject(BlockHeader); const indexWithinCheckpoint = reader.readNumber(); const blockSignature = reader.readObject(Signature); - const txHashes = reader.readArray(reader.readNumber(), TxHash); + const txHashCount = reader.readNumber(); + if (txHashCount > MAX_TXS_PER_BLOCK) { + throw new Error(`txHashes count ${txHashCount} exceeds maximum ${MAX_TXS_PER_BLOCK}`); + } + const txHashes = reader.readArray(txHashCount, TxHash); let signedTxs: SignedTxs | undefined; if (!reader.isEmpty()) { diff --git a/yarn-project/stdlib/src/p2p/signed_txs.ts b/yarn-project/stdlib/src/p2p/signed_txs.ts index 8c873dacd08f..cd88f56aa278 100644 --- a/yarn-project/stdlib/src/p2p/signed_txs.ts +++ b/yarn-project/stdlib/src/p2p/signed_txs.ts @@ -4,6 +4,7 @@ import type { EthAddress } from '@aztec/foundation/eth-address'; import { Signature } from '@aztec/foundation/eth-signature'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { MAX_TXS_PER_BLOCK } from '../deserialization/index.js'; import { Tx } from '../tx/tx.js'; import { SignatureDomainSeparator, @@ -64,7 +65,11 @@ export class SignedTxs { static fromBuffer(buf: Buffer | BufferReader): SignedTxs { const reader = BufferReader.asReader(buf); - const txs = reader.readArray(reader.readNumber(), Tx); + const txCount = reader.readNumber(); + if (txCount > MAX_TXS_PER_BLOCK) { + throw new Error(`txs count ${txCount} exceeds maximum ${MAX_TXS_PER_BLOCK}`); + } + const txs = reader.readArray(txCount, Tx); const signature = reader.readObject(Signature); return new SignedTxs(txs, signature); }