summary
HeaderValidator::sanitizeHeadersWithUnderscores() stores header names as non-owning absl::string_view references and then removes headers from the header map while iterating. if the request contains duplicate header names with underscores, later iterations construct a LowerCaseString from a dangling view, producing a use-after-free read.
callsite: source/extensions/http/header_validators/envoy_default/header_validator.cc:631-652
prerequisites
- UHV enabled in build/runtime (
envoy.reloadable_features.enable_universal_header_validator)
headers_with_underscores_action: DROP_HEADER configured in HTTP protocol options
root cause
the function collects header names with underscores into std::vector<absl::string_view>. it then iterates and calls header_map.remove(LowerCaseString(name)) for each. when two entries refer to the same backing storage (duplicate header names), the first removal frees the storage and the second iteration reads from a dangling view.
reproduction
send a request with duplicate header names containing underscores (e.g. x_foo twice) through an envoy instance with UHV enabled and DROP_HEADER configured. under ASAN the heap-use-after-free read is deterministic.
suggested fix
change the collected names from absl::string_view to std::string (owning copies) so that removals don't invalidate remaining references:
- std::vector<absl::string_view> drop_headers;
+ std::vector<std::string> drop_headers;
header_map.iterate([&drop_headers](const ::Envoy::Http::HeaderEntry& header_entry)
-> ::Envoy::Http::HeaderMap::Iterate {
const absl::string_view header_name = header_entry.key().getStringView();
if (absl::StrContains(header_name, '_')) {
- drop_headers.push_back(header_name);
+ drop_headers.emplace_back(header_name);
}
return ::Envoy::Http::HeaderMap::Iterate::Continue;
});
alternatively, deduplicating names before iterating and mutating the map would also prevent the dangling reference.
happy to open a PR with the fix + a regression test if that's preferred.
summary
HeaderValidator::sanitizeHeadersWithUnderscores()stores header names as non-owningabsl::string_viewreferences and then removes headers from the header map while iterating. if the request contains duplicate header names with underscores, later iterations construct aLowerCaseStringfrom a dangling view, producing a use-after-free read.callsite:
source/extensions/http/header_validators/envoy_default/header_validator.cc:631-652prerequisites
envoy.reloadable_features.enable_universal_header_validator)headers_with_underscores_action: DROP_HEADERconfigured in HTTP protocol optionsroot cause
the function collects header names with underscores into
std::vector<absl::string_view>. it then iterates and callsheader_map.remove(LowerCaseString(name))for each. when two entries refer to the same backing storage (duplicate header names), the first removal frees the storage and the second iteration reads from a dangling view.reproduction
send a request with duplicate header names containing underscores (e.g.
x_footwice) through an envoy instance with UHV enabled andDROP_HEADERconfigured. under ASAN the heap-use-after-free read is deterministic.suggested fix
change the collected names from
absl::string_viewtostd::string(owning copies) so that removals don't invalidate remaining references:alternatively, deduplicating names before iterating and mutating the map would also prevent the dangling reference.
happy to open a PR with the fix + a regression test if that's preferred.