From 4a51b2afefcc1373c2a5b834fa0ae8d935dbff46 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Tue, 9 Jul 2024 08:55:02 -0400 Subject: [PATCH] fix(http1): reject final chunked if missing 0 If a chunked body had valid chunks, but ended without a `0` in the final chunk (so, just `\r\n\r\n`), it would be parsed as a valid end. Now it will be rejected as the final chunk MUST be `0\r\n\r\n`. This was partially done before, but only if there were no chunks before the final. This fixes both paths. --- src/proto/h1/decode.rs | 17 ++++++++++++++++- tests/server.rs | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs index 6752d58369..3206863530 100644 --- a/src/proto/h1/decode.rs +++ b/src/proto/h1/decode.rs @@ -431,7 +431,7 @@ impl ChunkedState { rdr: &mut R, ) -> Poll> { match byte!(rdr, cx) { - b'\n' => Poll::Ready(Ok(ChunkedState::Size)), + b'\n' => Poll::Ready(Ok(ChunkedState::Start)), _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk body LF", @@ -666,6 +666,21 @@ mod tests { assert_eq!("1234567890abcdef", &result); } + #[tokio::test] + async fn test_read_chunked_with_missing_zero_digit() { + // After reading a valid chunk, the ending is missing a zero. + let mut mock_buf = &b"1\r\nZ\r\n\r\n\r\n"[..]; + let mut decoder = Decoder::chunked(); + let buf = decoder.decode_fut(&mut mock_buf).await.expect("decode"); + assert_eq!("Z", buf); + + let err = decoder + .decode_fut(&mut mock_buf) + .await + .expect_err("decode 2"); + assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + } + #[tokio::test] async fn test_read_chunked_extensions_over_limit() { // construct a chunked body where each individual chunked extension diff --git a/tests/server.rs b/tests/server.rs index 786bb4b42f..09f0dffb8d 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -585,6 +585,29 @@ fn post_with_incomplete_body() { req.read(&mut [0; 256]).expect("read"); } +#[test] +fn post_with_chunked_missing_final_digit() { + let _ = pretty_env_logger::try_init(); + 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\ + 1\r\n\ + Z\r\n\ + \r\n\r\n\ + ", + ) + .expect("write"); + + server.body_err(); + + req.read(&mut [0; 256]).expect("read"); +} + #[test] fn head_response_can_send_content_length() { let _ = pretty_env_logger::try_init();