From ce3ba31bcdd01079f3bb802bc8f3626f643c00c6 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 30 Jun 2026 20:43:11 -0700 Subject: [PATCH] fix(ec): reject oversized bitrot payload before narrowing to u32 (#10172) 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. The check uses encoded_len() before serializing, so an oversized manifest never allocates a large buffer. Never triggers for a real sidecar (a few KB). Claude-Session: https://claude.ai/code/session_015EE9Sc9EvNp8BCVva4RKdo --- .../src/storage/erasure_coding/ec_bitrot.rs | 13 +++++++++++++ 1 file changed, 13 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..1d959ce81 100644 --- a/seaweed-volume/src/storage/erasure_coding/ec_bitrot.rs +++ b/seaweed-volume/src/storage/erasure_coding/ec_bitrot.rs @@ -248,6 +248,19 @@ impl ShardChecksumBuilder { /// Atomically writes `prot` to `path`, wrapped in the on-disk header with a /// CRC32C over the serialized payload (temp file + rename). pub fn save_bitrot_sidecar(path: &str, prot: &EcBitrotProtection) -> io::Result<()> { + // The header records payload_len as a uint32 and the buffer 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. + // Check the encoded length BEFORE serializing so an oversized manifest never + // allocates a huge buffer. Mirrors Go's SaveBitrotSidecar maxBitrotPayloadSize. + const MAX_BITROT_PAYLOAD_SIZE: usize = 1 << 30; // 1 GiB, vastly above any real sidecar + let payload_len = prot.encoded_len(); + 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 payload = prot.encode_to_vec(); let mut buf = Vec::with_capacity(BITROT_HEADER_SIZE + payload.len()); buf.extend_from_slice(&BITROT_MAGIC.to_be_bytes());