fix(ecstore): reject zero erasure block_size in codec streaming read (#4340)

The codec streaming GET reader divides by erasure.block_size in
build_codec_streaming_part_reader without validating the erasure
dimensions, unlike the legacy multipart path which already rejects
block_size==0 / data_shards==0. FileInfo::is_valid() does not check
block_size, so corrupted on-disk metadata (block_size==0, data_blocks>0)
passes validation and panics the read task with a divide-by-zero.

Add Erasure::has_valid_dimensions() and reject invalid dimensions at the
codec streaming entry before any disk access, mirroring the legacy guard
(which now reuses the same predicate).

Refs backlog#868 (868-1).
This commit is contained in:
Zhengchao An
2026-07-07 07:17:58 +08:00
committed by GitHub
parent 8b2c67a100
commit b813fc7739
2 changed files with 67 additions and 3 deletions
@@ -787,6 +787,17 @@ impl Erasure {
pub fn total_shard_count(&self) -> usize {
self.data_shards + self.parity_shards
}
/// Whether the erasure dimensions are safe for the shard/offset arithmetic.
///
/// `block_size` and `data_shards` come straight from on-disk metadata; a
/// corrupted or crafted `xl.meta` can carry zero values that would panic the
/// `block_size`/`data_shards` divisions in [`Self::shard_size`],
/// [`Self::shard_file_size`] and [`Self::shard_file_offset`]. Read paths must
/// reject such metadata before performing those divisions.
pub fn has_valid_dimensions(&self) -> bool {
self.block_size > 0 && self.data_shards > 0
}
// /// Calculate the shard size and total size for a given data size.
// // Returns (shard_size, total_size) for the given data size
// fn need_size(&self, data_size: usize) -> (usize, usize) {
@@ -962,6 +973,21 @@ mod tests {
assert_eq!(owned, borrowed);
}
#[test]
fn has_valid_dimensions_rejects_zero_block_size_or_data_shards() {
// Well-formed erasure metadata is accepted.
assert!(Erasure::new(4, 2, 64).has_valid_dimensions());
assert!(Erasure::new_with_options(6, 4, 1, true).has_valid_dimensions());
// Corrupted on-disk metadata with a zero block_size or zero data_shards
// must be rejected before it reaches the shard/offset divisions.
// (parity_shards is kept 0 for the zero-data_shards cases so the
// Reed-Solomon encoder is not constructed with an invalid shard count.)
assert!(!Erasure::new(4, 2, 0).has_valid_dimensions());
assert!(!Erasure::new(0, 0, 64).has_valid_dimensions());
assert!(!Erasure::new(0, 0, 0).has_valid_dimensions());
}
#[test]
fn encode_data_owned_matches_borrowed_path() {
for uses_legacy in [false, true] {