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
This commit is contained in:
Chris Lu
2026-06-30 20:20:19 -07:00
parent 9bc216b9eb
commit 223bc86c92
@@ -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());