fix(tvix/castore): handle Directory::size overflow explicitly

We use checked arithmetic for computing the total size, and verify
that size is in-bounds in Directory::validate.

If an out-of-bounds size makes it to the "unchecked" size method,
we either panic (in debug mode), or silently saturate to u32::MAX.

No new panic sites are added, since overflows in debug mode already
panic at the language level.

Change-Id: I95b8c066a42614fa447f08b4f8fe74e16fbe8bf9
Reviewed-on: https://cl.tvl.fyi/c/depot/+/9616
Reviewed-by: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
This commit is contained in:
edef 2023-10-10 08:55:00 +00:00
parent e2dba089c4
commit baae5ce473
2 changed files with 90 additions and 9 deletions

View file

@ -40,6 +40,8 @@ pub enum ValidateDirectoryError {
/// Invalid digest length encountered
#[error("Invalid Digest length: {0}")]
InvalidDigestLen(usize),
#[error("Total size exceeds u32::MAX")]
SizeOverflow,
}
/// Checks a Node name for validity as an intermediate node, and returns an
@ -130,16 +132,29 @@ fn insert_once<'n>(
Ok(())
}
fn checked_sum(iter: impl IntoIterator<Item = u32>) -> Option<u32> {
iter.into_iter().try_fold(0u32, |acc, i| acc.checked_add(i))
}
impl Directory {
/// The size of a directory is the number of all regular and symlink elements,
/// the number of directory elements, and their size fields.
pub fn size(&self) -> u32 {
self.files.len() as u32
+ self.symlinks.len() as u32
+ self
.directories
.iter()
.fold(0, |acc: u32, e| (acc + 1 + e.size))
if cfg!(debug_assertions) {
self.size_checked()
.expect("Directory::size exceeds u32::MAX")
} else {
self.size_checked().unwrap_or(u32::MAX)
}
}
fn size_checked(&self) -> Option<u32> {
checked_sum([
self.files.len().try_into().ok()?,
self.symlinks.len().try_into().ok()?,
self.directories.len().try_into().ok()?,
checked_sum(self.directories.iter().map(|e| e.size))?,
])
}
/// Calculates the digest of a Directory, which is the blake3 hash of a
@ -201,6 +216,9 @@ impl Directory {
insert_once(&mut seen_names, &symlink_node.name)?;
}
self.size_checked()
.ok_or(ValidateDirectoryError::SizeOverflow)?;
Ok(())
}