Skip to content

Commit

Permalink
uhv: fully validate IPv6 Host address (#35931)
Browse files Browse the repository at this point in the history
Validate each part of ipv6 address splitted by
`:` is 16 bit. No more than 8 parts. Only one double colon is allowed.
Risk Level: Low
Testing: Unit testing
Docs Changes:
Release Notes:
Platform Specific Features:
Fixes #23314 #22859

---------

Signed-off-by: Yuanguo Lang <[email protected]>
  • Loading branch information
Yuanguo-notebook authored Sep 6, 2024
1 parent 19c1872 commit 69f1b69
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,6 @@ HeaderValidator::validateHostHeader(const HeaderString& value) {
//
// Host = uri-host [ ":" port ]
// uri-host = IP-literal / IPv4address / reg-name
//
// TODO(#22859, #23314) - Fully implement IPv6 address validation
const auto host = value.getStringView();
if (host.empty()) {
return {HeaderValueValidationResult::Action::Reject, UhvResponseCodeDetail::get().InvalidHost};
Expand Down Expand Up @@ -351,12 +349,46 @@ HeaderValidator::validateHostHeaderIPv6(absl::string_view host) {
// Get the trailing port substring
const auto port_string = host.substr(closing_bracket + 1);
// Validate the IPv6 address characters
bool is_valid = !address.empty();
for (auto iter = address.begin(); iter != address.end() && is_valid; ++iter) {
is_valid &= testCharInTable(kHostIPv6AddressCharTable, *iter);
if (address.empty()) {
return HostHeaderValidationResult::reject(UhvResponseCodeDetail::get().InvalidHost);
}
if (address == "::") {
return HostHeaderValidationResult::success(address, port_string);
}
// Split address by (:) and validate:
// 1. there are no more than 8 parts
// 2. each part has only hex digit and is 16-bit
// 3. only one double colon is allowed
absl::InlinedVector<absl::string_view, 8> address_components = absl::StrSplit(address, ':');
if (address_components.size() > 8) {
return HostHeaderValidationResult::reject(UhvResponseCodeDetail::get().InvalidHost);
}
uint32_t empty_string_count = 0;
for (absl::string_view cur_component : address_components) {
// each part must be 16 bits
if (cur_component.size() > 4) {
return HostHeaderValidationResult::reject(UhvResponseCodeDetail::get().InvalidHost);
}
if (cur_component.empty()) {
empty_string_count++;
continue;
}
// Validate each char is hex digit
for (char c : cur_component) {
if (!testCharInTable(kHostIPv6AddressCharTable, c)) {
return HostHeaderValidationResult::reject(UhvResponseCodeDetail::get().InvalidHost);
}
}
}
// The address should never have more than 2 empty parts, except "::"
if (empty_string_count >= 3) {
return HostHeaderValidationResult::reject(UhvResponseCodeDetail::get().InvalidHost);
}

if (!is_valid) {
// Double colon is allowed at the beginning or end
// Otherwise the address shouldn't have two empty parts
if (empty_string_count == 2 &&
!(absl::StartsWith(address, "::") || absl::EndsWith(address, "::"))) {
return HostHeaderValidationResult::reject(UhvResponseCodeDetail::get().InvalidHost);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,33 @@ TEST_F(BaseHeaderValidatorTest, ValidateHostHeaderInvalidRegName) {
TEST_F(BaseHeaderValidatorTest, ValidateHostHeaderValidIPv6) {
HeaderString valid{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:443"};
HeaderString valid_no_port{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"};
HeaderString valid_double_colon_all_0{"[::]"};
HeaderString valid_double_colon{"[2001::7334]"};
HeaderString valid_double_colon_at_beginning{"[::2001:7334]"};
HeaderString valid_double_colon_at_end{"[2001:7334::]"};
auto uhv = createBase(empty_config);

EXPECT_ACCEPT(uhv->validateHostHeader(valid));
EXPECT_ACCEPT(uhv->validateHostHeader(valid_no_port));
EXPECT_ACCEPT(uhv->validateHostHeader(valid_double_colon_all_0));
EXPECT_ACCEPT(uhv->validateHostHeader(valid_double_colon));
EXPECT_ACCEPT(uhv->validateHostHeader(valid_double_colon_at_beginning));
EXPECT_ACCEPT(uhv->validateHostHeader(valid_double_colon_at_end));
}

TEST_F(BaseHeaderValidatorTest, ValidateHostHeaderInvalidIPv6) {
HeaderString invalid_missing_closing_bracket{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334"};
HeaderString invalid_chars{"[200z:0db8:85a3:0000:0000:8a2e:0370:7334]"};
HeaderString invalid_no_brackets{"200z:0db8:85a3:0000:0000:8a2e:0370:7334"};
HeaderString invalid_more_than_8_parts{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334:1]:443"};
HeaderString invalid_not_16_bits{"[1:1:20012:1:1:1:1:1]:443"};
HeaderString invalid_2_double_colons{"[2::1::1]:443"};
HeaderString invalid_2_double_colons_at_beginning{"[::1::1]:443"};
HeaderString invalid_2_double_colons_at_end{"[1::1::]:443"};
HeaderString invalid_2_double_colons_at_beginning_and_end{"[::1:1::]:443"};
HeaderString invalid_3_colons{"[:::]:443"};
HeaderString invalid_single_colon_at_end{"[1::1:]:443"};
HeaderString invalid_single_colon_at_beginning{"[:1::1:2]:443"};
auto uhv = createBase(empty_config);

EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_missing_closing_bracket),
Expand All @@ -207,6 +224,24 @@ TEST_F(BaseHeaderValidatorTest, ValidateHostHeaderInvalidIPv6) {
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_no_brackets),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_more_than_8_parts),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_not_16_bits),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_2_double_colons),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_2_double_colons_at_beginning),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_2_double_colons_at_end),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_2_double_colons_at_beginning_and_end),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_3_colons),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_single_colon_at_end),
UhvResponseCodeDetail::get().InvalidHost);
EXPECT_REJECT_WITH_DETAILS(uhv->validateHostHeader(invalid_single_colon_at_beginning),
UhvResponseCodeDetail::get().InvalidHost);
}

TEST_F(BaseHeaderValidatorTest, ValidateHostHeaderInvalidEmpty) {
Expand Down

0 comments on commit 69f1b69

Please sign in to comment.