From 8e5de1bb57e10b5bd9e70ab22489da787517238a 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 | 22 +++++++++++++++++++++- tests/server.rs | 23 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs index 275d6a9b8a..2e196c36a5 100644 --- a/src/proto/h1/decode.rs +++ b/src/proto/h1/decode.rs @@ -517,7 +517,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", @@ -902,6 +902,26 @@ 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(None, None); + let buf = decoder + .decode_fut(&mut mock_buf) + .await + .expect("decode") + .into_data() + .expect("unknown frame type"); + 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 82bc80669c..5120ad776f 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -572,6 +572,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();