diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs index dad015e092..5d1c1c3c1b 100644 --- a/src/proto/h1/decode.rs +++ b/src/proto/h1/decode.rs @@ -208,19 +208,32 @@ impl ChunkedState { size: &mut u64, ) -> Poll> { trace!("Read chunk hex size"); + + macro_rules! or_overflow { + ($e:expr) => ( + match $e { + Some(val) => val, + None => return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid chunk size: overflow", + ))), + } + ) + } + let radix = 16; match byte!(rdr, cx) { b @ b'0'..=b'9' => { - *size *= radix; - *size += (b - b'0') as u64; + *size = or_overflow!(size.checked_mul(radix)); + *size = or_overflow!(size.checked_add((b - b'0') as u64)); } b @ b'a'..=b'f' => { - *size *= radix; - *size += (b + 10 - b'a') as u64; + *size = or_overflow!(size.checked_mul(radix)); + *size = or_overflow!(size.checked_add((b + 10 - b'a') as u64)); } b @ b'A'..=b'F' => { - *size *= radix; - *size += (b + 10 - b'A') as u64; + *size = or_overflow!(size.checked_mul(radix)); + *size = or_overflow!(size.checked_add((b + 10 - b'A') as u64)); } b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)), b';' => return Poll::Ready(Ok(ChunkedState::Extension)), @@ -449,7 +462,7 @@ mod tests { #[tokio::test] async fn test_read_chunk_size() { - use std::io::ErrorKind::{InvalidInput, UnexpectedEof}; + use std::io::ErrorKind::{InvalidData, InvalidInput, UnexpectedEof}; async fn read(s: &str) -> u64 { let mut state = ChunkedState::Size; @@ -524,6 +537,8 @@ mod tests { read_err("1 invalid extension\r\n", InvalidInput).await; read_err("1 A\r\n", InvalidInput).await; read_err("1;no CRLF", UnexpectedEof).await; + // Overflow + read_err("f0000000000000003\r\n", InvalidData).await; } #[tokio::test] diff --git a/tests/server.rs b/tests/server.rs index 297b09ac73..ce65849f90 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -431,6 +431,35 @@ fn post_with_chunked_body() { assert_eq!(server.body(), b"qwert"); } +#[test] +fn post_with_chunked_overflow() { + let server = serve(); + let mut req = connect(server.addr()); + req.write_all( + b"\ + POST / HTTP/1.1\r\n\ + Host: example.domain\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + f0000000000000003\r\n\ + abc\r\n\ + 0\r\n\ + \r\n\ + GET /sneaky HTTP/1.1\r\n\ + \r\n\ + ", + ) + .unwrap(); + req.read(&mut [0; 256]).unwrap(); + + let err = server.body_err().to_string(); + assert!( + err.contains("overflow"), + "error should be overflow: {:?}", + err + ); +} + #[test] fn post_with_incomplete_body() { let _ = pretty_env_logger::try_init();