diff --git a/nodedb-codec/src/bounds.rs b/nodedb-codec/src/bounds.rs index 278d0d525..d6e0a7f53 100644 --- a/nodedb-codec/src/bounds.rs +++ b/nodedb-codec/src/bounds.rs @@ -63,13 +63,27 @@ pub(crate) fn decoded_len(value: usize, codec: &str) -> Result Result { - let bytes = checked_mul(count, element_size, context)?; - decoded_len(bytes, context)?; + let bytes = (count as u64).saturating_mul(element_size as u64); + if bytes > MAX_DECODED_BYTES as u64 { + return Err(CodecError::ResourceLimit { + resource: format!("{context} decoded bytes"), + requested: usize::try_from(bytes).unwrap_or(usize::MAX), + limit: MAX_DECODED_BYTES, + }); + } Ok(count) } @@ -102,8 +116,21 @@ mod tests { } #[test] - fn checked_capacity_rejects_usize_overflow() { - let error = checked_capacity(usize::MAX, 2, "test"); - assert!(matches!(error, Err(CodecError::Corrupt { .. }))); + fn checked_capacity_rejects_oversized_element_count() { + // `usize::MAX` elements of eight bytes each. Multiplying those in + // `usize` overflows, and on a 32-bit target even `u32::MAX` elements + // do; the classification is the resource limit either way, on every + // target the workspace builds for. + let error = checked_capacity(usize::MAX, 8, "test"); + assert!(matches!(error, Err(CodecError::ResourceLimit { .. }))); + } + + #[test] + fn checked_capacity_rejects_wide_element_count_on_every_target() { + // The 32-bit case in the large: `u32::MAX` elements of eight bytes is + // 32 GiB at an element size that cannot be expressed in a 32-bit + // `usize`. + let error = checked_capacity(u32::MAX as usize, 8, "test"); + assert!(matches!(error, Err(CodecError::ResourceLimit { .. }))); } } diff --git a/nodedb-codec/src/delta.rs b/nodedb-codec/src/delta.rs index 86f3381cc..a19b3e43a 100644 --- a/nodedb-codec/src/delta.rs +++ b/nodedb-codec/src/delta.rs @@ -20,9 +20,7 @@ use std::mem::size_of; -use crate::bounds::{ - checked_add, checked_capacity, checked_mul, decoded_len, encode_input_len, u32_to_usize, -}; +use crate::bounds::{checked_add, checked_capacity, checked_mul, encode_input_len, u32_to_usize}; use crate::error::CodecError; // --------------------------------------------------------------------------- @@ -198,8 +196,10 @@ fn encode_value_count(len: usize, codec: &str) -> Result { } fn validate_value_count(count: usize, codec: &str) -> Result<(), CodecError> { - let bytes = checked_mul(count, size_of::(), "integer decoded bytes")?; - decoded_len(bytes, codec)?; + // See `checked_capacity`: the byte count is scaled in 64-bit arithmetic so + // a 32-bit target reports the resource limit rather than a `usize` + // overflow for a count it simply cannot express. + checked_capacity(count, size_of::(), codec)?; Ok(()) } diff --git a/nodedb-codec/src/double_delta.rs b/nodedb-codec/src/double_delta.rs index e42cec162..a507c38b1 100644 --- a/nodedb-codec/src/double_delta.rs +++ b/nodedb-codec/src/double_delta.rs @@ -31,9 +31,7 @@ use std::mem::size_of; -use crate::bounds::{ - checked_add, checked_capacity, checked_mul, decoded_len, encode_input_len, u32_to_usize, -}; +use crate::bounds::{checked_add, checked_capacity, encode_input_len, u32_to_usize}; use crate::error::CodecError; // --------------------------------------------------------------------------- @@ -334,8 +332,10 @@ fn encode_value_count(len: usize, codec: &str) -> Result { } fn validate_value_count(count: usize, codec: &str) -> Result<(), CodecError> { - let bytes = checked_mul(count, size_of::(), "integer decoded bytes")?; - decoded_len(bytes, codec)?; + // See `checked_capacity`: the byte count is scaled in 64-bit arithmetic so + // a 32-bit target reports the resource limit rather than a `usize` + // overflow for a count it simply cannot express. + checked_capacity(count, size_of::(), codec)?; Ok(()) } diff --git a/nodedb-codec/src/fastlanes/header.rs b/nodedb-codec/src/fastlanes/header.rs index bb5079a45..54b0f3303 100644 --- a/nodedb-codec/src/fastlanes/header.rs +++ b/nodedb-codec/src/fastlanes/header.rs @@ -4,7 +4,7 @@ use std::mem::size_of; -use crate::bounds::{checked_mul, checked_range, decoded_len, u32_to_usize}; +use crate::bounds::{checked_capacity, checked_mul, checked_range, u32_to_usize}; use crate::error::CodecError; use super::block::skip_block; @@ -23,8 +23,9 @@ pub(super) fn parse_header(data: &[u8]) -> Result<(usize, usize), CodecError> { u32::from_le_bytes([header[0], header[1], header[2], header[3]]), "FastLanes value count", )?; - let decoded_bytes = checked_mul(total_count, size_of::(), "FastLanes decoded bytes")?; - decoded_len(decoded_bytes, "FastLanes")?; + // Rejects a hostile count before it is used for block math; see + // `checked_capacity` for why the scaling is done in 64-bit arithmetic. + checked_capacity(total_count, size_of::(), "FastLanes")?; let block_count = usize::from(u16::from_le_bytes([header[4], header[5]])); let expected_blocks = total_count.div_ceil(BLOCK_SIZE); if block_count != expected_blocks {