Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Header frame with non-zero content-length and END_STREAM is malformed #813

Merged
merged 1 commit into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/frame/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ impl Headers {
&mut self.header_block.pseudo
}

pub(crate) fn pseudo(&self) -> &Pseudo {
&self.header_block.pseudo
}

/// Whether it has status 1xx
pub(crate) fn is_informational(&self) -> bool {
self.header_block.pseudo.is_informational()
Expand Down
12 changes: 12 additions & 0 deletions src/proto/streams/recv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ impl Recv {
};

stream.content_length = ContentLength::Remaining(content_length);
// END_STREAM on headers frame with non-zero content-length is malformed.
// https://datatracker.ietf.org/doc/html/rfc9113#section-8.1.1
if frame.is_end_stream()
&& content_length > 0
&& frame
.pseudo()
.status
.map_or(true, |status| status != 204 && status != 304)
{
proto_err!(stream: "recv_headers with END_STREAM: content-length is not zero; stream={:?};", stream.id);
return Err(Error::library_reset(stream.id, Reason::PROTOCOL_ERROR).into());
}
}
}

Expand Down
43 changes: 43 additions & 0 deletions tests/h2-tests/tests/client_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,49 @@ async fn allow_empty_data_for_head() {
join(srv, h2).await;
}

#[tokio::test]
async fn reject_none_zero_content_length_header_with_end_stream() {
h2_support::trace_init!();
let (io, mut srv) = mock::new();

let srv = async move {
let settings = srv.assert_client_handshake().await;
assert_default_settings!(settings);
srv.recv_frame(
frames::headers(1)
.request("GET", "https://example.com/")
.eos(),
)
.await;
srv.send_frame(
frames::headers(1)
.response(200)
.field("content-length", 100)
.eos(),
)
.await;
};

let h2 = async move {
let (mut client, h2) = client::Builder::new()
.handshake::<_, Bytes>(io)
.await
.unwrap();
tokio::spawn(async {
h2.await.expect("connection failed");
});
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.body(())
.unwrap();
let (response, _) = client.send_request(request, true).unwrap();
let _ = response.await.unwrap_err();
};

join(srv, h2).await;
}

#[tokio::test]
async fn early_hints() {
h2_support::trace_init!();
Expand Down