From 601d775da87c42c2ec20dfad035c8dec80733843 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 22 Nov 2025 20:09:42 +0100 Subject: [PATCH] Fix integer underflow in `try_range_response` for empty files (#3566) --- Cargo.lock | 15 ++++---- axum-extra/Cargo.toml | 1 + axum-extra/src/response/file_stream.rs | 52 ++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a47ebd74..63a2279c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,7 @@ dependencies = [ "serde_html_form", "serde_json", "serde_path_to_error", + "tempfile", "tokio", "tokio-stream", "tokio-util", @@ -2947,9 +2948,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "listenfd" @@ -4139,9 +4140,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags 2.6.0", "errno", @@ -4865,12 +4866,12 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ - "cfg-if 1.0.0", "fastrand", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index 9980ead2..88cd4599 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -146,6 +146,7 @@ hyper = "1.0.0" reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "multipart"] } serde = { version = "1.0.221", features = ["derive"] } serde_json = "1.0.71" +tempfile = "3.23.0" tokio = { version = "1.14", features = ["full"] } tower = { version = "0.5.2", features = ["util"] } tower-http = { version = "0.6.0", features = ["map-response-body", "timeout"] } diff --git a/axum-extra/src/response/file_stream.rs b/axum-extra/src/response/file_stream.rs index 9d1f11d5..11b79134 100644 --- a/axum-extra/src/response/file_stream.rs +++ b/axum-extra/src/response/file_stream.rs @@ -191,6 +191,10 @@ where let metadata = file.metadata().await?; let total_size = metadata.len(); + if total_size == 0 { + return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response()); + } + if end == 0 { end = total_size - 1; } @@ -596,4 +600,52 @@ mod tests { } Some((start, end)) } + + #[tokio::test] + async fn response_range_empty_file() -> Result<(), Box> { + let file = tempfile::NamedTempFile::new()?; + file.as_file().set_len(0)?; + let path = file.path().to_owned(); + + let app = Router::new().route( + "/range_empty", + get(move |headers: HeaderMap| { + let path = path.clone(); + async move { + let range_header = headers + .get(header::RANGE) + .and_then(|value| value.to_str().ok()); + + let (start, end) = if let Some(range) = range_header { + if let Some(range) = parse_range_header(range) { + range + } else { + return (StatusCode::RANGE_NOT_SATISFIABLE, "Invalid Range") + .into_response(); + } + } else { + (0, 0) + }; + + FileStream::>::try_range_response(path, start, end) + .await + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()) + } + }), + ); + + let response = app + .oneshot( + Request::builder() + .uri("/range_empty") + .header(header::RANGE, "bytes=0-") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); + Ok(()) + } }