Adds validation for the `:authority` header field value. The field value must contain only characters from the `host` ABNF in RFC 3986 Section 3.2.2. PiperOrigin-RevId: 423335883
diff --git a/http2/adapter/header_validator.cc b/http2/adapter/header_validator.cc index d5ae7a2..d4c5ee4 100644 --- a/http2/adapter/header_validator.cc +++ b/http2/adapter/header_validator.cc
@@ -21,6 +21,30 @@ const absl::string_view kHttp2StatusValueAllowedChars = "0123456789"; +const absl::string_view kValidAuthorityChars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()[" + "]*+,;=:"; + +// Returns whether `authority` contains only characters from the `host` ABNF +// from RFC 3986 section 3.2.2. +bool IsValidAuthority(absl::string_view authority) { + static const bool* valid_chars = []() { + using ValidCharArray = bool[256]; + bool* chars = new ValidCharArray; + memset(chars, 0, sizeof(ValidCharArray)); + for (char c : kValidAuthorityChars) { + chars[static_cast<uint8_t>(c)] = true; + } + return chars; + }(); + for (char c : authority) { + if (!valid_chars[static_cast<uint8_t>(c)]) { + return false; + } + } + return true; +} + bool ValidateRequestHeaders(const std::vector<std::string>& pseudo_headers, absl::string_view method, bool allow_connect) { QUICHE_VLOG(2) << "Request pseudo-headers: [" @@ -102,6 +126,8 @@ status_ = std::string(value); } else if (key == ":method") { method_ = std::string(value); + } else if (key == ":authority" && !IsValidAuthority(value)) { + return HEADER_FIELD_INVALID; } pseudo_headers_.push_back(std::string(key)); } else if (key == "content-length") {
diff --git a/http2/adapter/header_validator_test.cc b/http2/adapter/header_validator_test.cc index 0d4d7fc..addb35a 100644 --- a/http2/adapter/header_validator_test.cc +++ b/http2/adapter/header_validator_test.cc
@@ -109,6 +109,40 @@ } } +TEST(HeaderValidatorTest, AuthorityHasInvalidChar) { + HeaderValidator v; + v.StartHeaderBlock(); + + // These characters should be allowed. (Not exhaustive.) + for (const absl::string_view c : {"1", "-", "!", ":", "+", "=", ","}) { + HeaderValidator::HeaderStatus status = v.ValidateSingleHeader( + ":authority", absl::StrCat("ho", c, "st.example.com")); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + } + // These should not. + for (const absl::string_view c : {"\r", "\n", "|", "\\", "`"}) { + HeaderValidator::HeaderStatus status = v.ValidateSingleHeader( + ":authority", absl::StrCat("ho", c, "st.example.com")); + EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); + } + + // IPv4 example + HeaderValidator::HeaderStatus status = + v.ValidateSingleHeader(":authority", "123.45.67.89"); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + + // IPv6 examples + status = v.ValidateSingleHeader(":authority", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + status = v.ValidateSingleHeader(":authority", "[::1]:80"); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); + + // Empty field + status = v.ValidateSingleHeader(":authority", ""); + EXPECT_EQ(HeaderValidator::HEADER_OK, status); +} + TEST(HeaderValidatorTest, RequestPseudoHeaders) { HeaderValidator v; const absl::string_view headers[] = {":authority", ":method", ":path",
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc index f2dbd67..9771f3c 100644 --- a/http2/adapter/nghttp2_adapter_test.cc +++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -2928,6 +2928,57 @@ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); } +TEST(NgHttp2AdapterTest, ServerReceivesInvalidAuthority) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "ex|ample.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/false) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL( + visitor, + OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " + "stream: 1, name: [:authority], value: [ex|ample.com]")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0x0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, 4, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); +} + TEST(NgHttp2AdapterTest, ServerSubmitResponse) { DataSavingVisitor visitor; auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc index d785891..47638dd 100644 --- a/http2/adapter/oghttp2_adapter_test.cc +++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -3694,6 +3694,57 @@ spdy::SpdyFrameType::GOAWAY})); } +TEST(OgHttp2AdapterServerTest, ServerReceivesInvalidAuthority) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options{.perspective = Perspective::kServer}; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "ex|ample.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/false) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL( + visitor, + OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(static_cast<int64_t>(frames.size()), result); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0x0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0x0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, 4, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + + int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::SETTINGS, + spdy::SpdyFrameType::RST_STREAM})); +} + TEST(OgHttp2AdapterServerTest, ServerRejectsStreamData) { DataSavingVisitor visitor; OgHttp2Adapter::Options options{.perspective = Perspective::kServer};