Skip to content

Commit 479cd1d

Browse files
sbenzaquencopybara-github
authored andcommitted
Add a hard limit on the number of errors and warnings we generate.
This prevents pathological inputs from wasting too much time/space on error output. PiperOrigin-RevId: 952879265
1 parent 6a31b74 commit 479cd1d

5 files changed

Lines changed: 90 additions & 19 deletions

File tree

src/google/protobuf/descriptor.cc

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4356,7 +4356,6 @@ internal::DescriptorBuilder::DescriptorBuilder(
43564356
tables_(tables),
43574357
deferred_validation_(deferred_validation),
43584358
error_collector_(error_collector),
4359-
had_errors_(false),
43604359
possible_undeclared_dependency_(nullptr),
43614360
undefine_resolved_name_("") {}
43624361

@@ -4366,9 +4365,12 @@ PROTOBUF_NOINLINE void internal::DescriptorBuilder::AddError(
43664365
const absl::string_view element_name, const Message& descriptor,
43674366
DescriptorPool::ErrorCollector::ErrorLocation location,
43684367
absl::FunctionRef<std::string()> make_error) {
4368+
if (++error_count_ > kMaxNumErrors) {
4369+
return;
4370+
}
43694371
std::string error = make_error();
43704372
if (error_collector_ == nullptr) {
4371-
if (!had_errors_) {
4373+
if (!has_errors()) {
43724374
ABSL_LOG(ERROR) << "Invalid proto descriptor for file \"" << filename_
43734375
<< "\":";
43744376
}
@@ -4377,7 +4379,6 @@ PROTOBUF_NOINLINE void internal::DescriptorBuilder::AddError(
43774379
error_collector_->RecordError(filename_, element_name, &descriptor,
43784380
location, error);
43794381
}
4380-
had_errors_ = true;
43814382
}
43824383

43834384
PROTOBUF_NOINLINE void internal::DescriptorBuilder::AddError(
@@ -4426,6 +4427,9 @@ PROTOBUF_NOINLINE void internal::DescriptorBuilder::AddWarning(
44264427
const absl::string_view element_name, const Message& descriptor,
44274428
DescriptorPool::ErrorCollector::ErrorLocation location,
44284429
absl::FunctionRef<std::string()> make_error) {
4430+
if (++warning_count_ > kMaxNumErrors) {
4431+
return;
4432+
}
44294433
std::string error = make_error();
44304434
if (error_collector_ == nullptr) {
44314435
ABSL_LOG(WARNING) << filename_ << " " << element_name << ": " << error;
@@ -4845,7 +4849,7 @@ bool internal::DescriptorBuilder::AddSymbol(const absl::string_view full_name,
48454849
if (!file_tables_->AddAliasUnderParent(parent, name, symbol)) {
48464850
// This is only possible if there was already an error adding something of
48474851
// the same name.
4848-
if (!had_errors_) {
4852+
if (!has_errors()) {
48494853
ABSL_DLOG(FATAL) << "\"" << full_name
48504854
<< "\" not previously defined in "
48514855
"symbols_by_name_, but was defined in "
@@ -5785,7 +5789,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
57855789
// Interpret only the non-extension options first, including features and
57865790
// their extensions. This has to be done in two passes, since option
57875791
// extensions defined in this file may have features attached to them.
5788-
if (!had_errors_) {
5792+
if (!has_errors()) {
57895793
OptionInterpreter option_interpreter(this);
57905794
for (std::vector<OptionsToInterpret>::iterator iter =
57915795
options_to_interpret_.begin();
@@ -5837,7 +5841,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
58375841

58385842
// Validate options. See comments at InternalSetLazilyBuildDependencies about
58395843
// error checking and lazy import building.
5840-
if (!had_errors_ && !pool_->lazily_build_dependencies_) {
5844+
if (!has_errors() && !pool_->lazily_build_dependencies_) {
58415845
internal::VisitDescriptors(
58425846
*result, proto, [&](const auto& descriptor, const auto& desc_proto) {
58435847
ValidateOptions(&descriptor, desc_proto);
@@ -5846,7 +5850,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
58465850

58475851
// Additional naming conflict check for map entry types. Only need to check
58485852
// this if there are already errors.
5849-
if (had_errors_) {
5853+
if (has_errors()) {
58505854
for (int i = 0; i < proto.message_type_size(); ++i) {
58515855
DetectMapConflicts(result->message_type(i), proto.message_type(i));
58525856
}
@@ -5856,14 +5860,14 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
58565860
// Again, see comments at InternalSetLazilyBuildDependencies about error
58575861
// checking. Also, don't log unused dependencies if there were previous
58585862
// errors, since the results might be inaccurate.
5859-
if (!had_errors_ && !unused_dependency_.empty() &&
5863+
if (!has_errors() && !unused_dependency_.empty() &&
58605864
!pool_->lazily_build_dependencies_) {
58615865
LogUnusedDependency(proto, result);
58625866
}
58635867

58645868
// Store feature information for deferred validation outside of the database
58655869
// mutex.
5866-
if (!had_errors_ && !pool_->lazily_build_dependencies_) {
5870+
if (!has_errors() && !pool_->lazily_build_dependencies_) {
58675871
internal::VisitDescriptors(
58685872
*result, proto, [&](const auto& descriptor, const auto& desc_proto) {
58695873
if (!IsDefaultInstance(*descriptor.proto_features_)) {
@@ -5880,7 +5884,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
58805884
});
58815885
}
58825886

5883-
if (!had_errors_ && pool_->enforce_naming_style_) {
5887+
if (!has_errors() && pool_->enforce_naming_style_) {
58845888
internal::VisitDescriptors(
58855889
*result, proto, [&](const auto& descriptor, const auto& desc_proto) {
58865890
if (IsStyleOrGreater(&descriptor, FeatureSet::STYLE2024)) {
@@ -5889,7 +5893,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
58895893
});
58905894
}
58915895

5892-
if (!had_errors_ && pool_->enforce_proto_limits_) {
5896+
if (!has_errors() && pool_->enforce_proto_limits_) {
58935897
internal::VisitDescriptors(
58945898
*result, proto, [&](const auto& descriptor, const auto& desc_proto) {
58955899
if (internal::InternalFeatureHelper::GetFeatures(descriptor)
@@ -5899,7 +5903,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
58995903
}
59005904
});
59015905
}
5902-
if (!had_errors_ && pool_->enforce_symbol_visibility_) {
5906+
if (!has_errors() && pool_->enforce_symbol_visibility_) {
59035907
SymbolChecker symbol_checker(result, proto);
59045908
// Check Symbol Visibility and future co-location Rules.
59055909
auto errors = symbol_checker.CheckSymbolVisibilityRules();
@@ -5912,7 +5916,7 @@ FileDescriptor* internal::DescriptorBuilder::BuildFileImpl(
59125916
}
59135917
}
59145918

5915-
if (had_errors_) {
5919+
if (has_errors()) {
59165920
return nullptr;
59175921
} else {
59185922
return result;
@@ -7057,7 +7061,7 @@ void internal::DescriptorBuilder::CrossLinkMessage(
70577061
out_oneof_decl.fields_ = message->field(i);
70587062
}
70597063

7060-
if (!had_errors_) {
7064+
if (!has_errors()) {
70617065
// Verify that they are contiguous.
70627066
// This is assumed by OneofDescriptor::field(i).
70637067
// But only if there are no errors.
@@ -7118,13 +7122,13 @@ void internal::DescriptorBuilder::CrossLinkMessage(
71187122
void internal::DescriptorBuilder::CheckExtensionDeclarationFieldType(
71197123
const FieldDescriptor& field, const FieldDescriptorProto& proto,
71207124
absl::string_view type) {
7121-
if (had_errors_) return;
7125+
if (has_errors()) return;
71227126
std::string actual_type(field.type_name());
71237127
std::string expected_type(type);
71247128
if (field.message_type() || field.enum_type()) {
71257129
// Field message type descriptor can be in a partial state which will cause
71267130
// segmentation fault if it is being accessed.
7127-
if (had_errors_) return;
7131+
if (has_errors()) return;
71287132
absl::string_view full_name = field.message_type() != nullptr
71297133
? field.message_type()->full_name()
71307134
: field.enum_type()->full_name();

src/google/protobuf/descriptor_builder.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ class DescriptorBuilder {
9191
const FileDescriptor* BuildFile(const FileDescriptorProto& proto);
9292

9393
private:
94+
static constexpr size_t kMaxNumErrors = 1000;
95+
9496
DescriptorBuilder(const DescriptorPool* pool, DescriptorPool::Tables* tables,
9597
DescriptorPool::DeferredValidation& deferred_validation,
9698
DescriptorPool::ErrorCollector* error_collector);
@@ -101,6 +103,8 @@ class DescriptorBuilder {
101103
FileDescriptor* BuildFileImpl(const FileDescriptorProto& proto,
102104
internal::FlatAllocator& alloc);
103105

106+
bool has_errors() const { return error_count_ != 0; }
107+
104108
const DescriptorPool* pool_;
105109
DescriptorPool::Tables* tables_; // for convenience
106110
DescriptorPool::DeferredValidation& deferred_validation_;
@@ -113,7 +117,8 @@ class DescriptorBuilder {
113117
// can later (after cross-linking) interpret those options.
114118
std::vector<OptionsToInterpret> options_to_interpret_;
115119

116-
bool had_errors_;
120+
size_t error_count_ = 0;
121+
size_t warning_count_ = 0;
117122
std::string filename_;
118123
FileDescriptor* file_;
119124
FileDescriptorTables* file_tables_ = nullptr;

src/google/protobuf/descriptor_test_utils.cc

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ void MockErrorCollector::RecordError(absl::string_view filename,
4242
const Message* descriptor,
4343
ErrorLocation location,
4444
absl::string_view message) {
45-
absl::SubstituteAndAppend(&text_, "$0: $1: $2: $3\n", filename, element_name,
45+
std::string& e = text_lines_.emplace_back();
46+
absl::SubstituteAndAppend(&e, "$0: $1: $2: $3", filename, element_name,
4647
ErrorLocationName(location), message);
48+
absl::StrAppend(&text_, e, "\n");
4749
}
4850

4951
// implements ErrorCollector ---------------------------------------
@@ -52,8 +54,10 @@ void MockErrorCollector::RecordWarning(absl::string_view filename,
5254
const Message* descriptor,
5355
ErrorLocation location,
5456
absl::string_view message) {
55-
absl::SubstituteAndAppend(&warning_text_, "$0: $1: $2: $3\n", filename,
57+
std::string& w = warn_lines_.emplace_back();
58+
absl::SubstituteAndAppend(&warning_text_, "$0: $1: $2: $3", filename,
5659
element_name, ErrorLocationName(location), message);
60+
absl::StrAppend(&warning_text_, w, "\n");
5761
}
5862

5963
// ===================================================================
@@ -115,6 +119,16 @@ void ValidationErrorTest::BuildFileWithErrors(
115119
BuildFileWithErrors(file_proto, expected_errors);
116120
}
117121

122+
void ValidationErrorTest::BuildFileWithErrorList(
123+
const FileDescriptorProto& file_proto,
124+
testing::Matcher<std::vector<std::string>> expected_errors,
125+
testing::Matcher<std::vector<std::string>> expected_warnings) {
126+
MockErrorCollector error_collector;
127+
pool_.BuildFileCollectingErrors(file_proto, &error_collector);
128+
EXPECT_THAT(error_collector.text_lines_, expected_errors);
129+
EXPECT_THAT(error_collector.warn_lines_, expected_warnings);
130+
}
131+
118132
// Parse a proto file and build it. Expect errors to be produced which match
119133
// the given error text.
120134
void ValidationErrorTest::ParseAndBuildFileWithErrors(

src/google/protobuf/descriptor_test_utils.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class MockErrorCollector : public DescriptorPool::ErrorCollector {
3535

3636
std::string text_;
3737
std::string warning_text_;
38+
std::vector<std::string> text_lines_;
39+
std::vector<std::string> warn_lines_;
3840

3941
// implements ErrorCollector ---------------------------------------
4042
void RecordError(absl::string_view filename, absl::string_view element_name,
@@ -87,6 +89,13 @@ class ValidationErrorTest : public testing::Test {
8789
void BuildFileWithErrors(const std::string& file_text,
8890
testing::Matcher<std::string> expected_errors);
8991

92+
// As above, but with separate errors
93+
void BuildFileWithErrorList(
94+
const FileDescriptorProto& file_proto,
95+
testing::Matcher<std::vector<std::string>> expected_errors,
96+
testing::Matcher<std::vector<std::string>> expected_warnings =
97+
testing::_);
98+
9099
// Parse a proto file and build it. Expect errors to be produced which match
91100
// the given error text.
92101
void ParseAndBuildFileWithErrors(absl::string_view file_name,

src/google/protobuf/descriptor_unittest.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,14 @@ using ::google::protobuf::internal::cpp::HasbitMode;
9292
using ::google::protobuf::internal::cpp::HasHasbitWithoutProfile;
9393
using ::google::protobuf::internal::cpp::HasPreservingUnknownEnumSemantics;
9494
using ::google::protobuf::internal::cpp::Utf8CheckMode;
95+
using ::testing::_;
9596
using ::testing::AnyOf;
9697
using ::testing::AtLeast;
9798
using ::testing::ElementsAre;
9899
using ::testing::HasSubstr;
99100
using ::testing::NotNull;
100101
using ::testing::Return;
102+
using ::testing::SizeIs;
101103

102104
absl::Status GetStatus(const absl::Status& s) { return s; }
103105
template <typename T>
@@ -5544,6 +5546,43 @@ TEST_F(ValidationErrorTest, ReservedRangeOverlap) {
55445546
" overlaps with already-defined range 10 to 19.\n");
55455547
}
55465548

5549+
TEST_F(ValidationErrorTest, LimitNumberOfErrors) {
5550+
FileDescriptorProto file;
5551+
file.set_name("foo.proto");
5552+
auto* m = file.add_message_type();
5553+
m->set_name("Foo");
5554+
// This would generate O(N^2) errors
5555+
for (int i = 0; i < 100; ++i) {
5556+
auto* r = m->add_reserved_range();
5557+
r->set_start(100);
5558+
r->set_end(200);
5559+
}
5560+
// DescriptorBuilder::kMaxNumErrors
5561+
BuildFileWithErrorList(file, SizeIs(1000));
5562+
}
5563+
5564+
TEST_F(ValidationErrorTest, LimitNumberOfWarnings) {
5565+
constexpr int N = 1100;
5566+
// Create N deps.
5567+
for (int i = 0; i < N; ++i) {
5568+
FileDescriptorProto file;
5569+
file.set_name(absl::StrCat("dep", i, ".proto"));
5570+
ASSERT_TRUE(pool_.BuildFile(file));
5571+
}
5572+
5573+
FileDescriptorProto file;
5574+
file.set_name("foo.proto");
5575+
5576+
// This generates one warning per dep.
5577+
for (int i = 0; i < N; ++i) {
5578+
file.add_dependency(absl::StrCat("dep", i, ".proto"));
5579+
}
5580+
5581+
pool_.AddDirectInputFile(file.name());
5582+
// DescriptorBuilder::kMaxNumErrors
5583+
BuildFileWithErrorList(file, _, SizeIs(1000));
5584+
}
5585+
55475586
TEST_F(ValidationErrorTest, ReservedNameError) {
55485587
BuildFileWithErrors(
55495588
"name: \"foo.proto\" "

0 commit comments

Comments
 (0)