Reject forbidden frame types and incomplete frames in ALPS. https://vasilvv.github.io/httpbis-alps/draft-vvv-httpbis-alps.html#name-use-of-alps-in-http-3 PiperOrigin-RevId: 359360218 Change-Id: Ice2650cb934ba776f38ab9465c5d4d3e3294fdb6
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h index 65e9f2e..fe955c3 100644 --- a/quic/core/http/http_decoder.h +++ b/quic/core/http/http_decoder.h
@@ -150,6 +150,9 @@ const std::string& error_detail() const { return error_detail_; } + // Returns true if input data processed so far ends on a frame boundary. + bool AtFrameBoundary() const { return state_ == STATE_READING_FRAME_TYPE; } + private: friend test::HttpDecoderPeer;
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index 94e2bfc..91530d3 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -98,51 +98,74 @@ // HttpDecoder::Visitor implementation. void OnError(HttpDecoder* /*decoder*/) override {} bool OnCancelPushFrame(const CancelPushFrame& /*frame*/) override { - return true; + error_detail_ = "CANCEL_PUSH frame forbidden"; + return false; } bool OnMaxPushIdFrame(const MaxPushIdFrame& /*frame*/) override { - return true; + error_detail_ = "MAX_PUSH_ID frame forbidden"; + return false; } - bool OnGoAwayFrame(const GoAwayFrame& /*frame*/) override { return true; } + bool OnGoAwayFrame(const GoAwayFrame& /*frame*/) override { + error_detail_ = "GOAWAY frame forbidden"; + return false; + } bool OnSettingsFrameStart(QuicByteCount /*header_length*/) override { return true; } bool OnSettingsFrame(const SettingsFrame& /*frame*/) override { return true; } bool OnDataFrameStart(QuicByteCount /*header_length*/, QuicByteCount /*payload_length*/) override { - return true; + error_detail_ = "DATA frame forbidden"; + return false; } bool OnDataFramePayload(absl::string_view /*payload*/) override { - return true; + QUICHE_NOTREACHED(); + return false; } - bool OnDataFrameEnd() override { return true; } + bool OnDataFrameEnd() override { + QUICHE_NOTREACHED(); + return false; + } bool OnHeadersFrameStart(QuicByteCount /*header_length*/, QuicByteCount /*payload_length*/) override { - return true; + error_detail_ = "HEADERS frame forbidden"; + return false; } bool OnHeadersFramePayload(absl::string_view /*payload*/) override { - return true; + QUICHE_NOTREACHED(); + return false; } - bool OnHeadersFrameEnd() override { return true; } + bool OnHeadersFrameEnd() override { + QUICHE_NOTREACHED(); + return false; + } bool OnPushPromiseFrameStart(QuicByteCount /*header_length*/) override { - return true; + error_detail_ = "PUSH_PROMISE frame forbidden"; + return false; } bool OnPushPromiseFramePushId( PushId /*push_id*/, QuicByteCount /*push_id_length*/, QuicByteCount /*header_block_length*/) override { - return true; + QUICHE_NOTREACHED(); + return false; } bool OnPushPromiseFramePayload(absl::string_view /*payload*/) override { - return true; + QUICHE_NOTREACHED(); + return false; } - bool OnPushPromiseFrameEnd() override { return true; } + bool OnPushPromiseFrameEnd() override { + QUICHE_NOTREACHED(); + return false; + } bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override { - return true; + error_detail_ = "PRIORITY_UPDATE frame forbidden"; + return false; } bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override { - return true; + QUICHE_NOTREACHED(); + return false; } bool OnAcceptChFrameStart(QuicByteCount /*header_length*/) override { return true; @@ -162,8 +185,13 @@ } bool OnUnknownFrameEnd() override { return true; } + const absl::optional<std::string>& error_detail() const { + return error_detail_; + } + private: QuicSpdySession* const session_; + absl::optional<std::string> error_detail_; }; } // namespace @@ -1053,10 +1081,18 @@ AlpsFrameDecoder alps_frame_decoder(this); HttpDecoder decoder(&alps_frame_decoder); decoder.ProcessInput(reinterpret_cast<const char*>(alps_data), alps_length); + if (alps_frame_decoder.error_detail()) { + return alps_frame_decoder.error_detail(); + } + if (decoder.error() != QUIC_NO_ERROR) { return decoder.error_detail(); } + if (!decoder.AtFrameBoundary()) { + return "incomplete HTTP/3 frame"; + } + return absl::nullopt; }
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index db263b0..c2ab731 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -3314,11 +3314,14 @@ session_.OnStreamFrame(data3); } -TEST_P(QuicSpdySessionTestClient, OnAlpsData) { +TEST_P(QuicSpdySessionTestClient, AcceptChViaAlps) { + if (!VersionUsesHttp3(transport_version())) { + return; + } + StrictMock<MockHttp3DebugVisitor> debug_visitor; session_.set_debug_visitor(&debug_visitor); - AcceptChFrame accept_ch_frame{{{"foo", "bar"}}}; std::string serialized_accept_ch_frame = absl::HexStringToBytes( "4089" // type (ACCEPT_CH) "08" // length @@ -3328,12 +3331,48 @@ "626172"); // value "bar" if (GetQuicReloadableFlag(quic_parse_accept_ch_frame)) { - EXPECT_CALL(debug_visitor, OnAcceptChFrameReceivedViaAlps(accept_ch_frame)); + AcceptChFrame expected_accept_ch_frame{{{"foo", "bar"}}}; + EXPECT_CALL(debug_visitor, + OnAcceptChFrameReceivedViaAlps(expected_accept_ch_frame)); } - session_.OnAlpsData( + auto error = session_.OnAlpsData( reinterpret_cast<const uint8_t*>(serialized_accept_ch_frame.data()), serialized_accept_ch_frame.size()); + EXPECT_FALSE(error); +} + +TEST_P(QuicSpdySessionTestClient, AlpsForbiddenFrame) { + if (!VersionUsesHttp3(transport_version())) { + return; + } + + std::string forbidden_frame = absl::HexStringToBytes( + "00" // type (DATA) + "03" // length + "66666f"); // "foo" + + auto error = session_.OnAlpsData( + reinterpret_cast<const uint8_t*>(forbidden_frame.data()), + forbidden_frame.size()); + ASSERT_TRUE(error); + EXPECT_EQ("DATA frame forbidden", error.value()); +} + +TEST_P(QuicSpdySessionTestClient, AlpsIncompleteFrame) { + if (!VersionUsesHttp3(transport_version())) { + return; + } + + std::string incomplete_frame = absl::HexStringToBytes( + "04" // type (SETTINGS) + "03"); // non-zero length but empty payload + + auto error = session_.OnAlpsData( + reinterpret_cast<const uint8_t*>(incomplete_frame.data()), + incomplete_frame.size()); + ASSERT_TRUE(error); + EXPECT_EQ("incomplete HTTP/3 frame", error.value()); } } // namespace