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