Coverage Report

Created: 2023-11-12 09:30

/proc/self/cwd/source/common/protobuf/utility.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/common/protobuf/utility.h"
2
3
#include <limits>
4
#include <numeric>
5
6
#include "envoy/annotations/deprecation.pb.h"
7
#include "envoy/protobuf/message_validator.h"
8
#include "envoy/type/v3/percent.pb.h"
9
10
#include "source/common/common/assert.h"
11
#include "source/common/common/documentation_url.h"
12
#include "source/common/common/fmt.h"
13
#include "source/common/protobuf/message_validator_impl.h"
14
#include "source/common/protobuf/protobuf.h"
15
#include "source/common/protobuf/visitor.h"
16
#include "source/common/runtime/runtime_features.h"
17
18
#include "absl/strings/match.h"
19
#include "udpa/annotations/sensitive.pb.h"
20
#include "udpa/annotations/status.pb.h"
21
#include "validate/validate.h"
22
#include "xds/annotations/v3/status.pb.h"
23
24
using namespace std::chrono_literals;
25
26
namespace Envoy {
27
namespace {
28
29
44.5M
absl::string_view filenameFromPath(absl::string_view full_path) {
30
44.5M
  size_t index = full_path.rfind('/');
31
44.5M
  if (index == std::string::npos || index == full_path.size()) {
32
0
    return full_path;
33
0
  }
34
44.5M
  return full_path.substr(index + 1, full_path.size());
35
44.5M
}
36
37
// Logs a warning for use of a deprecated field or runtime-overridden use of an
38
// otherwise fatal field. Throws a warning on use of a fatal by default field.
39
void deprecatedFieldHelper(Runtime::Loader* runtime, bool proto_annotated_as_deprecated,
40
                           bool proto_annotated_as_disallowed, const std::string& feature_name,
41
                           std::string error, const Protobuf::Message& message,
42
150k
                           ProtobufMessage::ValidationVisitor& validation_visitor) {
43
// This option is for Envoy builds with --define deprecated_features=disabled
44
// The build options CI then verifies that as Envoy developers deprecate fields,
45
// that they update canonical configs and unit tests to not use those deprecated
46
// fields, by making their use fatal in the build options CI.
47
#ifdef ENVOY_DISABLE_DEPRECATED_FEATURES
48
  bool warn_only = false;
49
#else
50
150k
  bool warn_only = true;
51
150k
#endif
52
150k
  if (runtime &&
53
150k
      runtime->snapshot().getBoolean("envoy.features.fail_on_any_deprecated_feature", false)) {
54
0
    warn_only = false;
55
0
  }
56
150k
  bool warn_default = warn_only;
57
  // Allow runtime to be null both to not crash if this is called before server initialization,
58
  // and so proto validation works in context where runtime singleton is not set up (e.g.
59
  // standalone config validation utilities)
60
150k
  if (runtime && proto_annotated_as_deprecated) {
61
    // This is set here, rather than above, so that in the absence of a
62
    // registry (i.e. test) the default for if a feature is allowed or not is
63
    // based on ENVOY_DISABLE_DEPRECATED_FEATURES.
64
0
    warn_only &= !proto_annotated_as_disallowed;
65
0
    warn_default = warn_only;
66
0
    warn_only = runtime->snapshot().deprecatedFeatureEnabled(feature_name, warn_only);
67
0
  }
68
  // Note this only checks if the runtime override has an actual effect. It
69
  // does not change the logged warning if someone "allows" a deprecated but not
70
  // yet fatal field.
71
150k
  const bool runtime_overridden = (warn_default == false && warn_only == true);
72
73
150k
  std::string with_overridden = fmt::format(
74
150k
      fmt::runtime(error),
75
150k
      (runtime_overridden ? "runtime overrides to continue using now fatal-by-default " : ""));
76
77
150k
  validation_visitor.onDeprecatedField("type " + message.GetTypeName() + " " + with_overridden,
78
150k
                                       warn_only);
79
150k
}
80
81
} // namespace
82
83
namespace ProtobufPercentHelper {
84
85
10.5k
uint64_t checkAndReturnDefault(uint64_t default_value, uint64_t max_value) {
86
10.5k
  ASSERT(default_value <= max_value);
87
10.5k
  return default_value;
88
10.5k
}
89
90
422
uint64_t convertPercent(double percent, uint64_t max_value) {
91
  // Checked by schema.
92
422
  ASSERT(percent >= 0.0 && percent <= 100.0);
93
422
  return max_value * (percent / 100.0);
94
422
}
95
96
2.71k
bool evaluateFractionalPercent(envoy::type::v3::FractionalPercent percent, uint64_t random_value) {
97
2.71k
  return random_value % fractionalPercentDenominatorToInt(percent.denominator()) <
98
2.71k
         percent.numerator();
99
2.71k
}
100
101
uint64_t fractionalPercentDenominatorToInt(
102
6.71k
    const envoy::type::v3::FractionalPercent::DenominatorType& denominator) {
103
6.71k
  switch (denominator) {
104
0
    PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
105
6.37k
  case envoy::type::v3::FractionalPercent::HUNDRED:
106
6.37k
    return 100;
107
0
  case envoy::type::v3::FractionalPercent::TEN_THOUSAND:
108
0
    return 10000;
109
341
  case envoy::type::v3::FractionalPercent::MILLION:
110
341
    return 1000000;
111
6.71k
  }
112
0
  PANIC_DUE_TO_CORRUPT_ENUM
113
0
}
114
115
} // namespace ProtobufPercentHelper
116
117
MissingFieldException::MissingFieldException(const std::string& message)
118
187
    : EnvoyException(message) {}
119
120
ProtoValidationException::ProtoValidationException(const std::string& message)
121
3.81k
    : EnvoyException(message) {
122
3.81k
  ENVOY_LOG_MISC(debug, "Proto validation error; throwing {}", what());
123
3.81k
}
124
125
void ProtoExceptionUtil::throwMissingFieldException(const std::string& field_name,
126
187
                                                    const Protobuf::Message& message) {
127
187
  std::string error =
128
187
      fmt::format("Field '{}' is missing in: {}", field_name, message.DebugString());
129
187
  throwExceptionOrPanic(MissingFieldException, error);
130
187
}
131
132
void ProtoExceptionUtil::throwProtoValidationException(const std::string& validation_error,
133
3.80k
                                                       const Protobuf::Message& message) {
134
3.80k
  std::string error = fmt::format("Proto constraint validation failed ({}): {}", validation_error,
135
3.80k
                                  message.DebugString());
136
3.80k
  throwExceptionOrPanic(ProtoValidationException, error);
137
3.80k
}
138
139
205k
size_t MessageUtil::hash(const Protobuf::Message& message) {
140
205k
  std::string text_format;
141
142
205k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
143
205k
  {
144
205k
    Protobuf::TextFormat::Printer printer;
145
205k
    printer.SetExpandAny(true);
146
205k
    printer.SetUseFieldNumber(true);
147
205k
    printer.SetSingleLineMode(true);
148
205k
    printer.SetHideUnknownFields(true);
149
205k
    printer.PrintToString(message, &text_format);
150
205k
  }
151
#else
152
  absl::StrAppend(&text_format, message.SerializeAsString());
153
#endif
154
155
205k
  return HashUtil::xxHash64(text_format);
156
205k
}
157
158
#if !defined(ENVOY_ENABLE_FULL_PROTOS)
159
// NOLINTNEXTLINE(readability-identifier-naming)
160
bool MessageLiteDifferencer::Equals(const Protobuf::Message& message1,
161
                                    const Protobuf::Message& message2) {
162
  return MessageUtil::hash(message1) == MessageUtil::hash(message2);
163
}
164
165
// NOLINTNEXTLINE(readability-identifier-naming)
166
bool MessageLiteDifferencer::Equivalent(const Protobuf::Message& message1,
167
                                        const Protobuf::Message& message2) {
168
  return Equals(message1, message2);
169
}
170
#endif
171
172
namespace {
173
174
void checkForDeprecatedNonRepeatedEnumValue(
175
    const Protobuf::Message& message, absl::string_view filename,
176
    const Protobuf::FieldDescriptor* field, const Protobuf::Reflection* reflection,
177
44.5M
    Runtime::Loader* runtime, ProtobufMessage::ValidationVisitor& validation_visitor) {
178
  // Repeated fields will be handled by recursion in checkForUnexpectedFields.
179
44.5M
  if (field->is_repeated() || field->cpp_type() != Protobuf::FieldDescriptor::CPPTYPE_ENUM) {
180
43.0M
    return;
181
43.0M
  }
182
183
1.51M
  Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
184
1.51M
  bool default_value = !reflection->HasField(*reflectable_message, field);
185
186
1.51M
  const Protobuf::EnumValueDescriptor* enum_value_descriptor =
187
1.51M
      reflection->GetEnum(*reflectable_message, field);
188
1.51M
  if (!enum_value_descriptor->options().deprecated()) {
189
1.50M
    return;
190
1.50M
  }
191
192
4.96k
  const std::string error =
193
4.96k
      absl::StrCat("Using {}", (default_value ? "the default now-" : ""), "deprecated value ",
194
4.96k
                   enum_value_descriptor->name(), " for enum '", field->full_name(), "' from file ",
195
4.96k
                   filename, ". This enum value will be removed from Envoy soon",
196
4.96k
                   (default_value ? " so a non-default value must now be explicitly set" : ""),
197
4.96k
                   ". Please see " ENVOY_DOC_URL_VERSION_HISTORY " for details.");
198
4.96k
  deprecatedFieldHelper(
199
4.96k
      runtime, true /*deprecated*/,
200
4.96k
      enum_value_descriptor->options().GetExtension(envoy::annotations::disallowed_by_default_enum),
201
4.96k
      absl::StrCat("envoy.deprecated_features:", enum_value_descriptor->full_name()), error,
202
4.96k
      message, validation_visitor);
203
4.96k
}
204
205
constexpr absl::string_view WipWarning =
206
    "API features marked as work-in-progress are not considered stable, are not covered by the "
207
    "threat model, are not supported by the security team, and are subject to breaking changes. Do "
208
    "not use this feature without understanding each of the previous points.";
209
210
class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor {
211
public:
212
  UnexpectedFieldProtoVisitor(ProtobufMessage::ValidationVisitor& validation_visitor,
213
                              Runtime::Loader* runtime)
214
274k
      : validation_visitor_(validation_visitor), runtime_(runtime) {}
215
216
44.5M
  void onField(const Protobuf::Message& message, const Protobuf::FieldDescriptor& field) override {
217
44.5M
    Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
218
44.5M
    const Protobuf::Reflection* reflection = reflectable_message->GetReflection();
219
44.5M
    absl::string_view filename = filenameFromPath(field.file()->name());
220
221
    // Before we check to see if the field is in use, see if there's a
222
    // deprecated default enum value.
223
44.5M
    checkForDeprecatedNonRepeatedEnumValue(message, filename, &field, reflection, runtime_,
224
44.5M
                                           validation_visitor_);
225
226
    // If this field is not in use, continue.
227
44.5M
    if ((field.is_repeated() && reflection->FieldSize(*reflectable_message, &field) == 0) ||
228
44.5M
        (!field.is_repeated() && !reflection->HasField(*reflectable_message, &field))) {
229
31.5M
      return;
230
31.5M
    }
231
232
13.0M
    const auto& field_status = field.options().GetExtension(xds::annotations::v3::field_status);
233
13.0M
    if (field_status.work_in_progress()) {
234
10.2k
      validation_visitor_.onWorkInProgress(fmt::format(
235
10.2k
          "field '{}' is marked as work-in-progress. {}", field.full_name(), WipWarning));
236
10.2k
    }
237
238
    // If this field is deprecated, warn or throw an error.
239
13.0M
    if (field.options().deprecated()) {
240
145k
      const std::string warning =
241
145k
          absl::StrCat("Using {}deprecated option '", field.full_name(), "' from file ", filename,
242
145k
                       ". This configuration will be removed from "
243
145k
                       "Envoy soon. Please see " ENVOY_DOC_URL_VERSION_HISTORY " for details.");
244
245
145k
      deprecatedFieldHelper(runtime_, true /*deprecated*/,
246
145k
                            field.options().GetExtension(envoy::annotations::disallowed_by_default),
247
145k
                            absl::StrCat("envoy.deprecated_features:", field.full_name()), warning,
248
145k
                            message, validation_visitor_);
249
145k
    }
250
13.0M
  }
251
252
  void onMessage(const Protobuf::Message& message,
253
10.1M
                 absl::Span<const Protobuf::Message* const> parents, bool) override {
254
10.1M
    Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
255
10.1M
    if (reflectable_message->GetDescriptor()
256
10.1M
            ->options()
257
10.1M
            .GetExtension(xds::annotations::v3::message_status)
258
10.1M
            .work_in_progress()) {
259
32.0k
      validation_visitor_.onWorkInProgress(fmt::format(
260
32.0k
          "message '{}' is marked as work-in-progress. {}", message.GetTypeName(), WipWarning));
261
32.0k
    }
262
263
10.1M
    const auto& udpa_file_options =
264
10.1M
        reflectable_message->GetDescriptor()->file()->options().GetExtension(
265
10.1M
            udpa::annotations::file_status);
266
10.1M
    const auto& xds_file_options =
267
10.1M
        reflectable_message->GetDescriptor()->file()->options().GetExtension(
268
10.1M
            xds::annotations::v3::file_status);
269
10.1M
    if (udpa_file_options.work_in_progress() || xds_file_options.work_in_progress()) {
270
1.70k
      validation_visitor_.onWorkInProgress(fmt::format(
271
1.70k
          "message '{}' is contained in proto file '{}' marked as work-in-progress. {}",
272
1.70k
          message.GetTypeName(), reflectable_message->GetDescriptor()->file()->name(), WipWarning));
273
1.70k
    }
274
275
    // Reject unknown fields.
276
10.1M
    const auto& unknown_fields =
277
10.1M
        reflectable_message->GetReflection()->GetUnknownFields(*reflectable_message);
278
10.1M
    if (!unknown_fields.empty()) {
279
22
      std::string error_msg;
280
117
      for (int n = 0; n < unknown_fields.field_count(); ++n) {
281
95
        absl::StrAppend(&error_msg, n > 0 ? ", " : "", unknown_fields.field(n).number());
282
95
      }
283
22
      if (!error_msg.empty()) {
284
22
        validation_visitor_.onUnknownField(
285
22
            fmt::format("type {}({}) with unknown field set {{{}}}", message.GetTypeName(),
286
22
                        !parents.empty()
287
22
                            ? absl::StrJoin(parents, "::",
288
18
                                            [](std::string* out, const Protobuf::Message* const m) {
289
18
                                              absl::StrAppend(out, m->GetTypeName());
290
18
                                            })
291
22
                            : "root",
292
22
                        error_msg));
293
22
      }
294
22
    }
295
10.1M
  }
296
297
private:
298
  ProtobufMessage::ValidationVisitor& validation_visitor_;
299
  Runtime::Loader* runtime_;
300
};
301
302
} // namespace
303
304
void MessageUtil::checkForUnexpectedFields(const Protobuf::Message& message,
305
                                           ProtobufMessage::ValidationVisitor& validation_visitor,
306
274k
                                           bool recurse_into_any) {
307
274k
  Runtime::Loader* runtime = validation_visitor.runtime().has_value()
308
274k
                                 ? &validation_visitor.runtime().value().get()
309
274k
                                 : nullptr;
310
274k
  UnexpectedFieldProtoVisitor unexpected_field_visitor(validation_visitor, runtime);
311
274k
  ProtobufMessage::traverseMessage(unexpected_field_visitor, message, recurse_into_any);
312
274k
}
313
314
namespace {
315
316
class PgvCheckVisitor : public ProtobufMessage::ConstProtoVisitor {
317
public:
318
  void onMessage(const Protobuf::Message& message, absl::Span<const Protobuf::Message* const>,
319
0
                 bool was_any_or_top_level) override {
320
0
    Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
321
0
    std::string err;
322
    // PGV verification is itself recursive up to the point at which it hits an Any message. As
323
    // such, to avoid N^2 checking of the tree, we only perform an additional check at the point
324
    // at which PGV would have stopped because it does not itself check within Any messages.
325
0
    if (was_any_or_top_level &&
326
0
        !pgv::BaseValidator::AbstractCheckMessage(*reflectable_message, &err)) {
327
0
      ProtoExceptionUtil::throwProtoValidationException(err, *reflectable_message);
328
0
    }
329
0
  }
330
331
0
  void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) override {}
332
};
333
334
} // namespace
335
336
0
void MessageUtil::recursivePgvCheck(const Protobuf::Message& message) {
337
0
  PgvCheckVisitor visitor;
338
0
  ProtobufMessage::traverseMessage(visitor, message, true);
339
0
}
340
341
2.65k
void MessageUtil::packFrom(ProtobufWkt::Any& any_message, const Protobuf::Message& message) {
342
2.65k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
343
2.65k
  any_message.PackFrom(message);
344
#else
345
  any_message.set_type_url(message.GetTypeName());
346
  any_message.set_value(message.SerializeAsString());
347
#endif
348
2.65k
}
349
350
200k
void MessageUtil::unpackTo(const ProtobufWkt::Any& any_message, Protobuf::Message& message) {
351
200k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
352
200k
  if (!any_message.UnpackTo(&message)) {
353
61
    throwEnvoyExceptionOrPanic(fmt::format("Unable to unpack as {}: {}",
354
61
                                           message.GetDescriptor()->full_name(),
355
61
                                           any_message.DebugString()));
356
#else
357
  if (!message.ParseFromString(any_message.value())) {
358
    throwEnvoyExceptionOrPanic(
359
        fmt::format("Unable to unpack as {}: {}", message.GetTypeName(), any_message.type_url()));
360
#endif
361
61
  }
362
200k
}
363
364
absl::Status MessageUtil::unpackToNoThrow(const ProtobufWkt::Any& any_message,
365
0
                                          Protobuf::Message& message) {
366
0
#if defined(ENVOY_ENABLE_FULL_PROTOS)
367
0
  if (!any_message.UnpackTo(&message)) {
368
0
    return absl::InternalError(absl::StrCat("Unable to unpack as ",
369
0
                                            message.GetDescriptor()->full_name(), ": ",
370
0
                                            any_message.DebugString()));
371
#else
372
  if (!message.ParseFromString(any_message.value())) {
373
    return absl::InternalError(
374
        absl::StrCat("Unable to unpack as ", message.GetTypeName(), ": ", any_message.type_url()));
375
#endif
376
0
  }
377
  // Ok Status is returned if `UnpackTo` succeeded.
378
0
  return absl::OkStatus();
379
0
}
380
381
0
ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::string& key, const std::string& value) {
382
0
  ProtobufWkt::Struct struct_obj;
383
0
  ProtobufWkt::Value val;
384
0
  val.set_string_value(value);
385
0
  (*struct_obj.mutable_fields())[key] = val;
386
0
  return struct_obj;
387
0
}
388
389
13
ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::map<std::string, std::string>& fields) {
390
13
  ProtobufWkt::Struct struct_obj;
391
13
  ProtobufWkt::Value val;
392
65
  for (const auto& pair : fields) {
393
65
    val.set_string_value(pair.second);
394
65
    (*struct_obj.mutable_fields())[pair.first] = val;
395
65
  }
396
13
  return struct_obj;
397
13
}
398
399
149
std::string MessageUtil::codeEnumToString(absl::StatusCode code) {
400
149
  std::string result = absl::StatusCodeToString(code);
401
  // This preserves the behavior of the `ProtobufUtil::Status(code, "").ToString();`
402
149
  return !result.empty() ? result : "UNKNOWN: ";
403
149
}
404
405
namespace {
406
407
// Forward declaration for mutually-recursive helper functions.
408
void redact(Protobuf::Message* message, bool ancestor_is_sensitive);
409
410
using Transform = std::function<void(Protobuf::Message*, const Protobuf::Reflection*,
411
                                     const Protobuf::FieldDescriptor*)>;
412
413
// To redact opaque types, namely `Any` and `TypedStruct`, we have to reify them to the concrete
414
// message types specified by their `type_url` before we can redact their contents. This is mostly
415
// identical between `Any` and `TypedStruct`, the only difference being how they are packed and
416
// unpacked. Note that we have to use reflection on the opaque type here, rather than downcasting
417
// to `Any` or `TypedStruct`, because any message we might be handling could have originated from
418
// a `DynamicMessageFactory`.
419
bool redactOpaque(Protobuf::Message* message, bool ancestor_is_sensitive,
420
1.69M
                  absl::string_view opaque_type_name, Transform unpack, Transform repack) {
421
  // Ensure this message has the opaque type we're expecting.
422
1.69M
  Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
423
1.69M
  const auto* opaque_descriptor = reflectable_message->GetDescriptor();
424
1.69M
  if (opaque_descriptor->full_name() != opaque_type_name) {
425
1.64M
    return false;
426
1.64M
  }
427
428
  // Find descriptors for the `type_url` and `value` fields. The `type_url` field must not be
429
  // empty, but `value` may be (in which case our work is done).
430
42.6k
  const auto* reflection = reflectable_message->GetReflection();
431
42.6k
  const auto* type_url_field_descriptor = opaque_descriptor->FindFieldByName("type_url");
432
42.6k
  const auto* value_field_descriptor = opaque_descriptor->FindFieldByName("value");
433
42.6k
  ASSERT(type_url_field_descriptor != nullptr && value_field_descriptor != nullptr);
434
42.6k
  if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) &&
435
42.6k
      !reflection->HasField(*reflectable_message, value_field_descriptor)) {
436
0
    return true;
437
0
  }
438
42.6k
  if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) ||
439
42.6k
      !reflection->HasField(*reflectable_message, value_field_descriptor)) {
440
5.29k
    return false;
441
5.29k
  }
442
443
  // Try to find a descriptor for `type_url` in the pool and instantiate a new message of the
444
  // correct concrete type.
445
37.3k
  const std::string type_url(
446
37.3k
      reflection->GetString(*reflectable_message, type_url_field_descriptor));
447
37.3k
  const std::string concrete_type_name(TypeUtil::typeUrlToDescriptorFullName(type_url));
448
37.3k
  const auto* concrete_descriptor =
449
37.3k
      Protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(concrete_type_name);
450
37.3k
  if (concrete_descriptor == nullptr) {
451
    // If the type URL doesn't correspond to a known proto, don't try to reify it, just treat it
452
    // like any other message. See the documented limitation on `MessageUtil::redact()` for more
453
    // context.
454
0
    ENVOY_LOG_MISC(warn, "Could not reify {} with unknown type URL {}", opaque_type_name, type_url);
455
0
    return false;
456
0
  }
457
37.3k
  Protobuf::DynamicMessageFactory message_factory;
458
37.3k
  std::unique_ptr<Protobuf::Message> typed_message(
459
37.3k
      message_factory.GetPrototype(concrete_descriptor)->New());
460
461
  // Finally we can unpack, redact, and repack the opaque message using the provided callbacks.
462
463
  // Note: the content of opaque types may contain illegal content that mismatches the type_url
464
  // which may cause unpacking to fail. We catch the exception here to avoid crashing Envoy.
465
37.3k
  TRY_ASSERT_MAIN_THREAD { unpack(typed_message.get(), reflection, value_field_descriptor); }
466
37.3k
  END_TRY CATCH(const EnvoyException& e, {
467
37.3k
    ENVOY_LOG_MISC(warn, "Could not unpack {} with type URL {}: {}", opaque_type_name, type_url,
468
37.3k
                   e.what());
469
37.3k
    return false;
470
37.3k
  });
471
37.3k
  redact(typed_message.get(), ancestor_is_sensitive);
472
37.3k
  repack(typed_message.get(), reflection, value_field_descriptor);
473
37.3k
  return true;
474
37.3k
}
475
476
588k
bool redactAny(Protobuf::Message* message, bool ancestor_is_sensitive) {
477
588k
  return redactOpaque(
478
588k
      message, ancestor_is_sensitive, "google.protobuf.Any",
479
588k
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
480
588k
                const Protobuf::FieldDescriptor* field_descriptor) {
481
37.3k
        Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
482
        // To unpack an `Any`, parse the serialized proto.
483
37.3k
        typed_message->ParseFromString(
484
37.3k
            reflection->GetString(*reflectable_message, field_descriptor));
485
37.3k
      },
486
588k
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
487
588k
                const Protobuf::FieldDescriptor* field_descriptor) {
488
37.3k
        Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
489
        // To repack an `Any`, reserialize its proto.
490
37.3k
        reflection->SetString(&(*reflectable_message), field_descriptor,
491
37.3k
                              typed_message->SerializeAsString());
492
37.3k
      });
