From 0660fb18036e3a5e953f267553667d0ac76e12e6 Mon Sep 17 00:00:00 2001 From: Nayana Naik Date: Tue, 13 Oct 2026 13:11:01 +0530 Subject: [PATCH 1/2] MINOR: [C++][Engine] Validate Substrait emit output_mapping indices --- cpp/src/arrow/engine/substrait/relation_internal.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cpp/src/arrow/engine/substrait/relation_internal.cc b/cpp/src/arrow/engine/substrait/relation_internal.cc index d8d77fa800cd..55ca899680e9 100644 --- a/cpp/src/arrow/engine/substrait/relation_internal.cc +++ b/cpp/src/arrow/engine/substrait/relation_internal.cc @@ -85,6 +85,9 @@ Result GetEmitInfo(const RelMessage& rel, FieldVector emit_fields(emit_size); for (int i = 0; i < emit_size; i++) { int32_t map_id = emit.output_mapping(i); + if (map_id < 0 || map_id >= input_schema->num_fields()) { + return Status::Invalid("Out of bounds emit index ", map_id); + } proj_field_refs[i] = compute::field_ref(FieldRef(map_id)); emit_fields[i] = input_schema->field(map_id); } @@ -111,6 +114,9 @@ Result ProcessEmitProject( std::vector emit_proj_exprs(emit_size); for (int i = 0; i < emit_size; i++) { int32_t map_id = emit.output_mapping(i); + if (map_id < 0 || map_id >= input_schema->num_fields()) { + return Status::Invalid("Out of bounds emit index ", map_id); + } emit_fields[i] = input_schema->field(map_id); emit_proj_exprs[i] = std::move(proj_options.expressions[map_id]); } From cb287e84d5610caae4286698d4d6688ef2da20cc Mon Sep 17 00:00:00 2001 From: Nayana Naik Date: Sat, 17 Oct 2026 15:21:31 +0530 Subject: [PATCH 2/2] GH-54025: [C++][Engine] Test out-of-bounds Substrait emit indices --- cpp/src/arrow/engine/substrait/serde_test.cc | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/cpp/src/arrow/engine/substrait/serde_test.cc b/cpp/src/arrow/engine/substrait/serde_test.cc index 311876379830..a7af64db51d7 100644 --- a/cpp/src/arrow/engine/substrait/serde_test.cc +++ b/cpp/src/arrow/engine/substrait/serde_test.cc @@ -3147,6 +3147,77 @@ TEST(SubstraitRoundTrip, ReadRelWithEmit) { /*include_columns=*/{}, conversion_options); } +void CheckInvalidEmitIndex(const std::string& rel_json) { + std::string substrait_json = R"({ + "version": { "major_number": 9999, "minor_number": 9999, "patch_number": 9999 }, + "relations": [{ + "rel": )" + rel_json + R"( + }] + })"; + + ASSERT_OK_AND_ASSIGN(auto buf, + internal::SubstraitFromJSON("Plan", substrait_json, + /*ignore_unknown_fields=*/false)); + auto dummy_schema = + schema({field("A", int32()), field("B", int32()), field("C", int32())}); + ConversionOptions conversion_options; + conversion_options.named_table_provider = + AlwaysProvideSameTable(TableFromJSON(dummy_schema, {"[[1, 2, 3]]"})); + std::shared_ptr sp_ext_id_reg = MakeExtensionIdRegistry(); + ExtensionIdRegistry* ext_id_reg = sp_ext_id_reg.get(); + ExtensionSet ext_set(ext_id_reg); + EXPECT_THAT( + DeserializePlans( + *buf, [] { return kNullConsumer; }, ext_id_reg, &ext_set, conversion_options), + Raises(StatusCode::Invalid, HasSubstr("Out of bounds emit index"))); +} + +constexpr std::string_view kEmitTestReadRel = R"({ + "read": { + "base_schema": { + "names": ["A", "B", "C"], + "struct": {"types": [{"i32": {}}, {"i32": {}}, {"i32": {}}]} + }, + "namedTable": {"names": ["A"]} + } +})"; + +TEST(Substrait, ReadRelWithOutOfBoundsEmit) { + for (const char* mapping : {"[0, 3]", "[-1, 1]"}) { + ARROW_SCOPED_TRACE("outputMapping: ", mapping); + CheckInvalidEmitIndex(R"({ + "read": { + "common": {"emit": {"outputMapping": )" + + std::string(mapping) + R"(}}, + "base_schema": { + "names": ["A", "B", "C"], + "struct": {"types": [{"i32": {}}, {"i32": {}}, {"i32": {}}]} + }, + "namedTable": {"names": ["A"]} + } + })"); + } +} + +TEST(Substrait, ProjectRelWithOutOfBoundsEmit) { + // The project emits the 3 input fields plus 1 expression, so 4 is out of range + for (const char* mapping : {"[0, 4]", "[-1, 3]"}) { + ARROW_SCOPED_TRACE("outputMapping: ", mapping); + CheckInvalidEmitIndex(R"({ + "project": { + "common": {"emit": {"outputMapping": )" + + std::string(mapping) + R"(}}, + "expressions": [ + {"selection": {"directReference": {"structField": {"field": 0}}, + "rootReference": {}}} + ], + "input": )" + std::string(kEmitTestReadRel) + + R"( + } + })"); + } +} + TEST(SubstraitRoundTrip, FilterRelWithEmit) { auto dummy_schema = schema({field("A", int32()), field("B", int32()), field("C", int32()), field("D", int32())});