You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Title: HTTP Auth combined with grpc endpoints lead to auth body being copied into grpc header and potential assert() crashes when auth service returns 401
Description:
Combining a HTTP based external auth service with GRPC crashes envoy if the HTTP body returned from the external auth service has \r, \n or \0 in it and envoy decides to return it to the caller. This happens only when the request to the external auth service ends up denying access (401, but probably also on 403s). The root cause is that Utility::sendLocalReply() copies the external auth body text into a GrpMessage header without encoding the content (there is, in 1.10, even a TODO that this needs to be percent encoded). In our case it copies a pretty printed 401 JSON response into the header, and when the code in Filter::onComplete(...) calls HeaderString::setCopy, the latter has an an assert that verifies that the headers do not include \0, \r or \n (code in envoy/http/header_map.h's validHeaderString method).
(Caveat: We are using envoy through ambassador, but we checked with a plain upstream 1.10 release and could reproduce the issue. We also tested that removing the offending code did make it stop crashing)
Before going through this with code references, it is interesting to contemplate whether it makes sense to add the body as an header in the first place? Or is it actually added as a trailer but the code is tricking me?
All line numbers are relative to source corresponding to the v1.10 tag:
A request comes into envoy, which is configured with ext_authz.
this calls out to a configured external auth service, which in this case returns this as body:
Since we are running a http based external auth, and we end up in Filter::onComplete in source/extensions/filters/http/ext_authz/ext_authz.cc, line 157. response->status == CheckStatus::Denied
is true, so we enter the if-clause and callbacks_->sendLocalReply (line 160) is called.This ends up in source/common/http/async_client_impl.h's sendLocalReply which calls Utility::sendLocalReply (source/common/http/utility.cc) with the first value as true since it is a grpc request. This agains ends up in this code:
if (is_grpc) {
HeaderMapPtr response_headers{new HeaderMapImpl{
{Headers::get().Status, std::to_string(enumToInt(Code::OK))},
{Headers::get().ContentType, Headers::get().ContentTypeValues.Grpc},
{Headers::get().GrpcStatus,
std::to_string(
enumToInt(grpc_status ? grpc_status.value()
: Grpc::Utility::httpToGrpcStatus(enumToInt(response_code))))}}};
if (!body_text.empty() && !is_head_request) {
// TODO: GrpcMessage should be percent-encoded
response_headers->insertGrpcMessage().value(body_text);
}
encode_headers(std::move(response_headers), true); // Trailers only response
return;
}
Notice the TODO. Here we copy the body into a HeaderMapImpl (source/common/http/header_map_impl.cc), which then goes through it and is all happy until ti reaches ASSERT(valid()); on line 23, which verifies headers:
// Used by ASSERTs to validate internal consistency. E.g. valid HTTP header keys/values should
// never contain embedded NULLs.
static inline bool validHeaderString(absl::string_view s) {
for (const char c : {'\0', '\r', '\n'}) {
if (s.find(c) != absl::string_view::npos) {
return false;
}
}
return true;
}
which returns false due to the newlines in the body, and then the assert hits and envoy shuts down.
Repro steps:
Setup envoy with an external HTTP based auth service (we do this with ambassador), in our case the generated envoy config for the filter looks like this:
Title: HTTP Auth combined with grpc endpoints lead to auth body being copied into grpc header and potential assert() crashes when auth service returns 401
Description:
Combining a HTTP based external auth service with GRPC crashes envoy if the HTTP body returned from the external auth service has \r, \n or \0 in it and envoy decides to return it to the caller. This happens only when the request to the external auth service ends up denying access (401, but probably also on 403s). The root cause is that Utility::sendLocalReply() copies the external auth body text into a GrpMessage header without encoding the content (there is, in 1.10, even a TODO that this needs to be percent encoded). In our case it copies a pretty printed 401 JSON response into the header, and when the code in Filter::onComplete(...) calls HeaderString::setCopy, the latter has an an assert that verifies that the headers do not include \0, \r or \n (code in envoy/http/header_map.h's validHeaderString method).
(Caveat: We are using envoy through ambassador, but we checked with a plain upstream 1.10 release and could reproduce the issue. We also tested that removing the offending code did make it stop crashing)
Before going through this with code references, it is interesting to contemplate whether it makes sense to add the body as an header in the first place? Or is it actually added as a trailer but the code is tricking me?
All line numbers are relative to source corresponding to the v1.10 tag:
response->status == CheckStatus::Denied
is true, so we enter the if-clause and callbacks_->sendLocalReply (line 160) is called.This ends up in source/common/http/async_client_impl.h's sendLocalReply which calls Utility::sendLocalReply (source/common/http/utility.cc) with the first value as true since it is a grpc request. This agains ends up in this code:
Notice the TODO. Here we copy the body into a HeaderMapImpl (source/common/http/header_map_impl.cc), which then goes through it and is all happy until ti reaches
ASSERT(valid());
on line 23, which verifies headers:which again leads to:
which returns false due to the newlines in the body, and then the assert hits and envoy shuts down.
Repro steps:
Here we use the example hello world service from grpc: https://github.com/grpc/grpc/tree/master/examples/python/helloworld
Logs and call stack:
Possible fixes:
I am not (yet) that familiar with GRPC, but 2) sounds pretty good for me.
The text was updated successfully, but these errors were encountered: