Skip to content
Open
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
27 changes: 22 additions & 5 deletions src/codec/framed_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,28 @@ fn decode_frame(
Kind::WindowUpdate => {
let res = frame::WindowUpdate::load(head, &bytes[frame::HEADER_LEN..]);

res.map_err(|e| {
proto_err!(conn: "failed to load WINDOW_UPDATE frame; err={:?}", e);
Error::library_go_away(Reason::PROTOCOL_ERROR)
})?
.into()
match res {
Ok(frame) => frame.into(),
Err(frame::Error::InvalidWindowUpdateValue) => {
// RFC 9113 §6.9.9: "A receiver MUST treat the receipt of a WINDOW_UPDATE
// frame with a flow-control window increment of 0 as a stream error (Section
// 5.4.2) of type PROTOCOL_ERROR; errors on the connection flow-control window
// MUST be treated as a connection error (Section 5.4.1)."
//
// https://www.rfc-editor.org/rfc/rfc9113.html#section-6.9-9
let id = head.stream_id();
if id.is_zero() {
proto_err!(conn: "WINDOW_UPDATE: window_size_increment == 0 on stream 0");
return Err(Error::library_go_away(Reason::PROTOCOL_ERROR));
}
proto_err!(stream: "WINDOW_UPDATE: window_size_increment == 0 on stream {:?}", id);
return Err(Error::library_reset(id, Reason::PROTOCOL_ERROR));
}
Err(e) => {
proto_err!(conn: "failed to load WINDOW_UPDATE frame; err={:?}", e);
return Err(Error::library_go_away(Reason::PROTOCOL_ERROR));
}
}
}
Kind::Data => {
bytes.advance(frame::HEADER_LEN);
Expand Down
89 changes: 89 additions & 0 deletions tests/h2-tests/tests/flow_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,95 @@ fn recv_window_update_causes_overflow() {
// A received window update causes the window to overflow.
}

// Regression test for https://github.com/hyperium/h2/pull/892
#[tokio::test]
async fn recv_window_update_zero_increment_on_stream_is_stream_error() {
h2_support::trace_init!();

let (io, mut srv) = mock::new();

let mock = async move {
let _ = srv.assert_client_handshake().await;
srv.recv_frame(
frames::headers(1)
.request("GET", "https://http2.akamai.com/")
.eos(),
)
.await;
// Hand-craft a WINDOW_UPDATE on stream 1 with a 0 increment
srv.send_bytes(&[
0, 0, 4, // length
8, // type = WINDOW_UPDATE
0, // flags
0, 0, 0, 1, // stream id 1
0, 0, 0, 0, // increment = 0
])
.await;
// The bad frame must trigger a stream-level reset, not a GOAWAY
srv.recv_frame(frames::reset(1).protocol_error()).await;

// The connection must survive: a follow-up request still works
srv.recv_frame(
frames::headers(3)
.request("GET", "https://http2.akamai.com/")
.eos(),
)
.await;
srv.send_frame(frames::headers(3).response(200).eos()).await;
};

let h2 = async move {
let (mut client, mut conn) = client::handshake(io).await.unwrap();

let req1 = Request::builder()
.uri("https://http2.akamai.com/")
.body(())
.unwrap();
let (resp1, _send1) = client.send_request(req1, true).unwrap();
// First request fails with the stream error; connection stays open
let res = conn.drive(resp1).await;
assert!(res.is_err(), "first request should error");

let req2 = Request::builder()
.uri("https://http2.akamai.com/")
.body(())
.unwrap();
let (resp2, _send2) = client.send_request(req2, true).unwrap();
let resp2 = conn.drive(resp2).await.expect("second request");
assert_eq!(resp2.status(), StatusCode::OK);
};
join(mock, h2).await;
}

// Regression test for https://github.com/hyperium/h2/pull/892
#[tokio::test]
async fn recv_window_update_zero_increment_on_connection_is_connection_error() {
h2_support::trace_init!();

let (io, mut srv) = mock::new();

let mock = async move {
let _ = srv.assert_client_handshake().await;
// Hand-craft a WINDOW_UPDATE on stream 0 with a 0 increment
srv.send_bytes(&[
0, 0, 4, // length
8, // type = WINDOW_UPDATE
0, // flags
0, 0, 0, 0, // stream id 0
0, 0, 0, 0, // increment = 0
])
.await;
srv.recv_frame(frames::go_away(0).protocol_error()).await;
};

let h2 = async move {
let (_client, conn) = client::handshake(io).await.unwrap();
let err = conn.await.expect_err("connection should error");
assert_eq!(err.reason(), Some(Reason::PROTOCOL_ERROR));
};
join(mock, h2).await;
}

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