Skip to content

Commit de8c505

Browse files
authored
config: fix crash when xDS endpoints receive unexpected typed config (envoyproxy#14709)
When a listener config contains an Any proto with an unknown proto type, the config is rejected. If debug logging is turned on, though, Envoy first attempts to print the JSON representation of the config. This caused a RELEASE_ASSERT to trigger, crashing Envoy. This PR renames the unsafe proto-to-JSON conversion function and replaces its usage with the safe version in the listener manager to prevent the crash. Signed-off-by: Alex Konradi <[email protected]>
1 parent 7d1a3a3 commit de8c505

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+241
-110
lines changed

docs/root/version_history/current.rst

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Bug Fixes
3636
* grpc-web: fix local reply and non-proto-encoded gRPC response handling for small response bodies. This fix can be temporarily reverted by setting `envoy.reloadable_features.grpc_web_fix_non_proto_encoded_response_handling` to false.
3737
* http: disallowing "host:" in request_headers_to_add for behavioral consistency with rejecting :authority header. This behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_host_like_authority` to false.
3838
* http: reverting a behavioral change where upstream connect timeouts were temporarily treated differently from other connection failures. The change back to the original behavior can be temporarily reverted by setting `envoy.reloadable_features.treat_upstream_connect_timeout_as_connect_failure` to false.
39+
* listener: prevent crashing when an unknown listener config proto is received and debug logging is enabled.
3940
* upstream: fix handling of moving endpoints between priorities when active health checks are enabled. Previously moving to a higher numbered priority was a NOOP, and moving to a lower numbered priority caused an abort.
4041

4142
Removed Config or Runtime

source/common/config/xds_context_params.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const NodeContextRenderers& nodeParamCbs() {
4242
void mergeMetadataJson(Protobuf::Map<std::string, std::string>& params,
4343
const ProtobufWkt::Struct& metadata, const std::string& prefix) {
4444
for (const auto& it : metadata.fields()) {
45-
params[prefix + it.first] = MessageUtil::getJsonStringFromMessage(it.second);
45+
params[prefix + it.first] = MessageUtil::getJsonStringFromMessageOrDie(it.second);
4646
}
4747
}
4848

source/common/formatter/substitution_formatter.cc

+4-3
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,11 @@ std::string JsonFormatterImpl::format(const Http::RequestHeaderMap& request_head
138138
const Http::ResponseTrailerMap& response_trailers,
139139
const StreamInfo::StreamInfo& stream_info,
140140
absl::string_view local_reply_body) const {
141-
const auto output_struct = struct_formatter_.format(
141+
const ProtobufWkt::Struct output_struct = struct_formatter_.format(
142142
request_headers, response_headers, response_trailers, stream_info, local_reply_body);
143143

144-
const std::string log_line = MessageUtil::getJsonStringFromMessage(output_struct, false, true);
144+
const std::string log_line =
145+
MessageUtil::getJsonStringFromMessageOrDie(output_struct, false, true);
145146
return absl::StrCat(log_line, "\n");
146147
}
147148

@@ -1116,7 +1117,7 @@ MetadataFormatter::formatMetadata(const envoy::config::core::v3::Metadata& metad
11161117
return absl::nullopt;
11171118
}
11181119

1119-
std::string json = MessageUtil::getJsonStringFromMessage(value, false, true);
1120+
std::string json = MessageUtil::getJsonStringFromMessageOrDie(value, false, true);
11201121
truncate(json, max_length_);
11211122
return json;
11221123
}

source/common/protobuf/protobuf.h

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "google/protobuf/service.h"
2222
#include "google/protobuf/struct.pb.h"
2323
#include "google/protobuf/stubs/status.h"
24+
#include "google/protobuf/stubs/statusor.h"
2425
#include "google/protobuf/text_format.h"
2526
#include "google/protobuf/util/field_mask_util.h"
2627
#include "google/protobuf/util/json_util.h"

source/common/protobuf/utility.cc

+23-9
Original file line numberDiff line numberDiff line change
@@ -589,10 +589,14 @@ void MessageUtil::checkForUnexpectedFields(const Protobuf::Message& message,
589589
std::string MessageUtil::getYamlStringFromMessage(const Protobuf::Message& message,
590590
const bool block_print,
591591
const bool always_print_primitive_fields) {
592-
std::string json = getJsonStringFromMessage(message, false, always_print_primitive_fields);
592+
593+
auto json_or_error = getJsonStringFromMessage(message, false, always_print_primitive_fields);
594+
if (!json_or_error.ok()) {
595+
throw EnvoyException(json_or_error.status().ToString());
596+
}
593597
YAML::Node node;
594598
try {
595-
node = YAML::Load(json);
599+
node = YAML::Load(json_or_error.value());
596600
} catch (YAML::ParserException& e) {
597601
throw EnvoyException(e.what());
598602
} catch (YAML::BadConversion& e) {
@@ -612,9 +616,9 @@ std::string MessageUtil::getYamlStringFromMessage(const Protobuf::Message& messa
612616
return out.c_str();
613617
}
614618

615-
std::string MessageUtil::getJsonStringFromMessage(const Protobuf::Message& message,
616-
const bool pretty_print,
617-
const bool always_print_primitive_fields) {
619+
ProtobufUtil::StatusOr<std::string>
620+
MessageUtil::getJsonStringFromMessage(const Protobuf::Message& message, const bool pretty_print,
621+
const bool always_print_primitive_fields) {
618622
Protobuf::util::JsonPrintOptions json_options;
619623
// By default, proto field names are converted to camelCase when the message is converted to JSON.
620624
// Setting this option makes debugging easier because it keeps field names consistent in JSON
@@ -629,12 +633,23 @@ std::string MessageUtil::getJsonStringFromMessage(const Protobuf::Message& messa
629633
json_options.always_print_primitive_fields = true;
630634
}
631635
std::string json;
632-
const auto status = Protobuf::util::MessageToJsonString(message, &json, json_options);
633-
// This should always succeed unless something crash-worthy such as out-of-memory.
634-
RELEASE_ASSERT(status.ok(), "");
636+
if (auto status = Protobuf::util::MessageToJsonString(message, &json, json_options);
637+
!status.ok()) {
638+
return status;
639+
}
635640
return json;
636641
}
637642

643+
std::string MessageUtil::getJsonStringFromMessageOrError(const Protobuf::Message& message,
644+
bool pretty_print,
645+
bool always_print_primitive_fields) {
646+
auto json_or_error =
647+
getJsonStringFromMessage(message, pretty_print, always_print_primitive_fields);
648+
return json_or_error.ok() ? std::move(json_or_error).value()
649+
: fmt::format("Failed to convert protobuf message to JSON string: {}",
650+
json_or_error.status().ToString());
651+
}
652+
638653
void MessageUtil::unpackTo(const ProtobufWkt::Any& any_message, Protobuf::Message& message) {
639654
// If we don't have a type URL match, try an earlier version.
640655
const absl::string_view any_full_name =
@@ -1004,5 +1019,4 @@ void TimestampUtil::systemClockToTimestamp(const SystemTime system_clock_time,
10041019
.time_since_epoch()
10051020
.count()));
10061021
}
1007-
10081022
} // namespace Envoy

source/common/protobuf/utility.h

+39-4
Original file line numberDiff line numberDiff line change
@@ -422,16 +422,51 @@ class MessageUtil {
422422
const bool always_print_primitive_fields = false);
423423

424424
/**
425-
* Extract JSON as string from a google.protobuf.Message.
425+
* Extract JSON as string from a google.protobuf.Message. Returns an error if the message cannot
426+
* be represented as JSON, which can occur if it contains an Any proto with an unrecognized type
427+
* URL or invalid data, or if memory cannot be allocated.
428+
* @param message message of type type.googleapis.com/google.protobuf.Message.
429+
* @param pretty_print whether the returned JSON should be formatted.
430+
* @param always_print_primitive_fields whether to include primitive fields set to their default
431+
* values, e.g. an int32 set to 0 or a bool set to false.
432+
* @return ProtobufUtil::StatusOr<std::string> of formatted JSON object, or an error status if
433+
* conversion fails.
434+
*/
435+
static ProtobufUtil::StatusOr<std::string>
436+
getJsonStringFromMessage(const Protobuf::Message& message, bool pretty_print = false,
437+
bool always_print_primitive_fields = false);
438+
439+
/**
440+
* Extract JSON as string from a google.protobuf.Message, crashing if the conversion to JSON
441+
* fails. This method is safe so long as the message does not contain an Any proto with an
442+
* unrecognized type or invalid data.
426443
* @param message message of type type.googleapis.com/google.protobuf.Message.
427444
* @param pretty_print whether the returned JSON should be formatted.
428445
* @param always_print_primitive_fields whether to include primitive fields set to their default
429446
* values, e.g. an int32 set to 0 or a bool set to false.
430447
* @return std::string of formatted JSON object.
431448
*/
432-
static std::string getJsonStringFromMessage(const Protobuf::Message& message,
433-
bool pretty_print = false,
434-
bool always_print_primitive_fields = false);
449+
static std::string getJsonStringFromMessageOrDie(const Protobuf::Message& message,
450+
bool pretty_print = false,
451+
bool always_print_primitive_fields = false) {
452+
auto json_or_error =
453+
getJsonStringFromMessage(message, pretty_print, always_print_primitive_fields);
454+
RELEASE_ASSERT(json_or_error.ok(), json_or_error.status().ToString());
455+
return std::move(json_or_error).value();
456+
}
457+
458+
/**
459+
* Extract JSON as string from a google.protobuf.Message, returning some error string if the
460+
* conversion to JSON fails.
461+
* @param message message of type type.googleapis.com/google.protobuf.Message.
462+
* @param pretty_print whether the returned JSON should be formatted.
463+
* @param always_print_primitive_fields whether to include primitive fields set to their default
464+
* values, e.g. an int32 set to 0 or a bool set to false.
465+
* @return std::string of formatted JSON object, or an error message if conversion fails.
466+
*/
467+
static std::string getJsonStringFromMessageOrError(const Protobuf::Message& message,
468+
bool pretty_print = false,
469+
bool always_print_primitive_fields = false);
435470

436471
/**
437472
* Utility method to create a Struct containing the passed in key/value strings.

source/common/tracing/http_tracer_impl.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,10 @@ void MetadataCustomTag::apply(Span& span, const CustomTagContext& ctx) const {
352352
span.setTag(tag(), value.string_value());
353353
return;
354354
case ProtobufWkt::Value::kListValue:
355-
span.setTag(tag(), MessageUtil::getJsonStringFromMessage(value.list_value()));
355+
span.setTag(tag(), MessageUtil::getJsonStringFromMessageOrDie(value.list_value()));
356356
return;
357357
case ProtobufWkt::Value::kStructValue:
358-
span.setTag(tag(), MessageUtil::getJsonStringFromMessage(value.struct_value()));
358+
span.setTag(tag(), MessageUtil::getJsonStringFromMessageOrDie(value.struct_value()));
359359
return;
360360
default:
361361
break;

source/common/upstream/health_checker_base_impl.cc

+3-2
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,9 @@ void HealthCheckEventLoggerImpl::createHealthCheckEvent(
450450
callback(event);
451451

452452
// Make sure the type enums make it into the JSON
453-
const auto json = MessageUtil::getJsonStringFromMessage(event, /* pretty_print */ false,
454-
/* always_print_primitive_fields */ true);
453+
const auto json =
454+
MessageUtil::getJsonStringFromMessageOrError(event, /* pretty_print */ false,
455+
/* always_print_primitive_fields */ true);
455456
file_->write(fmt::format("{}\n", json));
456457
}
457458
} // namespace Upstream

source/common/upstream/outlier_detection_impl.cc

+6-4
Original file line numberDiff line numberDiff line change
@@ -764,8 +764,9 @@ void EventLoggerImpl::logEject(const HostDescriptionConstSharedPtr& host, Detect
764764
event.mutable_eject_consecutive_event();
765765
}
766766

767-
const auto json = MessageUtil::getJsonStringFromMessage(event, /* pretty_print */ false,
768-
/* always_print_primitive_fields */ true);
767+
const auto json =
768+
MessageUtil::getJsonStringFromMessageOrError(event, /* pretty_print */ false,
769+
/* always_print_primitive_fields */ true);
769770
file_->write(fmt::format("{}\n", json));
770771
}
771772

@@ -777,8 +778,9 @@ void EventLoggerImpl::logUneject(const HostDescriptionConstSharedPtr& host) {
777778

778779
event.set_action(envoy::data::cluster::v2alpha::UNEJECT);
779780

780-
const auto json = MessageUtil::getJsonStringFromMessage(event, /* pretty_print */ false,
781-
/* always_print_primitive_fields */ true);
781+
const auto json =
782+
MessageUtil::getJsonStringFromMessageOrError(event, /* pretty_print */ false,
783+
/* always_print_primitive_fields */ true);
782784
file_->write(fmt::format("{}\n", json));
783785
}
784786

source/common/upstream/subset_lb.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,8 @@ std::string SubsetLoadBalancer::describeMetadata(const SubsetLoadBalancer::Subse
623623
first = false;
624624
}
625625

626-
buf << it.first << "=" << MessageUtil::getJsonStringFromMessage(it.second);
626+
const ProtobufWkt::Value& value = it.second;
627+
buf << it.first << "=" << MessageUtil::getJsonStringFromMessageOrDie(value);
627628
}
628629

629630
return buf.str();

source/extensions/common/tap/admin.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ void AdminHandler::AdminPerTapSinkHandle::submitTrace(
126126
switch (format) {
127127
case envoy::config::tap::v3::OutputSink::JSON_BODY_AS_STRING:
128128
case envoy::config::tap::v3::OutputSink::JSON_BODY_AS_BYTES:
129-
output_string = MessageUtil::getJsonStringFromMessage(*trace, true, true);
129+
output_string = MessageUtil::getJsonStringFromMessageOrError(*trace, true, true);
130130
break;
131131
default:
132132
NOT_REACHED_GCOVR_EXCL_LINE;

source/extensions/common/tap/tap_config_base.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ void FilePerTapSink::FilePerTapSinkHandle::submitTrace(
209209
break;
210210
case envoy::config::tap::v3::OutputSink::JSON_BODY_AS_BYTES:
211211
case envoy::config::tap::v3::OutputSink::JSON_BODY_AS_STRING:
212-
output_file_ << MessageUtil::getJsonStringFromMessage(*trace, true, true);
212+
output_file_ << MessageUtil::getJsonStringFromMessageOrError(*trace, true, true);
213213
break;
214214
default:
215215
NOT_REACHED_GCOVR_EXCL_LINE;

source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ void Filter::jsonizeRequest(Http::RequestHeaderMap const& headers, const Buffer:
310310
}
311311

312312
MessageUtil::validate(json_req, ProtobufMessage::getStrictValidationVisitor());
313-
const std::string json_data = MessageUtil::getJsonStringFromMessage(
313+
const std::string json_data = MessageUtil::getJsonStringFromMessageOrError(
314314
json_req, false /* pretty_print */, true /* always_print_primitive_fields */);
315315
out.add(json_data);
316316
}

source/extensions/filters/http/squash/squash_filter.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ SquashFilterConfig::SquashFilterConfig(
5050
std::string SquashFilterConfig::getAttachment(const ProtobufWkt::Struct& attachment_template) {
5151
ProtobufWkt::Struct attachment_json(attachment_template);
5252
updateTemplateInStruct(attachment_json);
53-
return MessageUtil::getJsonStringFromMessage(attachment_json);
53+
return MessageUtil::getJsonStringFromMessageOrDie(attachment_json);
5454
}
5555

5656
void SquashFilterConfig::updateTemplateInStruct(ProtobufWkt::Struct& attachment_template) {

source/extensions/filters/network/dubbo_proxy/config.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ void ConfigImpl::registerFilter(const DubboFilterConfig& proto_config) {
139139
ENVOY_LOG(debug, " dubbo filter #{}", filter_factories_.size());
140140
ENVOY_LOG(debug, " name: {}", string_name);
141141
ENVOY_LOG(debug, " config: {}",
142-
MessageUtil::getJsonStringFromMessage(proto_config.config(), true));
142+
MessageUtil::getJsonStringFromMessageOrError(proto_config.config(), true));
143143

144144
auto& factory =
145145
Envoy::Config::Utility::getAndCheckFactoryByName<DubboFilters::NamedDubboFilterConfigFactory>(

source/extensions/filters/network/http_connection_manager/config.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ void HttpConnectionManagerConfig::processFilter(
516516
callback, proto_config.name());
517517
ENVOY_LOG(debug, " name: {}", filter_config_provider->name());
518518
ENVOY_LOG(debug, " config: {}",
519-
MessageUtil::getJsonStringFromMessage(
519+
MessageUtil::getJsonStringFromMessageOrError(
520520
proto_config.has_typed_config()
521521
? static_cast<const Protobuf::Message&>(proto_config.typed_config())
522522
: static_cast<const Protobuf::Message&>(

source/extensions/filters/network/rocketmq_proxy/active_message.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ void ActiveMessage::onQueryTopicRoute() {
209209
TopicRouteData topic_route_data(std::move(queue_data_list), std::move(broker_data_list));
210210
ProtobufWkt::Struct data_struct;
211211
topic_route_data.encode(data_struct);
212-
std::string json = MessageUtil::getJsonStringFromMessage(data_struct);
212+
std::string json = MessageUtil::getJsonStringFromMessageOrDie(data_struct);
213213
ENVOY_LOG(trace, "Serialize TopicRouteData for {} OK:\n{}", cluster_name, json);
214214
RemotingCommandPtr response = std::make_unique<RemotingCommand>(
215215
static_cast<int>(ResponseCode::Success), downstreamRequest()->version(),

source/extensions/filters/network/rocketmq_proxy/codec.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ void Encoder::encode(const RemotingCommandPtr& command, Buffer::Instance& data)
391391
(*fields)["extFields"] = ext_fields_v;
392392
}
393393

394-
std::string json = MessageUtil::getJsonStringFromMessage(command_struct);
394+
std::string json = MessageUtil::getJsonStringFromMessageOrDie(command_struct);
395395

396396
int32_t frame_length = 4;
397397
int32_t header_length = json.size();

source/extensions/filters/network/rocketmq_proxy/conn_manager.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ void ConnectionManager::onGetConsumerListByGroup(RemotingCommandPtr request) {
301301
RemotingCommandPtr response = std::make_unique<RemotingCommand>(
302302
enumToSignedInt(ResponseCode::Success), request->version(), request->opaque());
303303
response->markAsResponse();
304-
std::string json = MessageUtil::getJsonStringFromMessage(body_struct);
304+
std::string json = MessageUtil::getJsonStringFromMessageOrDie(body_struct);
305305
response->body().add(json);
306306
ENVOY_LOG(trace, "GetConsumerListByGroup respond with body: {}", json);
307307

source/extensions/filters/network/thrift_proxy/config.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ void ConfigImpl::processFilter(
158158
ENVOY_LOG(debug, " thrift filter #{}", filter_factories_.size());
159159
ENVOY_LOG(debug, " name: {}", string_name);
160160
ENVOY_LOG(debug, " config: {}",
161-
MessageUtil::getJsonStringFromMessage(
161+
MessageUtil::getJsonStringFromMessageOrError(
162162
proto_config.has_typed_config()
163163
? static_cast<const Protobuf::Message&>(proto_config.typed_config())
164164
: static_cast<const Protobuf::Message&>(

source/extensions/tracers/dynamic_ot/config.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ Tracing::HttpTracerSharedPtr DynamicOpenTracingTracerFactory::createHttpTracerTy
2121
const envoy::config::trace::v3::DynamicOtConfig& proto_config,
2222
Server::Configuration::TracerFactoryContext& context) {
2323
const std::string& library = proto_config.library();
24-
const std::string config = MessageUtil::getJsonStringFromMessage(proto_config.config());
24+
const ProtobufWkt::Struct& config_struct = proto_config.config();
25+
const std::string config = MessageUtil::getJsonStringFromMessageOrDie(config_struct);
2526
Tracing::DriverPtr dynamic_driver = std::make_unique<DynamicOpenTracingDriver>(
2627
context.serverFactoryContext().scope(), library, config);
2728
return std::make_shared<Tracing::HttpTracerImpl>(std::move(dynamic_driver),

source/extensions/tracers/xray/daemon_broker.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ std::string createHeader(const std::string& format, uint32_t version) {
2222
source::extensions::tracers::xray::daemon::Header header;
2323
header.set_format(format);
2424
header.set_version(version);
25-
return MessageUtil::getJsonStringFromMessage(header, false /* pretty_print */,
26-
false /* always_print_primitive_fields */);
25+
return MessageUtil::getJsonStringFromMessageOrDie(header, false /* pretty_print */,
26+
false /* always_print_primitive_fields */);
2727
}
2828

2929
} // namespace

source/extensions/tracers/xray/tracer.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ void Span::finishSpan() {
9292
s.mutable_annotations()->insert({item.first, item.second});
9393
}
9494

95-
const std::string json = MessageUtil::getJsonStringFromMessage(
95+
const std::string json = MessageUtil::getJsonStringFromMessageOrDie(
9696
s, false /* pretty_print */, false /* always_print_primitive_fields */);
9797

9898
broker_.send(json);

source/extensions/tracers/zipkin/span_buffer.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ std::string JsonV2Serializer::serialize(const std::vector<Span>& zipkin_spans) {
8080
out, absl::StrJoin(
8181
toListOfSpans(zipkin_span, replacements), ",",
8282
[&replacement_values](std::string* element, const ProtobufWkt::Struct& span) {
83-
const std::string json = MessageUtil::getJsonStringFromMessage(
83+
const std::string json = MessageUtil::getJsonStringFromMessageOrDie(
8484
span, /* pretty_print */ false,
8585
/* always_print_primitive_fields */ true);
8686

0 commit comments

Comments
 (0)