Skip to content

Commit 4059727

Browse files
committed
Fix panic if remote causes local to reset a stream before opening
1 parent f52d5e6 commit 4059727

File tree

3 files changed

+85
-0
lines changed

3 files changed

+85
-0
lines changed

src/proto/streams/send.rs

+10
Original file line numberDiff line numberDiff line change
@@ -544,4 +544,14 @@ impl Send {
544544
true
545545
}
546546
}
547+
548+
pub(super) fn maybe_reset_next_stream_id(&mut self, id: StreamId) {
549+
if let Ok(next_id) = self.next_stream_id {
550+
// Peer::is_local_init should have been called beforehand
551+
debug_assert_eq!(id.is_server_initiated(), next_id.is_server_initiated());
552+
if id >= next_id {
553+
self.next_stream_id = id.next_id();
554+
}
555+
}
556+
}
547557
}

src/proto/streams/streams.rs

+18
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,24 @@ impl Inner {
865865
let key = match self.store.find_entry(id) {
866866
Entry::Occupied(e) => e.key(),
867867
Entry::Vacant(e) => {
868+
// Resetting a stream we don't know about? That could be OK...
869+
//
870+
// 1. As a server, we just received a request, but that request
871+
// was bad, so we're resetting before even accepting it.
872+
// This is totally fine.
873+
//
874+
// 2. The remote may have sent us a frame on new stream that
875+
// it's *not* supposed to have done, and thus, we don't know
876+
// the stream. In that case, sending a reset will "open" the
877+
// stream in our store. Maybe that should be a connection
878+
// error instead? At least for now, we need to update what
879+
// our vision of the next stream is.
880+
if self.counts.peer().is_local_init(id) {
881+
// We normally would open this stream, so update our
882+
// next-send-id record.
883+
self.actions.send.maybe_reset_next_stream_id(id);
884+
}
885+
868886
let stream = Stream::new(id, 0, 0);
869887

870888
e.insert(stream)

tests/h2-tests/tests/stream_states.rs

+57
Original file line numberDiff line numberDiff line change
@@ -1022,3 +1022,60 @@ async fn srv_window_update_on_lower_stream_id() {
10221022
};
10231023
join(srv, client).await;
10241024
}
1025+
1026+
// See https://github.com/hyperium/h2/issues/570
1027+
#[tokio::test]
1028+
async fn reset_new_stream_before_send() {
1029+
h2_support::trace_init!();
1030+
let (io, mut srv) = mock::new();
1031+
1032+
let srv = async move {
1033+
let settings = srv.assert_client_handshake().await;
1034+
assert_default_settings!(settings);
1035+
srv.recv_frame(
1036+
frames::headers(1)
1037+
.request("GET", "https://example.com/")
1038+
.eos(),
1039+
)
1040+
.await;
1041+
srv.send_frame(frames::headers(1).response(200).eos()).await;
1042+
// Send unexpected headers, that depends on itself, causing a framing error.
1043+
srv.send_bytes(&[
1044+
0, 0, 0x6, // len
1045+
0x1, // type (headers)
1046+
0x25, // flags (eos, eoh, pri)
1047+
0, 0, 0, 0x3, // stream id
1048+
0, 0, 0, 0x3, // dependency
1049+
2, // weight
1050+
0x88, // HPACK :status=200
1051+
])
1052+
.await;
1053+
srv.recv_frame(frames::reset(3).protocol_error()).await;
1054+
srv.recv_frame(
1055+
frames::headers(5)
1056+
.request("GET", "https://example.com/")
1057+
.eos(),
1058+
)
1059+
.await;
1060+
srv.send_frame(frames::headers(5).response(200).eos()).await;
1061+
};
1062+
1063+
let client = async move {
1064+
let (mut client, mut conn) = client::handshake(io).await.expect("handshake");
1065+
let resp = conn
1066+
.drive(client.get("https://example.com/"))
1067+
.await
1068+
.unwrap();
1069+
assert_eq!(resp.status(), StatusCode::OK);
1070+
1071+
// req number 2
1072+
let resp = conn
1073+
.drive(client.get("https://example.com/"))
1074+
.await
1075+
.unwrap();
1076+
assert_eq!(resp.status(), StatusCode::OK);
1077+
conn.await.expect("client");
1078+
};
1079+
1080+
join(srv, client).await;
1081+
}

0 commit comments

Comments
 (0)