493
588k
}
494
495
// To redact a `TypedStruct`, we have to reify it based on its `type_url` to redact it.
496
bool redactTypedStruct(Protobuf::Message* message, const char* typed_struct_type,
497
1.10M
                       bool ancestor_is_sensitive) {
498
1.10M
  return redactOpaque(
499
1.10M
      message, ancestor_is_sensitive, typed_struct_type,
500
1.10M
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
501
1.10M
                const Protobuf::FieldDescriptor* field_descriptor) {
502
0
#ifdef ENVOY_ENABLE_YAML
503
        // To unpack a `TypedStruct`, convert the struct from JSON.
504
0
        MessageUtil::jsonConvert(reflection->GetMessage(*message, field_descriptor),
505
0
                                 *typed_message);
506
#else
507
        UNREFERENCED_PARAMETER(message);
508
        UNREFERENCED_PARAMETER(typed_message);
509
        UNREFERENCED_PARAMETER(reflection);
510
        UNREFERENCED_PARAMETER(field_descriptor);
511
        IS_ENVOY_BUG("redaction requested with JSON/YAML support removed");
512
#endif
513
0
      },
514
1.10M
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
515
1.10M
                const Protobuf::FieldDescriptor* field_descriptor) {
516
  // To repack a `TypedStruct`, convert the message back to JSON.
517
0
#ifdef ENVOY_ENABLE_YAML
518
0
        MessageUtil::jsonConvert(*typed_message,
519
0
                                 *(reflection->MutableMessage(message, field_descriptor)));
520
#else
521
        UNREFERENCED_PARAMETER(message);
522
        UNREFERENCED_PARAMETER(typed_message);
523
        UNREFERENCED_PARAMETER(reflection);
524
        UNREFERENCED_PARAMETER(field_descriptor);
525
        IS_ENVOY_BUG("redaction requested with JSON/YAML support removed");
526
#endif
527
0
      });
528
1.10M
}
529
530
// Recursive helper method for MessageUtil::redact() below.
531
588k
void redact(Protobuf::Message* message, bool ancestor_is_sensitive) {
532
588k
  if (redactAny(message, ancestor_is_sensitive) ||
533
588k
      redactTypedStruct(message, "xds.type.v3.TypedStruct", ancestor_is_sensitive) ||
534
588k
      redactTypedStruct(message, "udpa.type.v1.TypedStruct", ancestor_is_sensitive)) {
535
37.3k
    return;
536
37.3k
  }
537
538
551k
  Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
539
551k
  const auto* descriptor = reflectable_message->GetDescriptor();
540
551k
  const auto* reflection = reflectable_message->GetReflection();
541
4.36M
  for (int i = 0; i < descriptor->field_count(); ++i) {
542
3.81M
    const auto* field_descriptor = descriptor->field(i);
543
544
    // Redact if this field or any of its ancestors have the `sensitive` option set.
545
3.81M
    const bool sensitive = ancestor_is_sensitive ||
546
3.81M
                           field_descriptor->options().GetExtension(udpa::annotations::sensitive);
547
548
3.81M
    if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) {
549
      // Recursive case: traverse message fields.
550
1.71M
      if (field_descriptor->is_map()) {
551
        // Redact values of maps only. Redacting both leaves the map with multiple "[redacted]"
552
        // keys.
553
39.4k
        const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor);
554
59.7k
        for (int i = 0; i < field_size; ++i) {
555
20.3k
          Protobuf::Message* map_pair_base =
556
20.3k
              reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i);
557
20.3k
          Protobuf::ReflectableMessage map_pair = createReflectableMessage(*map_pair_base);
558
20.3k
          auto* value_field_desc = map_pair->GetDescriptor()->FindFieldByName("value");
559
20.3k
          if (sensitive && (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_STRING ||
560
0
                            value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_BYTES)) {
561
0
            map_pair->GetReflection()->SetString(&(*map_pair), value_field_desc, "[redacted]");
562
20.3k
          } else if (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) {
563
20.3k
            redact(map_pair->GetReflection()->MutableMessage(&(*map_pair), value_field_desc),
564
20.3k
                   sensitive);
565
20.3k
          } else if (sensitive) {
566
0
            map_pair->GetReflection()->ClearField(&(*map_pair), value_field_desc);
567
0
          }
568
20.3k
        }
569
1.67M
      } else if (field_descriptor->is_repeated()) {
570
256k
        const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor);
571
597k
        for (int i = 0; i < field_size; ++i) {
572
340k
          redact(reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i),
573
340k
                 sensitive);
574
340k
        }
575
1.42M
      } else if (reflection->HasField(*reflectable_message, field_descriptor)) {
576
184k
        redact(reflection->MutableMessage(&(*reflectable_message), field_descriptor), sensitive);
577
184k
      }
578
2.09M
    } else if (sensitive) {
579
      // Base case: replace strings and bytes with "[redacted]" and clear all others.
580
63.2k
      if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_STRING ||
581
63.2k
          field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_BYTES) {
582
63.2k
        if (field_descriptor->is_repeated()) {
583
0
          const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor);
584
0
          for (int i = 0; i < field_size; ++i) {
585
0
            reflection->SetRepeatedString(&(*reflectable_message), field_descriptor, i,
586
0
                                          "[redacted]");
587
0
          }
588
63.2k
        } else if (reflection->HasField(*reflectable_message, field_descriptor)) {
589
15.8k
          reflection->SetString(&(*reflectable_message), field_descriptor, "[redacted]");
590
15.8k
        }
591
63.2k
      } else {
592
0
        reflection->ClearField(&(*reflectable_message), field_descriptor);
593
0
      }
594
63.2k
    }
595
3.81M
  }
