From 223bc86c92748f7590c1bd5a8a6ad46bc9f3e6e4 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 30 Jun 2026 20:20:19 -0700 Subject: [PATCH] fix(ec): reject oversized bitrot payload before narrowing to u32 save_bitrot_sidecar writes payload.len() into the header as a u32; guard against a payload > 1 GiB (which would silently truncate the length field), mirroring Go's SaveBitrotSidecar maxBitrotPayloadSize check. Never triggers for a real sidecar (a few KB). Claude-Session: https://claude.ai/code/session_015EE9Sc9EvNp8BCVva4RKdo --- .../src/storage/erasure_coding/ec_bitrot.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/seaweed-volume/src/storage/erasure_coding/ec_bitrot.rs b/seaweed-volume/src/storage/erasure_coding/ec_bitrot.rs index 06a31167f..a36791c5d 100644 --- a/seaweed-volume/src/storage/erasure_coding/ec_bitrot.rs +++ b/seaweed-volume/src/storage/erasure_coding/ec_bitrot.rs @@ -249,6 +249,17 @@ impl ShardChecksumBuilder { /// CRC32C over the serialized payload (temp file + rename). pub fn save_bitrot_sidecar(path: &str, prot: &EcBitrotProtection) -> io::Result<()> { let payload = prot.encode_to_vec(); + // The header records payload_len as a uint32 and the allocation below adds it + // to a constant. Bound the payload well under any overflow (a real manifest is + // a few KB) so neither the length field nor the buffer can wrap. Mirrors Go's + // SaveBitrotSidecar maxBitrotPayloadSize check. + const MAX_BITROT_PAYLOAD_SIZE: usize = 1 << 30; // 1 GiB, vastly above any real sidecar + if payload.len() > MAX_BITROT_PAYLOAD_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("bitrot sidecar payload too large: {} bytes", payload.len()), + )); + } let mut buf = Vec::with_capacity(BITROT_HEADER_SIZE + payload.len()); buf.extend_from_slice(&BITROT_MAGIC.to_be_bytes()); buf.extend_from_slice(&BITROT_FORMAT_VERSION.to_be_bytes());