clawhdf5-remote: no overflow on lengths near u64::MAX
A server can claim any length in Content-Range. block_len computed start + block_size, which overflowed in the last blocks of a file claimed to be near u64::MAX (a panic in debug builds, a wrapped value in release); insert() multiplied block indices unchecked. The cache's block arithmetic is now saturating/checked, and a run that does not split into whole blocks is an error instead of an endless loop or a slice panic. The test server gains fake_total (claim a length, serve zeros past the data); a test reads the last bytes of such files and opens a file whose superblock EOF and root addresses sit near u64::MAX. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -48,6 +48,10 @@ pub struct Shared {
|
||||
pub fail_next: AtomicU32,
|
||||
/// Sleep this long before answering each request.
|
||||
pub delay_ms: AtomicU64,
|
||||
/// When non-zero, claim the file is this long (in `Content-Range` and
|
||||
/// when checking ranges) and serve zeros past its real end: a hostile
|
||||
/// server lying about the length.
|
||||
pub fake_total: AtomicU64,
|
||||
/// Requests for a served path (every status); requests for other
|
||||
/// paths are not counted.
|
||||
pub requests: AtomicU64,
|
||||
@@ -262,7 +266,8 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> {
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
let len = data.len() as u64;
|
||||
let fake = s.fake_total.load(Ordering::SeqCst);
|
||||
let len = if fake > 0 { fake } else { data.len() as u64 };
|
||||
let mut validators = String::new();
|
||||
if s.weak_etag.load(Ordering::SeqCst) {
|
||||
validators = format!("ETag: W/{etag}\r\n");
|
||||
@@ -291,6 +296,7 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> {
|
||||
.unwrap()
|
||||
.push((path.clone(), range.and_then(Result::ok)));
|
||||
}
|
||||
let mut padded = Vec::new();
|
||||
let (status, body, extra) = match range {
|
||||
Some(Err(())) => {
|
||||
write!(
|
||||
@@ -302,7 +308,7 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> {
|
||||
}
|
||||
Some(Ok((a, b))) => (
|
||||
"206 Partial Content",
|
||||
&data[a as usize..=b as usize],
|
||||
slice_or_zeros(&data, a, b, &mut padded),
|
||||
format!("Content-Range: bytes {a}-{b}/{len}\r\n"),
|
||||
),
|
||||
None => ("200 OK", &data[..], String::new()),
|
||||
@@ -342,3 +348,18 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `data[a..=b]`, padded with zeros past its end (into `padded`).
|
||||
fn slice_or_zeros<'a>(data: &'a [u8], a: u64, b: u64, padded: &'a mut Vec<u8>) -> &'a [u8] {
|
||||
let real = data.len() as u64;
|
||||
if b < real {
|
||||
return &data[a as usize..=b as usize];
|
||||
}
|
||||
let n = usize::try_from(b - a + 1).expect("range fits in memory");
|
||||
padded.resize(n, 0);
|
||||
if a < real {
|
||||
let have = (real - a) as usize;
|
||||
padded[..have].copy_from_slice(&data[a as usize..]);
|
||||
}
|
||||
padded
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user