From daa5f73ebdf48143441045015d03337f1cfdf196 Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Sat, 31 Jan 2026 22:21:26 +0500 Subject: [PATCH 01/14] [C++] Fix Segfault in SparseCSFIndex::Equals with mismatched dimensions --- cpp/src/arrow/sparse_tensor.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index b84070b3d288..fb33ec6ead98 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -405,6 +405,9 @@ SparseCSFIndex::SparseCSFIndex(const std::vector>& indpt std::string SparseCSFIndex::ToString() const { return std::string("SparseCSFIndex"); } bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { + if (indices().size() != other.indices().size() || indptr().size() != other.indptr().size()) { + return false; + } for (int64_t i = 0; i < static_cast(indices().size()); ++i) { if (!indices()[i]->Equals(*other.indices()[i])) return false; } From 3e1cbd6735985152b2e4aca214c03ae71192695f Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Sun, 1 Feb 2026 18:39:12 +0500 Subject: [PATCH 02/14] [C++] Add regression test for SparseCSFIndex::Equals segfault --- cpp/src/arrow/sparse_tensor_test.cc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/cpp/src/arrow/sparse_tensor_test.cc b/cpp/src/arrow/sparse_tensor_test.cc index c9c28a11b1b3..0502c412ab77 100644 --- a/cpp/src/arrow/sparse_tensor_test.cc +++ b/cpp/src/arrow/sparse_tensor_test.cc @@ -1676,4 +1676,29 @@ TEST(TestSparseCSFMatrixForUInt64Index, Make) { ASSERT_RAISES(Invalid, SparseCSFTensor::Make(dense_tensor, uint64())); } +TEST(TestSparseCSFIndex, EqualsMismatchedDimensions) { + std::vector axis_order_4d = {0, 1, 2, 3}; + std::vector axis_order_2d = {0, 1}; + + // Create empty tensors for indptr/indices + auto empty_buffer = std::make_shared(nullptr, 0); + auto tensor_4d = std::make_shared(int32(), empty_buffer, std::vector{0}); + auto tensor_2d = std::make_shared(int32(), empty_buffer, std::vector{0}); + + // Create a 4D index + std::vector> indptr_4d = {tensor_4d, tensor_4d, tensor_4d}; + std::vector> indices_4d = {tensor_4d, tensor_4d, tensor_4d, tensor_4d}; + auto si_4d = std::make_shared(indptr_4d, indices_4d, axis_order_4d); + + // Create a 2D index + std::vector> indptr_2d = {tensor_2d}; + std::vector> indices_2d = {tensor_2d, tensor_2d}; + auto si_2d = std::make_shared(indptr_2d, indices_2d, axis_order_2d); + + // Before fix: This would segfault. + // After fix: Returns false safely. + ASSERT_FALSE(si_4d->Equals(*si_2d)); + ASSERT_FALSE(si_2d->Equals(*si_4d)); +} + } // namespace arrow From fdf506e57397aaa089d1f79f4409ff330949ceb1 Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Sun, 1 Feb 2026 21:24:07 +0500 Subject: [PATCH 03/14] Fix Segfault in SparseCSFIndex::Equals with mismatched dimensions and add test --- cpp/src/arrow/sparse_tensor.cc | 6 +++--- cpp/src/arrow/sparse_tensor_test.cc | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index fb33ec6ead98..5987c51999e4 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -405,9 +405,9 @@ SparseCSFIndex::SparseCSFIndex(const std::vector>& indpt std::string SparseCSFIndex::ToString() const { return std::string("SparseCSFIndex"); } bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { - if (indices().size() != other.indices().size() || indptr().size() != other.indptr().size()) { - return false; - } + if (indices().size() != other.indices().size()) return false; + if (indptr().size() != other.indptr().size()) return false; + if (axis_order().size() != other.axis_order().size()) return false; for (int64_t i = 0; i < static_cast(indices().size()); ++i) { if (!indices()[i]->Equals(*other.indices()[i])) return false; } diff --git a/cpp/src/arrow/sparse_tensor_test.cc b/cpp/src/arrow/sparse_tensor_test.cc index 0502c412ab77..87f07b9a7a81 100644 --- a/cpp/src/arrow/sparse_tensor_test.cc +++ b/cpp/src/arrow/sparse_tensor_test.cc @@ -1641,10 +1641,29 @@ TYPED_TEST_P(TestSparseCSFTensorForIndexValueType, TestNonAscendingShape) { ASSERT_TRUE(st->Equals(*sparse_tensor)); } +TYPED_TEST_P(TestSparseCSFTensorForIndexValueType, TestEqualityMismatchedDimensions) { + using IndexValueType = TypeParam; + using c_index_value_type = typename IndexValueType::c_type; + + // 1D (trivial) vs 2D + std::vector axis_order_1D = {0}; + std::vector> indptr_1D = {{0, 1}}; + std::vector> indices_1D = {{0}}; + auto si_1D = this->MakeSparseCSFIndex(axis_order_1D, indptr_1D, indices_1D); + + std::vector axis_order_2D = {0, 1}; + std::vector> indptr_2D = {{0, 1}}; + std::vector> indices_2D = {{0}, {0}}; + auto si_2D = this->MakeSparseCSFIndex(axis_order_2D, indptr_2D, indices_2D); + + ASSERT_FALSE(si_1D->Equals(*si_2D)); + ASSERT_FALSE(si_2D->Equals(*si_1D)); +} + REGISTER_TYPED_TEST_SUITE_P(TestSparseCSFTensorForIndexValueType, TestCreateSparseTensor, TestTensorToSparseTensor, TestSparseTensorToTensor, TestAlternativeAxisOrder, TestNonAscendingShape, - TestRoundTrip); + TestRoundTrip, TestEqualityMismatchedDimensions); INSTANTIATE_TYPED_TEST_SUITE_P(TestInt8, TestSparseCSFTensorForIndexValueType, Int8Type); INSTANTIATE_TYPED_TEST_SUITE_P(TestUInt8, TestSparseCSFTensorForIndexValueType, From c87afdb3bf68a88c14b24d9f573d0b80f91d73a9 Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Mon, 2 Feb 2026 19:36:05 +0500 Subject: [PATCH 04/14] Fix lint: Format SparseCSFIndex::Equals to comply with 90-char line limit and style guide --- cpp/src/arrow/sparse_tensor.cc | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index 5987c51999e4..acb531435b0d 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -405,14 +405,25 @@ SparseCSFIndex::SparseCSFIndex(const std::vector>& indpt std::string SparseCSFIndex::ToString() const { return std::string("SparseCSFIndex"); } bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { - if (indices().size() != other.indices().size()) return false; - if (indptr().size() != other.indptr().size()) return false; - if (axis_order().size() != other.axis_order().size()) return false; + if (indices().size() != other.indices().size()) { + return false; + } + if (indptr().size() != other.indptr().size()) { + return false; + } + if (axis_order().size() != other.axis_order().size()) { + return false; + } + for (int64_t i = 0; i < static_cast(indices().size()); ++i) { - if (!indices()[i]->Equals(*other.indices()[i])) return false; + if (!indices()[i]->Equals(*other.indices()[i])) { + return false; + } } for (int64_t i = 0; i < static_cast(indptr().size()); ++i) { - if (!indptr()[i]->Equals(*other.indptr()[i])) return false; + if (!indptr()[i]->Equals(*other.indptr()[i])) { + return false; + } } return axis_order() == other.axis_order(); } From 8dbc21bd21e12a257048342d7bc0c568db0c13ac Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Mon, 2 Feb 2026 20:35:17 +0500 Subject: [PATCH 05/14] Fix test: Use valid CSF index structures (2D vs 3D instead of invalid 1D) --- cpp/src/arrow/sparse_tensor_test.cc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor_test.cc b/cpp/src/arrow/sparse_tensor_test.cc index 87f07b9a7a81..a0ad171c7456 100644 --- a/cpp/src/arrow/sparse_tensor_test.cc +++ b/cpp/src/arrow/sparse_tensor_test.cc @@ -1645,19 +1645,21 @@ TYPED_TEST_P(TestSparseCSFTensorForIndexValueType, TestEqualityMismatchedDimensi using IndexValueType = TypeParam; using c_index_value_type = typename IndexValueType::c_type; - // 1D (trivial) vs 2D - std::vector axis_order_1D = {0}; - std::vector> indptr_1D = {{0, 1}}; - std::vector> indices_1D = {{0}}; - auto si_1D = this->MakeSparseCSFIndex(axis_order_1D, indptr_1D, indices_1D); - + // 2D vs 3D - comparing indices with different dimensionality + // 2D CSF: ndim=2, so indptr.size()=1, indices.size()=2 std::vector axis_order_2D = {0, 1}; std::vector> indptr_2D = {{0, 1}}; std::vector> indices_2D = {{0}, {0}}; auto si_2D = this->MakeSparseCSFIndex(axis_order_2D, indptr_2D, indices_2D); - ASSERT_FALSE(si_1D->Equals(*si_2D)); - ASSERT_FALSE(si_2D->Equals(*si_1D)); + // 3D CSF: ndim=3, so indptr.size()=2, indices.size()=3 + std::vector axis_order_3D = {0, 1, 2}; + std::vector> indptr_3D = {{0, 1}, {0, 1}}; + std::vector> indices_3D = {{0}, {0}, {0}}; + auto si_3D = this->MakeSparseCSFIndex(axis_order_3D, indptr_3D, indices_3D); + + ASSERT_FALSE(si_2D->Equals(*si_3D)); + ASSERT_FALSE(si_3D->Equals(*si_2D)); } REGISTER_TYPED_TEST_SUITE_P(TestSparseCSFTensorForIndexValueType, TestCreateSparseTensor, From 900b98d43cc8d5978bc4aabde2baf13d8c332ec5 Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Mon, 2 Feb 2026 20:49:15 +0500 Subject: [PATCH 06/14] Remove invalid test causing segfaults with empty tensors The TEST(TestSparseCSFIndex, EqualsMismatchedDimensions) test created SparseCSFIndex objects with empty tensors (nullptr buffers, 0-length shape), causing segfaults during validation on ASAN/UBSAN and 'front() called on empty vector' errors on MSVC. The typed test TestEqualityMismatchedDimensions already properly validates the fix with valid CSF index structures. --- cpp/src/arrow/sparse_tensor_test.cc | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor_test.cc b/cpp/src/arrow/sparse_tensor_test.cc index a0ad171c7456..062d5fceebd5 100644 --- a/cpp/src/arrow/sparse_tensor_test.cc +++ b/cpp/src/arrow/sparse_tensor_test.cc @@ -1697,29 +1697,4 @@ TEST(TestSparseCSFMatrixForUInt64Index, Make) { ASSERT_RAISES(Invalid, SparseCSFTensor::Make(dense_tensor, uint64())); } -TEST(TestSparseCSFIndex, EqualsMismatchedDimensions) { - std::vector axis_order_4d = {0, 1, 2, 3}; - std::vector axis_order_2d = {0, 1}; - - // Create empty tensors for indptr/indices - auto empty_buffer = std::make_shared(nullptr, 0); - auto tensor_4d = std::make_shared(int32(), empty_buffer, std::vector{0}); - auto tensor_2d = std::make_shared(int32(), empty_buffer, std::vector{0}); - - // Create a 4D index - std::vector> indptr_4d = {tensor_4d, tensor_4d, tensor_4d}; - std::vector> indices_4d = {tensor_4d, tensor_4d, tensor_4d, tensor_4d}; - auto si_4d = std::make_shared(indptr_4d, indices_4d, axis_order_4d); - - // Create a 2D index - std::vector> indptr_2d = {tensor_2d}; - std::vector> indices_2d = {tensor_2d, tensor_2d}; - auto si_2d = std::make_shared(indptr_2d, indices_2d, axis_order_2d); - - // Before fix: This would segfault. - // After fix: Returns false safely. - ASSERT_FALSE(si_4d->Equals(*si_2d)); - ASSERT_FALSE(si_2d->Equals(*si_4d)); -} - } // namespace arrow From 76fe33892b713edbd1915117ae06bf91fd9c8a43 Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Tue, 3 Feb 2026 23:58:42 +0500 Subject: [PATCH 07/14] Revert formatting changes per maintainer feedback Keep only essential size checks. Maintainers requested reverting formatting changes to reduce diff noise and improve readability. --- cpp/src/arrow/sparse_tensor.cc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index acb531435b0d..cdbb4be7e20d 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -416,14 +416,10 @@ bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { } for (int64_t i = 0; i < static_cast(indices().size()); ++i) { - if (!indices()[i]->Equals(*other.indices()[i])) { - return false; - } + if (!indices()[i]->Equals(*other.indices()[i])) return false; } for (int64_t i = 0; i < static_cast(indptr().size()); ++i) { - if (!indptr()[i]->Equals(*other.indptr()[i])) { - return false; - } + if (!indptr()[i]->Equals(*other.indptr()[i])) return false; } return axis_order() == other.axis_order(); } From a6e50883f8f3a94338f62da3dfee44cda952326b Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Wed, 4 Feb 2026 00:11:24 +0500 Subject: [PATCH 08/14] Remove redundant axis_order size check per review feedback The axis_order().size() check was unnecessary because vector equality operator already compares sizes. Keeping only the essential checks for indices() and indptr() that prevent segfault from out-of-bounds access. --- cpp/src/arrow/sparse_tensor.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index cdbb4be7e20d..ac27b80b2ae0 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -411,9 +411,6 @@ bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { if (indptr().size() != other.indptr().size()) { return false; } - if (axis_order().size() != other.axis_order().size()) { - return false; - } for (int64_t i = 0; i < static_cast(indices().size()); ++i) { if (!indices()[i]->Equals(*other.indices()[i])) return false; From f9bc643128d43f79f713d289f9c3575722182c66 Mon Sep 17 00:00:00 2001 From: Ali Mahmood Rana <159713825+AliRana30@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:57:52 +0500 Subject: [PATCH 09/14] Update cpp/src/arrow/sparse_tensor.cc Co-authored-by: Rok Mihevc --- cpp/src/arrow/sparse_tensor.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index ac27b80b2ae0..9af922c4b41a 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -419,6 +419,11 @@ bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { if (!indptr()[i]->Equals(*other.indptr()[i])) return false; } return axis_order() == other.axis_order(); +bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { + auto eq = [](const auto& a, const auto& b) { return a->Equals(*b); }; + return axis_order() == other.axis_order() + && std::ranges::equal(indices(), other.indices(), eq) + && std::ranges::equal(indptr(), other.indptr(), eq); } // ---------------------------------------------------------------------- From c1e33a27d4ad7802ca90dda35304e2a4689c4372 Mon Sep 17 00:00:00 2001 From: Ali Mahmood Rana <159713825+AliRana30@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:12:30 +0500 Subject: [PATCH 10/14] Update cpp/src/arrow/sparse_tensor_test.cc Co-authored-by: Rok Mihevc --- cpp/src/arrow/sparse_tensor_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/arrow/sparse_tensor_test.cc b/cpp/src/arrow/sparse_tensor_test.cc index 062d5fceebd5..434f4a1723c7 100644 --- a/cpp/src/arrow/sparse_tensor_test.cc +++ b/cpp/src/arrow/sparse_tensor_test.cc @@ -1660,6 +1660,7 @@ TYPED_TEST_P(TestSparseCSFTensorForIndexValueType, TestEqualityMismatchedDimensi ASSERT_FALSE(si_2D->Equals(*si_3D)); ASSERT_FALSE(si_3D->Equals(*si_2D)); + ASSERT_TRUE(si_2D->Equals(*si_2D)); } REGISTER_TYPED_TEST_SUITE_P(TestSparseCSFTensorForIndexValueType, TestCreateSparseTensor, From 915dcdc4d29e62163a1aef4a448d35733de829a5 Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Wed, 4 Feb 2026 15:23:07 +0500 Subject: [PATCH 11/14] Fix duplicate function definition from GitHub suggestion GitHub's 'Commit suggestion' feature added rok's implementation but didn't remove the old code, causing duplicate definition error. Removed old implementation to keep only the cleaner C++20 ranges version. --- cpp/src/arrow/sparse_tensor.cc | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index 9af922c4b41a..9d3ac1d7c18a 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -404,21 +404,6 @@ SparseCSFIndex::SparseCSFIndex(const std::vector>& indpt std::string SparseCSFIndex::ToString() const { return std::string("SparseCSFIndex"); } -bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { - if (indices().size() != other.indices().size()) { - return false; - } - if (indptr().size() != other.indptr().size()) { - return false; - } - - for (int64_t i = 0; i < static_cast(indices().size()); ++i) { - if (!indices()[i]->Equals(*other.indices()[i])) return false; - } - for (int64_t i = 0; i < static_cast(indptr().size()); ++i) { - if (!indptr()[i]->Equals(*other.indptr()[i])) return false; - } - return axis_order() == other.axis_order(); bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { auto eq = [](const auto& a, const auto& b) { return a->Equals(*b); }; return axis_order() == other.axis_order() From b508636b0a2f58c3bcb0981a65397f7ad00fe8f4 Mon Sep 17 00:00:00 2001 From: Ali Mahmood Rana <159713825+AliRana30@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:59:16 +0500 Subject: [PATCH 12/14] Update cpp/src/arrow/sparse_tensor.cc Co-authored-by: Rok Mihevc --- cpp/src/arrow/sparse_tensor.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index 9d3ac1d7c18a..17fc48da2b4f 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -406,9 +406,9 @@ std::string SparseCSFIndex::ToString() const { return std::string("SparseCSFInde bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { auto eq = [](const auto& a, const auto& b) { return a->Equals(*b); }; - return axis_order() == other.axis_order() - && std::ranges::equal(indices(), other.indices(), eq) - && std::ranges::equal(indptr(), other.indptr(), eq); + return axis_order() == other.axis_order() && + std::ranges::equal(indices(), other.indices(), eq) && + std::ranges::equal(indptr(), other.indptr(), eq); } // ---------------------------------------------------------------------- From ae4c1b975c20624d00596ffaeab283988ac0fafd Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Wed, 4 Feb 2026 17:31:33 +0500 Subject: [PATCH 13/14] Apply clang-format indentation per CI requirement --- cpp/src/arrow/sparse_tensor.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/arrow/sparse_tensor.cc b/cpp/src/arrow/sparse_tensor.cc index 17fc48da2b4f..477fa2f76505 100644 --- a/cpp/src/arrow/sparse_tensor.cc +++ b/cpp/src/arrow/sparse_tensor.cc @@ -407,8 +407,8 @@ std::string SparseCSFIndex::ToString() const { return std::string("SparseCSFInde bool SparseCSFIndex::Equals(const SparseCSFIndex& other) const { auto eq = [](const auto& a, const auto& b) { return a->Equals(*b); }; return axis_order() == other.axis_order() && - std::ranges::equal(indices(), other.indices(), eq) && - std::ranges::equal(indptr(), other.indptr(), eq); + std::ranges::equal(indices(), other.indices(), eq) && + std::ranges::equal(indptr(), other.indptr(), eq); } // ---------------------------------------------------------------------- From e01c88635d831375c8dd41eded49fd9f5a665d6c Mon Sep 17 00:00:00 2001 From: Alirana2829 Date: Thu, 5 Feb 2026 21:28:29 +0500 Subject: [PATCH 14/14] GH-49155: [C++][IPC] Allow disabling extension type deserialization Add extension_types_blocked option to IpcReadOptions to allow users to disable extension type deserialization for security/robustness. When enabled, extension types are returned as their storage types instead of calling custom deserialization code. --- cpp/src/arrow/ipc/metadata_internal.cc | 54 ++++++++++++++++++-------- cpp/src/arrow/ipc/options.h | 10 +++++ cpp/src/arrow/ipc/reader.cc | 2 +- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/cpp/src/arrow/ipc/metadata_internal.cc b/cpp/src/arrow/ipc/metadata_internal.cc index 65a4fcee7a2e..e73df6e98281 100644 --- a/cpp/src/arrow/ipc/metadata_internal.cc +++ b/cpp/src/arrow/ipc/metadata_internal.cc @@ -851,7 +851,9 @@ class FieldToFlatbufferVisitor { }; Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos, - DictionaryMemo* dictionary_memo, std::shared_ptr* out) { + DictionaryMemo* dictionary_memo, + const IpcReadOptions* options, + std::shared_ptr* out) { std::shared_ptr type; std::shared_ptr metadata; @@ -866,7 +868,7 @@ Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos, child_fields.resize(children->size()); for (int i = 0; i < static_cast(children->size()); ++i) { RETURN_NOT_OK(FieldFromFlatbuffer(children->Get(i), field_pos.child(i), - dictionary_memo, &child_fields[i])); + dictionary_memo, options, &child_fields[i])); } } @@ -899,21 +901,26 @@ Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos, // Look for extension metadata in custom_metadata field int name_index = metadata->FindKey(kExtensionTypeKeyName); if (name_index != -1) { - std::shared_ptr ext_type = - GetExtensionType(metadata->value(name_index)); - if (ext_type != nullptr) { - int data_index = metadata->FindKey(kExtensionMetadataKeyName); - std::string type_data = data_index == -1 ? "" : metadata->value(data_index); - - ARROW_ASSIGN_OR_RAISE(type, ext_type->Deserialize(type, type_data)); - // Remove the metadata, for faithful roundtripping - if (data_index != -1) { - RETURN_NOT_OK(metadata->DeleteMany({name_index, data_index})); - } else { - RETURN_NOT_OK(metadata->Delete(name_index)); + // Check if extension types are blocked + bool should_deserialize = (options == nullptr || !options->extension_types_blocked); + + if (should_deserialize) { + std::shared_ptr ext_type = + GetExtensionType(metadata->value(name_index)); + if (ext_type != nullptr) { + int data_index = metadata->FindKey(kExtensionMetadataKeyName); + std::string type_data = data_index == -1 ? "" : metadata->value(data_index); + + ARROW_ASSIGN_OR_RAISE(type, ext_type->Deserialize(type, type_data)); + // Remove the metadata, for faithful roundtripping + if (data_index != -1) { + RETURN_NOT_OK(metadata->DeleteMany({name_index, data_index})); + } else { + RETURN_NOT_OK(metadata->Delete(name_index)); + } } } - // NOTE: if extension type is unknown, we do not raise here and + // NOTE: if extension type is unknown or blocked, we do not raise here and // simply return the storage type. } } @@ -933,6 +940,13 @@ Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos, return Status::OK(); } +// Backward-compatible overload without options +Status FieldFromFlatbuffer(const flatbuf::Field* field, FieldPosition field_pos, + DictionaryMemo* dictionary_memo, std::shared_ptr* out) { + return FieldFromFlatbuffer(field, field_pos, dictionary_memo, nullptr, out); +} + + flatbuffers::Offset SerializeCustomMetadata( FBB& fbb, const std::shared_ptr& metadata) { std::vector key_values; @@ -1433,7 +1447,7 @@ Status WriteFileFooter(const Schema& schema, const std::vector& dicti // ---------------------------------------------------------------------- Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo, - std::shared_ptr* out) { + const IpcReadOptions* options, std::shared_ptr* out) { auto schema = static_cast(opaque_schema); CHECK_FLATBUFFERS_NOT_NULL(schema, "schema"); CHECK_FLATBUFFERS_NOT_NULL(schema->fields(), "Schema.fields"); @@ -1447,7 +1461,7 @@ Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo, // XXX I don't think this check is necessary (AP) CHECK_FLATBUFFERS_NOT_NULL(field, "DictionaryEncoding.indexType"); RETURN_NOT_OK( - FieldFromFlatbuffer(field, field_pos.child(i), dictionary_memo, &fields[i])); + FieldFromFlatbuffer(field, field_pos.child(i), dictionary_memo, options, &fields[i])); } std::shared_ptr metadata; @@ -1460,6 +1474,12 @@ Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo, return Status::OK(); } +// Backward-compatible overload +Status GetSchema(const void* opaque_schema, DictionaryMemo* dictionary_memo, + std::shared_ptr* out) { + return GetSchema(opaque_schema, dictionary_memo, nullptr, out); +} + Status GetTensorMetadata(const Buffer& metadata, std::shared_ptr* type, std::vector* shape, std::vector* strides, std::vector* dim_names) { diff --git a/cpp/src/arrow/ipc/options.h b/cpp/src/arrow/ipc/options.h index ec0e2a5b6f90..a40053ab3407 100644 --- a/cpp/src/arrow/ipc/options.h +++ b/cpp/src/arrow/ipc/options.h @@ -189,6 +189,16 @@ struct ARROW_EXPORT IpcReadOptions { /// The lazy property will always be reset to true to deliver the expected behavior io::CacheOptions pre_buffer_cache_options = io::CacheOptions::LazyDefaults(); + /// \brief Whether to disable deserialization of extension types + /// + /// If true, extension types will be deserialized as their storage types instead + /// of calling custom deserialization code. This can be useful for security-sensitive + /// applications that want to avoid potentially buggy third-party extension type + /// deserialization code. + /// + /// Default is false (extension types are deserialized normally). + bool extension_types_blocked = false; + static IpcReadOptions Defaults(); }; diff --git a/cpp/src/arrow/ipc/reader.cc b/cpp/src/arrow/ipc/reader.cc index f1571f76c243..6ad06a09f0df 100644 --- a/cpp/src/arrow/ipc/reader.cc +++ b/cpp/src/arrow/ipc/reader.cc @@ -810,7 +810,7 @@ Status UnpackSchemaMessage(const void* opaque_schema, const IpcReadOptions& opti std::shared_ptr* schema, std::shared_ptr* out_schema, std::vector* field_inclusion_mask, bool* swap_endian) { - RETURN_NOT_OK(internal::GetSchema(opaque_schema, dictionary_memo, schema)); + RETURN_NOT_OK(internal::GetSchema(opaque_schema, dictionary_memo, &options, schema)); // If we are selecting only certain fields, populate the inclusion mask now // for fast lookups