Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion yarn-project/p2p/src/services/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import { webcrypto } from 'node:crypto';
import { compressSync, uncompressSync } from 'snappy';
import xxhashFactory from 'xxhash-wasm';

/** Thrown when a Snappy-compressed response exceeds the allowed decompressed size. */
export class OversizedSnappyResponseError extends Error {
constructor(decompressedSize: number, maxSizeKb: number) {
super(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
this.name = 'OversizedSnappyResponseError';
}
}

// Load WASM
const xxhash = await xxhashFactory();

Expand Down Expand Up @@ -86,7 +94,7 @@ export class SnappyTransform implements DataTransform {
const { decompressedSize } = readSnappyPreamble(data);
if (decompressedSize > maxSizeKb * 1024) {
this.logger.warn(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
throw new Error(`Decompressed size ${decompressedSize} exceeds maximum allowed size of ${maxSizeKb}kb`);
throw new OversizedSnappyResponseError(decompressedSize, maxSizeKb);
}

return Buffer.from(uncompressSync(data, { asBuffer: true }));
Expand Down
28 changes: 18 additions & 10 deletions yarn-project/p2p/src/services/reqresp/reqresp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
IndividualReqRespTimeoutError,
InvalidResponseError,
} from '../../errors/reqresp.error.js';
import { SnappyTransform } from '../encoding.js';
import { OversizedSnappyResponseError, SnappyTransform } from '../encoding.js';
import type { PeerScoring } from '../peer-manager/peer_scoring.js';
import {
DEFAULT_INDIVIDUAL_REQUEST_TIMEOUT_MS,
Expand Down Expand Up @@ -553,16 +553,10 @@ export class ReqResp implements ReqRespInterface {
data: message,
};
} catch (e: any) {
// All errors (invalid status bytes, oversized snappy responses, corrupt data, etc.)
// are re-thrown so the caller can penalize the peer via handleResponseError.
this.logger.debug(`Reading message failed: ${e.message}`);

let status = ReqRespStatus.UNKNOWN;
if (e instanceof ReqRespStatusError) {
status = e.status;
}

return {
status,
};
throw e;
}
}

Expand Down Expand Up @@ -780,6 +774,20 @@ export class ReqResp implements ReqRespInterface {
return undefined;
}

// Invalid status byte: the peer sent a status byte that doesn't match any known status code.
// This is a protocol violation, penalize harshly.
if (e instanceof ReqRespStatusError) {
this.logger.warn(`Invalid status byte from peer ${peerId.toString()} in ${subProtocol}: ${e.message}`, logTags);
return PeerErrorSeverity.LowToleranceError;
}

// Oversized snappy response: the peer is sending data that exceeds the allowed size.
// This is a protocol violation that wastes bandwidth, so penalize harshly.
if (e instanceof OversizedSnappyResponseError) {
this.logger.warn(`Oversized response from peer ${peerId.toString()} in ${subProtocol}: ${e.message}`, logTags);
return PeerErrorSeverity.LowToleranceError;
}

return this.categorizeConnectionErrors(e, peerId, subProtocol);
}

Expand Down
Loading