596
551k
}
597
598
} // namespace
599
600
5.28k
void MessageUtil::redact(Protobuf::Message& message) {
601
5.28k
  ::Envoy::redact(&message, /* ancestor_is_sensitive = */ false);
602
5.28k
}
603
604
310
void MessageUtil::wireCast(const Protobuf::Message& src, Protobuf::Message& dst) {
605
  // This should should generally succeed, but if there are malformed UTF-8 strings in a message,
606
  // this can fail.
607
310
  if (!dst.ParseFromString(src.SerializeAsString())) {
608
0
    throwEnvoyExceptionOrPanic("Unable to deserialize during wireCast()");
609
0
  }
610
310
}
611
612
0
bool ValueUtil::equal(const ProtobufWkt::Value& v1, const ProtobufWkt::Value& v2) {
613
0
  ProtobufWkt::Value::KindCase kind = v1.kind_case();
614
0
  if (kind != v2.kind_case()) {
615
0
    return false;
616
0
  }
617
618
0
  switch (kind) {
619
0
  case ProtobufWkt::Value::KIND_NOT_SET:
620
0
    return v2.kind_case() == ProtobufWkt::Value::KIND_NOT_SET;
621
622
0
  case ProtobufWkt::Value::kNullValue:
623
0
    return true;
624
625
0
  case ProtobufWkt::Value::kNumberValue:
626
0
    return v1.number_value() == v2.number_value();
627
628
0
  case ProtobufWkt::Value::kStringValue:
629
0
    return v1.string_value() == v2.string_value();
630
631
0
  case ProtobufWkt::Value::kBoolValue:
632
0
    return v1.bool_value() == v2.bool_value();
633
634
0
  case ProtobufWkt::Value::kStructValue: {
635
0
    const ProtobufWkt::Struct& s1 = v1.struct_value();
636
0
    const ProtobufWkt::Struct& s2 = v2.struct_value();
637
0
    if (s1.fields_size() != s2.fields_size()) {
638
0
      return false;
639
0
    }
640
0
    for (const auto& it1 : s1.fields()) {
641
0
      const auto& it2 = s2.fields().find(it1.first);
642
0
      if (it2 == s2.fields().end()) {
643
0
        return false;
644
0
      }
645
646
0
      if (!equal(it1.second, it2->second)) {
647
0
        return false;
648
0
      }
649
0
    }
650
0
    return true;
651
0
  }
652
653
0
  case ProtobufWkt::Value::kListValue: {
654
0
    const ProtobufWkt::ListValue& l1 = v1.list_value();
655
0
    const ProtobufWkt::ListValue& l2 = v2.list_value();
656
0
    if (l1.values_size() != l2.values_size()) {
657
0
      return false;
658
0
    }
659
0
    for (int i = 0; i < l1.values_size(); i++) {
660
0
      if (!equal(l1.values(i), l2.values(i))) {
661
0
        return false;
662
0
      }
663
0
    }
664
0
    return true;
665
0
  }
666
0
  }
667
0
  return false;
668
0
}
669
670
43.0k
const ProtobufWkt::Value& ValueUtil::nullValue() {
671
43.0k
  static const auto* v = []() -> ProtobufWkt::Value* {
672
4
    auto* vv = new ProtobufWkt::Value();
673
4
    vv->set_null_value(ProtobufWkt::NULL_VALUE);
674
4
    return vv;
675
4
  }();
676
43.0k
  return *v;
677
43.0k
}
678
679
486k
ProtobufWkt::Value ValueUtil::stringValue(const std::string& str) {
680
486k
  ProtobufWkt::Value val;
681
486k
  val.set_string_value(str);
682
486k
  return val;
683
486k
}
684
685
0
ProtobufWkt::Value ValueUtil::optionalStringValue(const absl::optional<std::string>& str) {
686
0
  if (str.has_value()) {
687
0
    return ValueUtil::stringValue(str.value());
688
0
  }
689
0
  return ValueUtil::nullValue();
690
0
}
691
692
2.66k
ProtobufWkt::Value ValueUtil::boolValue(bool b) {
693
2.66k
  ProtobufWkt::Value val;
694
2.66k
  val.set_bool_value(b);
695
2.66k
  return val;
696
2.66k
}
697
698
18.2k
ProtobufWkt::Value ValueUtil::structValue(const ProtobufWkt::Struct& obj) {
699
18.2k
  ProtobufWkt::Value val;
700
18.2k
  (*val.mutable_struct_value()) = obj;
701
18.2k
  return val;
702
18.2k
}
703
704
10.0k
ProtobufWkt::Value ValueUtil::listValue(const std::vector<ProtobufWkt::Value>& values) {
705
10.0k
  auto list = std::make_unique<ProtobufWkt::ListValue>();
706
25.3k
  for (const auto& value : values) {
707
25.3k
    *list->add_values() = value;
708
25.3k
  }
709
10.0k
  ProtobufWkt::Value val;
710
10.0k
  val.set_allocated_list_value(list.release());
711
10.0k
  return val;
712
10.0k
}
713
714
namespace {
715
716
absl::Status validateDurationNoThrow(const ProtobufWkt::Duration& duration,
717
512k
                                     int64_t max_seconds_value) {
718
512k
  if (duration.seconds() < 0 || duration.nanos() < 0) {
719
33.2k
    return absl::OutOfRangeError(
720
33.2k
        fmt::format("Expected positive duration: {}", duration.DebugString()));
721
33.2k
  }
722
479k
  if (duration.nanos() > 999999999 || duration.seconds() > max_seconds_value) {
723
13.3k
    return absl::OutOfRangeError(fmt::format("Duration out-of-range: {}", duration.DebugString()));
724
13.3k
  }
725
466k
  return absl::OkStatus();
726
479k
}
727
728
86.9k
void validateDuration(const ProtobufWkt::Duration& duration, int64_t max_seconds_value) {
729
86.9k
  const auto result = validateDurationNoThrow(duration, max_seconds_value);
730
86.9k
  if (!result.ok()) {
731
132
    throwExceptionOrPanic(DurationUtil::OutOfRangeException, std::string(result.message()));
732
132
  }
733
86.9k
}
734
735
5.65k
void validateDuration(const ProtobufWkt::Duration& duration) {
736
5.65k
  validateDuration(duration, Protobuf::util::TimeUtil::kDurationMaxSeconds);
737
5.65k
}
738
739
81.3k
void validateDurationAsMilliseconds(const ProtobufWkt::Duration& duration) {
740
  // Apply stricter max boundary to the `seconds` value to avoid overflow.
741
  // Note that protobuf internally converts to nanoseconds.
742
  // The kMaxInt64Nanoseconds = 9223372036, which is about 300 years.
743
81.3k
  constexpr int64_t kMaxInt64Nanoseconds =
744
81.3k
      std::numeric_limits<int64_t>::max() / (1000 * 1000 * 1000);
745
81.3k
  validateDuration(duration, kMaxInt64Nanoseconds);
746
81.3k
}
747
748
425k
absl::Status validateDurationAsMillisecondsNoThrow(const ProtobufWkt::Duration& duration) {
749
425k
  constexpr int64_t kMaxInt64Nanoseconds =
750
425k
      std::numeric_limits<int64_t>::max() / (1000 * 1000 * 1000);
751
425k
  return validateDurationNoThrow(duration, kMaxInt64Nanoseconds);
752
425k
}
753
754
} // namespace
755
756
81.3k
uint64_t DurationUtil::durationToMilliseconds(const ProtobufWkt::Duration& duration) {
757
81.3k
  validateDurationAsMilliseconds(duration);
758
81.3k
  return Protobuf::util::TimeUtil::DurationToMilliseconds(duration);
759
81.3k
}
760
761
absl::StatusOr<uint64_t>
762
425k
DurationUtil::durationToMillisecondsNoThrow(const ProtobufWkt::Duration& duration) {
763
425k
  const auto result = validateDurationAsMillisecondsNoThrow(duration);
764
425k
  if (!result.ok()) {
765
46.5k
    return result;
766
46.5k
  }
767
379k
  return Protobuf::util::TimeUtil::DurationToMilliseconds(duration);
768
425k
}
769
770
5.65k
uint64_t DurationUtil::durationToSeconds(const ProtobufWkt::Duration& duration) {
771
5.65k
  validateDuration(duration);
772
5.65k
  return Protobuf::util::TimeUtil::DurationToSeconds(duration);
773
5.65k
}
774
775
void TimestampUtil::systemClockToTimestamp(const SystemTime system_clock_time,
776
12.1k
                                           ProtobufWkt::Timestamp& timestamp) {
777
  // Converts to millisecond-precision Timestamp by explicitly casting to millisecond-precision
778
  // time_point.
779
12.1k
  timestamp.MergeFrom(Protobuf::util::TimeUtil::MillisecondsToTimestamp(
780
12.1k
      std::chrono::time_point_cast<std::chrono::milliseconds>(system_clock_time)
781
12.1k
          .time_since_epoch()
782
12.1k
          .count()));
783
12.1k
}
784
785
349k
absl::string_view TypeUtil::typeUrlToDescriptorFullName(absl::string_view type_url) {
786
349k
  const size_t pos = type_url.rfind('/');
787
349k
  if (pos != absl::string_view::npos) {
788
326k
    type_url = type_url.substr(pos + 1);
789
326k
  }
790
349k
  return type_url;
791
349k
}
792
793
1.74k
std::string TypeUtil::descriptorFullNameToTypeUrl(absl::string_view type) {
794
1.74k
  return "type.googleapis.com/" + std::string(type);
795
1.74k
}
796
797
4
void StructUtil::update(ProtobufWkt::Struct& obj, const ProtobufWkt::Struct& with) {
798
4
  auto& obj_fields = *obj.mutable_fields();
799
800
4
  for (const auto& [key, val] : with.fields()) {
801
1
    auto& obj_key = obj_fields[key];
802
803
    // If the types are different, the last one wins.
804
1
    const auto val_kind = val.kind_case();
805
1
    if (val_kind != obj_key.kind_case()) {
806
1
      obj_key = val;
807
1
      continue;
808
1
    }
809
810
    // Otherwise, the strategy depends on the value kind.
811
0
    switch (val.kind_case()) {
812
    // For scalars, the last one wins.
813
0
    case ProtobufWkt::Value::kNullValue:
814
0
    case ProtobufWkt::Value::kNumberValue:
815
0
    case ProtobufWkt::Value::kStringValue:
816
0
    case ProtobufWkt::Value::kBoolValue:
817
0
      obj_key = val;
818
0
      break;
819
    // If we got a structure, recursively update.
820
0
    case ProtobufWkt::Value::kStructValue:
821
0
      update(*obj_key.mutable_struct_value(), val.struct_value());
822
0
      break;
823
    // For lists, append the new values.
824
0
    case ProtobufWkt::Value::kListValue: {
825
0
      auto& obj_key_vec = *obj_key.mutable_list_value()->mutable_values();
826
0
      const auto& vals = val.list_value().values();
827
0
      obj_key_vec.MergeFrom(vals);
828
0
      break;
829
0
    }
830
0
    case ProtobufWkt::Value::KIND_NOT_SET:
831
0
      break;
832
0
    }
833
0
  }
834
4
}
835
836
} // namespace Envoy