Fix risk of upb itof corruption when legacy json_name conflicts are allowed. This addresses a risk as identified by security researcher https://github.com/sphl. Since it is only exposed on untrusted descriptors this is outside of our CVE disclosure model, but we do intend to harden these cases as defense in depth. PiperOrigin-RevId: 998732157
diff --git a/upb/reflection/message_def.c b/upb/reflection/message_def.c index c737d2b..433f4f3 100644 --- a/upb/reflection/message_def.c +++ b/upb/reflection/message_def.c
@@ -406,10 +406,7 @@ const char* shortname = upb_FieldDef_Name(f); const size_t shortnamelen = strlen(shortname); - upb_value v = upb_value_constptr(f); - - upb_value existing_v; - if (upb_strtable_lookup(&m->ntof, shortname, &existing_v)) { + if (upb_strtable_lookup(&m->ntof, shortname, NULL)) { _upb_DefBuilder_Errf(ctx, "duplicate field name (%s)", shortname); } @@ -424,13 +421,13 @@ if (!skip_json_conflicts && strcmp(shortname, json_name) != 0 && google_protobuf_FeatureSet_json_format(m->resolved_features) == google_protobuf_FeatureSet_ALLOW && - upb_strtable_lookup(&m->ntof, json_name, &v)) { + upb_strtable_lookup(&m->ntof, json_name, NULL)) { _upb_DefBuilder_Errf( ctx, "duplicate json_name for (%s) with original field name (%s)", shortname, json_name); } - if (upb_strtable_lookup(&m->jtof, json_name, &v)) { + if (upb_strtable_lookup(&m->jtof, json_name, NULL)) { if (!skip_json_conflicts) { _upb_DefBuilder_Errf(ctx, "duplicate json_name (%s)", json_name); } @@ -445,7 +442,8 @@ _upb_DefBuilder_Errf(ctx, "duplicate field number (%u)", field_number); } - ok = upb_inttable_insert(&m->itof, field_number, v, ctx->arena); + ok = upb_inttable_insert(&m->itof, field_number, upb_value_constptr(f), + ctx->arena); if (!ok) _upb_DefBuilder_OomErr(ctx); }
diff --git a/upb/reflection/reflection_test.cc b/upb/reflection/reflection_test.cc index 635df53..50a387d 100644 --- a/upb/reflection/reflection_test.cc +++ b/upb/reflection/reflection_test.cc
@@ -498,6 +498,77 @@ EXPECT_TRUE(status.ok()) << status.message(); } +TEST(ReflectionTest, LegacyJsonFieldConflictsKeepsCorrectNumberMapping) { + // With deprecated_legacy_json_field_conflicts, two fields may share a + // json_name. Each field number must still map to its own FieldDef. + upb::DefPool pool = + LoadDescriptorProto( + R"pb( + syntax: "proto2" + name: "F" + message_type { name: "Sub" } + message_type { + name: "M" + options { deprecated_legacy_json_field_conflicts: true } + field { + name: "a" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".Sub" + json_name: "j" + } + field { + name: "b" + number: 2 + label: LABEL_OPTIONAL + type: TYPE_FIXED64 + json_name: "j" + } + } + )pb") + .value(); + upb::MessageDefPtr m = pool.FindMessageByName("M"); + ASSERT_TRUE(m); + upb::FieldDefPtr a = m.FindFieldByNumber(1); + upb::FieldDefPtr b = m.FindFieldByNumber(2); + ASSERT_TRUE(a); + ASSERT_TRUE(b); + EXPECT_STREQ(a.name(), "a"); + EXPECT_EQ(a.number(), 1); + EXPECT_STREQ(b.name(), "b"); + EXPECT_EQ(b.number(), 2); + EXPECT_EQ(b.type(), kUpb_FieldType_Fixed64); +} + +TEST(ReflectionTest, JsonFieldConflictsRejectedWithoutLegacyOption) { + absl::Status status = LoadDescriptorProto( + R"pb( + syntax: "proto2" + name: "F" + message_type { + name: "M" + field { + name: "a" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_FIXED64 + json_name: "j" + } + field { + name: "b" + number: 2 + label: LABEL_OPTIONAL + type: TYPE_FIXED64 + json_name: "j" + } + } + )pb") + .status(); + EXPECT_THAT(std::string(status.message()), + HasSubstr("duplicate json_name (j)")); +} + TEST(ReflectionTest, DefPoolFindMethodsRespectSize) { upb::DefPool pool = LoadDescriptorProto(R"pb( syntax: "proto2"