This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 43a31acc4b4 [fix](be) Validate Arrow input buffers before column 
conversion (#64796)
43a31acc4b4 is described below

commit 43a31acc4b45647b84d5298cfd0e2ec1aca1540a
Author: Mryange <[email protected]>
AuthorDate: Tue Jul 14 17:40:48 2026 +0800

    [fix](be) Validate Arrow input buffers before column conversion (#64796)
    
    ### What problem does this PR solve?
    
    
    Doris converts Arrow arrays into Doris columns through
    `DataTypeSerDe::read_column_from_arrow`. If the Arrow producer sends
    malformed array metadata, such as truncated validity bitmaps, truncated
    offsets buffers, non-monotonic string/list offsets, or offsets pointing
    past the child/value buffer, the existing conversion code may read
    invalid Arrow memory and crash BE.
    
    Root cause: the Arrow-to-Doris serde path trusted Arrow array metadata
    before accessing Arrow buffers. Several hot paths call `IsNull()`,
    `Value()`, raw value offsets, list offsets, or child arrays directly, so
    malformed Arrow buffers can trigger out-of-bounds reads before Doris
    reports a clean error.
    
    This PR adds lightweight, type-specific Arrow input validation before
    those buffer accesses. The checks are modeled as local preflight checks
    rather than full `ValidateFull()`: validity bitmap size, fixed-width
    data buffer size, boolean bitmap size, binary/string offsets buffer
    size, per-value data range, and list/map offsets monotonicity plus child
    length bounds. A BE config `enable_arrow_input_validation` is added and
    defaults to `true`.
    
    The change also fixes an existing `FixedSizeBinaryArray` sliced-read
    null check: the loop uses a relative index after `GetValue(start)`, but
    `IsNull()` expects the original Arrow row index, so it must check `start
    + offset_i`.
    
    
    | Type | Rows | Check disabled | Check enabled | Overhead |
    | --- | ---: | ---: | ---: | ---: |
    | String | 4096 | 39,352 ns | 42,032 ns | +6.8% |
    | String | 65536 | 604,782 ns | 624,064 ns | +3.2% |
    | Int64 | 4096 | 1,480 ns | 1,523 ns | +2.9% |
    | Int64 | 65536 | 16,160 ns | 15,883 ns | -1.7% |
    | Boolean | 4096 | 4,784 ns | 4,888 ns | +2.2% |
    | Boolean | 65536 | 79,017 ns | 80,453 ns | +1.8% |
    | ArrayString | 4096 | 139,747 ns | 147,793 ns | +5.8% |
    | ArrayString | 65536 | 2,384,377 ns | 2,477,601 ns | +3.9% |
    | MapStringInt | 4096 | 84,375 ns | 96,022 ns | +13.8% |
    | MapStringInt | 65536 | 2,742,538 ns | 2,903,358 ns | +5.9% |
    
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 be/benchmark/benchmark_arrow_validation.hpp        | 382 +++++++++++++++++++++
 be/benchmark/benchmark_main.cpp                    |   1 +
 be/src/common/config.cpp                           |   3 +
 be/src/common/config.h                             |   3 +
 be/src/core/data_type_serde/arrow_validation.h     | 320 +++++++++++++++++
 .../core/data_type_serde/data_type_array_serde.cpp |   5 +
 .../data_type_date_or_datetime_serde.cpp           |   5 +
 .../data_type_serde/data_type_datetimev2_serde.cpp |   5 +
 .../data_type_serde/data_type_datev2_serde.cpp     |   5 +
 .../data_type_serde/data_type_decimal_serde.cpp    |   5 +
 .../core/data_type_serde/data_type_ipv4_serde.cpp  |   5 +
 .../core/data_type_serde/data_type_ipv6_serde.cpp  |   5 +
 .../core/data_type_serde/data_type_jsonb_serde.cpp |   5 +
 .../core/data_type_serde/data_type_map_serde.cpp   |   5 +
 .../data_type_serde/data_type_nullable_serde.cpp   |   6 +
 .../data_type_serde/data_type_number_serde.cpp     |  26 +-
 .../data_type_serde/data_type_string_serde.cpp     |  39 ++-
 .../data_type_serde/data_type_struct_serde.cpp     |   5 +
 .../data_type_serde_arrow_validation_test.cpp      | 275 +++++++++++++++
 19 files changed, 1095 insertions(+), 10 deletions(-)

diff --git a/be/benchmark/benchmark_arrow_validation.hpp 
b/be/benchmark/benchmark_arrow_validation.hpp
new file mode 100644
index 00000000000..f5a2da45fb4
--- /dev/null
+++ b/be/benchmark/benchmark_arrow_validation.hpp
@@ -0,0 +1,382 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <arrow/array/array_binary.h>
+#include <arrow/array/array_nested.h>
+#include <arrow/array/array_primitive.h>
+#include <arrow/array/builder_binary.h>
+#include <arrow/array/builder_nested.h>
+#include <arrow/array/builder_primitive.h>
+#include <arrow/buffer.h>
+#include <arrow/type.h>
+#include <benchmark/benchmark.h>
+#include <cctz/time_zone.h>
+
+#include <chrono>
+#include <cstdint>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "common/config.h"
+#include "core/assert_cast.h"
+#include "core/column/column_array.h"
+#include "core/column/column_map.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/column/column_vector.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_map.h"
+#include "core/data_type/data_type_number.h"
+#include "core/data_type/data_type_string.h"
+#include "core/data_type/primitive_type.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace {
+
+enum class ArrowValidationBenchMode { OLD, CHECK_DISABLED, CHECK_ENABLED };
+
+void throw_if_not_ok(const arrow::Status& status) {
+    if (!status.ok()) {
+        throw std::runtime_error(status.ToString());
+    }
+}
+
+void throw_if_not_ok(const Status& status) {
+    if (!status.ok()) {
+        throw std::runtime_error(status.to_string());
+    }
+}
+
+void keep_column_size(size_t size) {
+    benchmark::DoNotOptimize(size);
+}
+
+template <typename Func>
+double measure_seconds(Func&& func) {
+    const auto start = std::chrono::steady_clock::now();
+    std::forward<Func>(func)();
+    const auto finish = std::chrono::steady_clock::now();
+    return std::chrono::duration<double>(finish - start).count();
+}
+
+std::shared_ptr<arrow::StringArray> make_string_array(int64_t rows) {
+    arrow::StringBuilder builder;
+    throw_if_not_ok(builder.Reserve(rows));
+    for (int64_t i = 0; i < rows; ++i) {
+        const std::string value = "arrow_validation_" + std::to_string(i % 
1000);
+        throw_if_not_ok(builder.Append(value));
+    }
+    std::shared_ptr<arrow::Array> array;
+    throw_if_not_ok(builder.Finish(&array));
+    return std::static_pointer_cast<arrow::StringArray>(array);
+}
+
+std::shared_ptr<arrow::Int64Array> make_int64_array(int64_t rows) {
+    arrow::Int64Builder builder;
+    throw_if_not_ok(builder.Reserve(rows));
+    for (int64_t i = 0; i < rows; ++i) {
+        throw_if_not_ok(builder.Append(i));
+    }
+    std::shared_ptr<arrow::Array> array;
+    throw_if_not_ok(builder.Finish(&array));
+    return std::static_pointer_cast<arrow::Int64Array>(array);
+}
+
+std::shared_ptr<arrow::BooleanArray> make_boolean_array(int64_t rows) {
+    arrow::BooleanBuilder builder;
+    throw_if_not_ok(builder.Reserve(rows));
+    for (int64_t i = 0; i < rows; ++i) {
+        throw_if_not_ok(builder.Append((i & 1) == 0));
+    }
+    std::shared_ptr<arrow::Array> array;
+    throw_if_not_ok(builder.Finish(&array));
+    return std::static_pointer_cast<arrow::BooleanArray>(array);
+}
+
+std::shared_ptr<arrow::ListArray> make_string_list_array(int64_t rows) {
+    auto value_builder = std::make_shared<arrow::StringBuilder>();
+    arrow::ListBuilder builder(arrow::default_memory_pool(), value_builder);
+    throw_if_not_ok(builder.Reserve(rows));
+    throw_if_not_ok(value_builder->Reserve(rows * 3));
+    for (int64_t i = 0; i < rows; ++i) {
+        throw_if_not_ok(builder.Append());
+        for (int64_t j = 0; j < 3; ++j) {
+            const std::string value = "item_" + std::to_string((i + j) % 1000);
+            throw_if_not_ok(value_builder->Append(value));
+        }
+    }
+    std::shared_ptr<arrow::Array> array;
+    throw_if_not_ok(builder.Finish(&array));
+    return std::static_pointer_cast<arrow::ListArray>(array);
+}
+
+std::shared_ptr<arrow::MapArray> make_string_int_map_array(int64_t rows) {
+    auto keys = make_string_array(rows * 2);
+    auto items = make_int64_array(rows * 2);
+    std::vector<int32_t> offsets(rows + 1);
+    for (int64_t i = 0; i <= rows; ++i) {
+        offsets[i] = static_cast<int32_t>(i * 2);
+    }
+    auto offsets_buffer = arrow::Buffer::FromVector(std::move(offsets));
+    auto map_type = arrow::map(arrow::utf8(), arrow::int64());
+    return std::make_shared<arrow::MapArray>(map_type, rows, offsets_buffer, 
keys, items);
+}
+
+void read_string_old(ColumnString& column, const arrow::BinaryArray& array, 
int64_t start,
+                     int64_t end) {
+    std::shared_ptr<arrow::Buffer> buffer = array.value_data();
+    const uint8_t* offsets_data = array.value_offsets()->data();
+    const size_t offset_size = sizeof(int32_t);
+
+    for (auto offset_i = start; offset_i < end; ++offset_i) {
+        if (!array.IsNull(offset_i)) {
+            auto start_offset = unaligned_load<int32_t>(offsets_data + 
offset_i * offset_size);
+            auto end_offset = unaligned_load<int32_t>(offsets_data + (offset_i 
+ 1) * offset_size);
+            int32_t length = end_offset - start_offset;
+            const auto* raw_data = buffer->data() + start_offset;
+            column.insert_data(reinterpret_cast<const char*>(raw_data), 
length);
+        } else {
+            column.insert_default();
+        }
+    }
+}
+
+void read_int64_old(ColumnInt64& column, const arrow::Array& array, int64_t 
start, int64_t end) {
+    auto& col_data = column.get_data();
+    std::shared_ptr<arrow::Buffer> buffer = array.data()->buffers[1];
+    const auto* raw_data = reinterpret_cast<const int64_t*>(buffer->data()) + 
start;
+    col_data.insert(raw_data, raw_data + end - start);
+}
+
+void read_boolean_old(ColumnUInt8& column, const arrow::BooleanArray& array, 
int64_t start,
+                      int64_t end) {
+    auto& col_data = column.get_data();
+    for (int64_t bool_i = start; bool_i != end; ++bool_i) {
+        col_data.emplace_back(array.Value(bool_i));
+    }
+}
+
+void read_array_string_old(ColumnArray& column, const arrow::ListArray& array, 
int64_t start,
+                           int64_t end) {
+    auto& offsets_data = column.get_offsets();
+    auto arrow_offsets_array = array.offsets();
+    auto* arrow_offsets = 
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
+    auto prev_size = offsets_data.back();
+    const auto* base_offsets_ptr = reinterpret_cast<const 
uint8_t*>(arrow_offsets->raw_values());
+    const size_t offset_element_size = sizeof(int32_t);
+    const uint8_t* start_offset_ptr = base_offsets_ptr + start * 
offset_element_size;
+    const uint8_t* end_offset_ptr = base_offsets_ptr + end * 
offset_element_size;
+    auto arrow_nested_start_offset = unaligned_load<int32_t>(start_offset_ptr);
+    auto arrow_nested_end_offset = unaligned_load<int32_t>(end_offset_ptr);
+
+    for (auto i = start + 1; i < end + 1; ++i) {
+        const uint8_t* current_offset_ptr = base_offsets_ptr + i * 
offset_element_size;
+        auto current_offset = unaligned_load<int32_t>(current_offset_ptr);
+        offsets_data.emplace_back(prev_size + current_offset - 
arrow_nested_start_offset);
+    }
+    auto& nested_nullable = assert_cast<ColumnNullable&>(column.get_data());
+    
read_string_old(assert_cast<ColumnString&>(nested_nullable.get_nested_column()),
+                    static_cast<const arrow::BinaryArray&>(*array.values()),
+                    arrow_nested_start_offset, arrow_nested_end_offset);
+    auto& null_map = nested_nullable.get_null_map_data();
+    null_map.resize_fill(null_map.size() + arrow_nested_end_offset - 
arrow_nested_start_offset, 0);
+}
+
+void read_map_string_int_old(ColumnMap& column, const arrow::MapArray& array, 
int64_t start,
+                             int64_t end) {
+    auto& offsets_data = column.get_offsets();
+    auto arrow_offsets_array = array.offsets();
+    auto* arrow_offsets = 
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
+    auto prev_size = offsets_data.back();
+    const auto* base_offsets_ptr = reinterpret_cast<const 
uint8_t*>(arrow_offsets->raw_values());
+    const size_t offset_element_size = sizeof(int32_t);
+    const uint8_t* start_offset_ptr = base_offsets_ptr + start * 
offset_element_size;
+    const uint8_t* end_offset_ptr = base_offsets_ptr + end * 
offset_element_size;
+    auto arrow_nested_start_offset = unaligned_load<int32_t>(start_offset_ptr);
+    auto arrow_nested_end_offset = unaligned_load<int32_t>(end_offset_ptr);
+    for (int64_t i = start + 1; i < end + 1; ++i) {
+        const uint8_t* current_offset_ptr = base_offsets_ptr + i * 
offset_element_size;
+        auto current_offset = unaligned_load<int32_t>(current_offset_ptr);
+        offsets_data.emplace_back(prev_size + current_offset - 
arrow_nested_start_offset);
+    }
+    read_string_old(assert_cast<ColumnString&>(column.get_keys()),
+                    static_cast<const arrow::BinaryArray&>(*array.keys()),
+                    arrow_nested_start_offset, arrow_nested_end_offset);
+    read_int64_old(assert_cast<ColumnInt64&>(column.get_values()), 
*array.items(),
+                   arrow_nested_start_offset, arrow_nested_end_offset);
+}
+
+template <ArrowValidationBenchMode mode, typename DataType, typename 
ArrowArray>
+void run_serde_benchmark(benchmark::State& state, const 
std::shared_ptr<DataType>& type,
+                         const std::shared_ptr<ArrowArray>& array) {
+    const bool old_config = config::enable_arrow_input_validation;
+    if constexpr (mode == ArrowValidationBenchMode::CHECK_DISABLED) {
+        config::enable_arrow_input_validation = false;
+    } else if constexpr (mode == ArrowValidationBenchMode::CHECK_ENABLED) {
+        config::enable_arrow_input_validation = true;
+    }
+
+    for (auto _ : state) {
+        const double seconds = measure_seconds([&] {
+            auto column = type->create_column();
+            throw_if_not_ok(type->get_serde()->read_column_from_arrow(
+                    *column, array.get(), 0, array->length(), 
cctz::utc_time_zone()));
+            keep_column_size(column->size());
+        });
+        state.SetIterationTime(seconds);
+    }
+    config::enable_arrow_input_validation = old_config;
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_String(benchmark::State& state) {
+    auto array = make_string_array(state.range(0));
+    auto type = std::make_shared<DataTypeString>();
+
+    if constexpr (mode == ArrowValidationBenchMode::OLD) {
+        for (auto _ : state) {
+            const double seconds = measure_seconds([&] {
+                auto column = ColumnString::create();
+                read_string_old(*column, *array, 0, array->length());
+                keep_column_size(column->size());
+            });
+            state.SetIterationTime(seconds);
+        }
+    } else {
+        run_serde_benchmark<mode>(state, type, array);
+    }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_Int64(benchmark::State& state) {
+    auto array = make_int64_array(state.range(0));
+    auto type = std::make_shared<DataTypeInt64>();
+
+    if constexpr (mode == ArrowValidationBenchMode::OLD) {
+        for (auto _ : state) {
+            const double seconds = measure_seconds([&] {
+                auto column = ColumnInt64::create();
+                read_int64_old(*column, *array, 0, array->length());
+                keep_column_size(column->size());
+            });
+            state.SetIterationTime(seconds);
+        }
+    } else {
+        run_serde_benchmark<mode>(state, type, array);
+    }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_Boolean(benchmark::State& state) {
+    auto array = make_boolean_array(state.range(0));
+    auto type = std::make_shared<DataTypeBool>();
+
+    if constexpr (mode == ArrowValidationBenchMode::OLD) {
+        for (auto _ : state) {
+            const double seconds = measure_seconds([&] {
+                auto column = ColumnUInt8::create();
+                read_boolean_old(*column, *array, 0, array->length());
+                keep_column_size(column->size());
+            });
+            state.SetIterationTime(seconds);
+        }
+    } else {
+        run_serde_benchmark<mode>(state, type, array);
+    }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_ArrayString(benchmark::State& state) {
+    auto array = make_string_list_array(state.range(0));
+    auto type = 
std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>());
+
+    if constexpr (mode == ArrowValidationBenchMode::OLD) {
+        for (auto _ : state) {
+            const double seconds = measure_seconds([&] {
+                auto column = type->create_column();
+                read_array_string_old(assert_cast<ColumnArray&>(*column), 
*array, 0,
+                                      array->length());
+                keep_column_size(column->size());
+            });
+            state.SetIterationTime(seconds);
+        }
+    } else {
+        run_serde_benchmark<mode>(state, type, array);
+    }
+}
+
+template <ArrowValidationBenchMode mode>
+void BM_ArrowValidation_MapStringInt(benchmark::State& state) {
+    auto array = make_string_int_map_array(state.range(0));
+    auto type = 
std::make_shared<DataTypeMap>(std::make_shared<DataTypeString>(),
+                                              
std::make_shared<DataTypeInt64>());
+
+    if constexpr (mode == ArrowValidationBenchMode::OLD) {
+        for (auto _ : state) {
+            const double seconds = measure_seconds([&] {
+                auto column = type->create_column();
+                read_map_string_int_old(assert_cast<ColumnMap&>(*column), 
*array, 0,
+                                        array->length());
+                keep_column_size(column->size());
+            });
+            state.SetIterationTime(seconds);
+        }
+    } else {
+        run_serde_benchmark<mode>(state, type, array);
+    }
+}
+
+#define REGISTER_ARROW_VALIDATION_BENCH(FUNC, MODE, NAME)               \
+    BENCHMARK_TEMPLATE(FUNC, ArrowValidationBenchMode::MODE)            \
+            ->Name("BM_ArrowValidation_" #NAME "_" #MODE "/rows:4096")  \
+            ->Arg(4096)                                                 \
+            ->UseManualTime();                                          \
+    BENCHMARK_TEMPLATE(FUNC, ArrowValidationBenchMode::MODE)            \
+            ->Name("BM_ArrowValidation_" #NAME "_" #MODE "/rows:65536") \
+            ->Arg(65536)                                                \
+            ->UseManualTime()
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_String, OLD, String);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_String, CHECK_DISABLED, 
String);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_String, CHECK_ENABLED, 
String);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Int64, OLD, Int64);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Int64, CHECK_DISABLED, 
Int64);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Int64, CHECK_ENABLED, 
Int64);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Boolean, OLD, Boolean);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Boolean, CHECK_DISABLED, 
Boolean);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_Boolean, CHECK_ENABLED, 
Boolean);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_ArrayString, OLD, 
ArrayString);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_ArrayString, 
CHECK_DISABLED, ArrayString);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_ArrayString, CHECK_ENABLED, 
ArrayString);
+
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_MapStringInt, OLD, 
MapStringInt);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_MapStringInt, 
CHECK_DISABLED, MapStringInt);
+REGISTER_ARROW_VALIDATION_BENCH(BM_ArrowValidation_MapStringInt, 
CHECK_ENABLED, MapStringInt);
+
+#undef REGISTER_ARROW_VALIDATION_BENCH
+
+} // namespace
+} // namespace doris
diff --git a/be/benchmark/benchmark_main.cpp b/be/benchmark/benchmark_main.cpp
index 53ac0c1df00..efb2930f836 100644
--- a/be/benchmark/benchmark_main.cpp
+++ b/be/benchmark/benchmark_main.cpp
@@ -17,6 +17,7 @@
 
 #include <benchmark/benchmark.h>
 
+#include "benchmark_arrow_validation.hpp"
 #include "benchmark_bit_pack.hpp"
 #include "benchmark_bits.hpp"
 #include "benchmark_block_bloom_filter.hpp"
diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index b193797a863..e8ca2a9de7e 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -72,6 +72,9 @@ DEFINE_Int32(brpc_port, "8060");
 
 DEFINE_Int32(arrow_flight_sql_port, "8050");
 
+// Validate Arrow input buffers in opted-in Arrow readers before converting 
them to Doris columns.
+DEFINE_Bool(enable_arrow_input_validation, "true");
+
 DEFINE_Int32(cdc_client_port, "9096");
 
 DEFINE_String(cdc_client_java_opts, "");
diff --git a/be/src/common/config.h b/be/src/common/config.h
index b79e10e8f69..cd50e4fb08f 100644
--- a/be/src/common/config.h
+++ b/be/src/common/config.h
@@ -119,6 +119,9 @@ DECLARE_Int32(brpc_port);
 // Default -1, do not start arrow flight sql server.
 DECLARE_Int32(arrow_flight_sql_port);
 
+// Validate Arrow input buffers in opted-in Arrow readers before converting 
them to Doris columns.
+DECLARE_Bool(enable_arrow_input_validation);
+
 // port for cdc client scan oltp cdc data
 DECLARE_Int32(cdc_client_port);
 
diff --git a/be/src/core/data_type_serde/arrow_validation.h 
b/be/src/core/data_type_serde/arrow_validation.h
new file mode 100644
index 00000000000..9d8b09b0cf1
--- /dev/null
+++ b/be/src/core/data_type_serde/arrow_validation.h
@@ -0,0 +1,320 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include <arrow/array/array_base.h>
+#include <arrow/array/array_binary.h>
+#include <arrow/array/array_nested.h>
+#include <arrow/array/array_primitive.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "common/compiler_util.h"
+#include "common/exception.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace arrow_validation_detail {
+
+inline std::string arrow_type_name(const arrow::Array& array) {
+    return array.type() ? array.type()->name() : "unknown";
+}
+
+inline void throw_invalid_arrow(std::string_view arrow_type, std::string_view 
message) {
+    throw Exception(ErrorCode::INVALID_ARGUMENT, "Invalid Arrow {}: {}", 
arrow_type, message);
+}
+
+inline void throw_invalid_arrow(const arrow::Array& array, std::string_view 
message) {
+    throw_invalid_arrow(arrow_type_name(array), message);
+}
+
+template <typename... Args>
+inline void throw_invalid_arrow(std::string_view arrow_type, std::string_view 
format,
+                                Args&&... args) {
+    throw Exception(ErrorCode::INVALID_ARGUMENT, "Invalid Arrow {}: {}", 
arrow_type,
+                    fmt::format(std::string(format), 
std::forward<Args>(args)...));
+}
+
+template <typename... Args>
+inline void throw_invalid_arrow(const arrow::Array& array, std::string_view 
format,
+                                Args&&... args) {
+    throw_invalid_arrow(arrow_type_name(array), format, 
std::forward<Args>(args)...);
+}
+
+inline void check_arrow_length_and_offset(const arrow::Array& array) {
+    if (UNLIKELY(array.length() < 0 || array.offset() < 0)) {
+        throw_invalid_arrow(array, "negative length or offset: length={}, 
offset={}",
+                            array.length(), array.offset());
+    }
+}
+
+inline void check_arrow_no_offset(const arrow::Array& array) {
+    check_arrow_length_and_offset(array);
+    if (UNLIKELY(array.offset() != 0)) {
+        throw_invalid_arrow(array, "non-zero array offset is not supported: 
offset={}",
+                            array.offset());
+    }
+}
+
+inline void check_add_overflow(size_t left, size_t right, const arrow::Array& 
array,
+                               std::string_view item) {
+    if (UNLIKELY(left > std::numeric_limits<size_t>::max() - right)) {
+        throw_invalid_arrow(array, "{} size overflow", item);
+    }
+}
+
+inline std::shared_ptr<arrow::Int32Array> get_int32_offsets_array(const 
arrow::Array& array) {
+    auto offsets_array = static_cast<const arrow::ListArray&>(array).offsets();
+    auto offsets = std::dynamic_pointer_cast<arrow::Int32Array>(offsets_array);
+    if (UNLIKELY(!offsets)) {
+        throw_invalid_arrow(array, "offsets array is not Int32Array");
+    }
+    return offsets;
+}
+
+} // namespace arrow_validation_detail
+
+inline void check_arrow_no_offset(const arrow::Array& array) {
+    arrow_validation_detail::check_arrow_no_offset(array);
+}
+
+// Validate the caller's requested read range before any Arrow buffer access.
+// This rejects negative or out-of-array start/end values up front.
+inline void check_arrow_array_range(const arrow::Array& array, int64_t start, 
int64_t end) {
+    arrow_validation_detail::check_arrow_no_offset(array);
+    if (UNLIKELY(start < 0 || end < start || end > array.length())) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "read range is invalid: start={}, end={}, length={}", 
start, end,
+                array.length());
+    }
+}
+
+// Validate buffers[0] when a validity bitmap exists. This must run before
+// IsNull()/IsValid()/null_count(), because those APIs may scan the bitmap.
+inline void check_arrow_validity_bitmap(const arrow::Array& array) {
+    arrow_validation_detail::check_arrow_length_and_offset(array);
+    const auto& buffers = array.data()->buffers;
+    if (buffers.empty() || !buffers[0]) {
+        if (UNLIKELY(array.data()->null_count > 0)) {
+            arrow_validation_detail::throw_invalid_arrow(
+                    array, "validity bitmap is missing but null_count={}",
+                    array.data()->null_count.load());
+        }
+        return;
+    }
+
+    const size_t offset = static_cast<size_t>(array.offset());
+    const size_t length = static_cast<size_t>(array.length());
+    arrow_validation_detail::check_add_overflow(offset, length, array, 
"validity bitmap");
+    const size_t count = offset + length;
+    const size_t required = count / 8 + (count % 8 != 0 ? 1 : 0);
+    const size_t available = static_cast<size_t>(buffers[0]->size());
+    if (UNLIKELY(available < required)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "validity bitmap too small: {} bytes available, {} 
required", available,
+                required);
+    }
+}
+
+// Validate buffers[1] for fixed-width arrays before raw_values(), Value(), or
+// direct buffer memcpy. elem_size is the physical byte width of one value.
+inline void check_arrow_fixed_width_buffer(const arrow::Array& array, size_t 
elem_size) {
+    check_arrow_validity_bitmap(array);
+    const auto& buffers = array.data()->buffers;
+    if (UNLIKELY(buffers.size() <= 1 || !buffers[1])) {
+        if (array.length() == 0) {
+            return;
+        }
+        arrow_validation_detail::throw_invalid_arrow(array, "data buffer is 
missing");
+    }
+
+    const size_t offset = static_cast<size_t>(array.offset());
+    const size_t length = static_cast<size_t>(array.length());
+    arrow_validation_detail::check_add_overflow(offset, length, array, "data 
buffer");
+    const size_t count = offset + length;
+    if (UNLIKELY(elem_size != 0 && count > std::numeric_limits<size_t>::max() 
/ elem_size)) {
+        arrow_validation_detail::throw_invalid_arrow(array, "data buffer size 
overflow");
+    }
+    const size_t required = elem_size * count;
+    const size_t available = static_cast<size_t>(buffers[1]->size());
+    if (UNLIKELY(available < required)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "data buffer too small: {} bytes available, {} 
required", available,
+                required);
+    }
+}
+
+// Validate the bit-packed boolean data bitmap before BooleanArray::Value().
+inline void check_arrow_boolean_buffer(const arrow::Array& array) {
+    check_arrow_validity_bitmap(array);
+    const auto& buffers = array.data()->buffers;
+    if (UNLIKELY(buffers.size() <= 1 || !buffers[1])) {
+        if (array.length() == 0) {
+            return;
+        }
+        arrow_validation_detail::throw_invalid_arrow(array, "data bitmap is 
missing");
+    }
+
+    const size_t offset = static_cast<size_t>(array.offset());
+    const size_t length = static_cast<size_t>(array.length());
+    arrow_validation_detail::check_add_overflow(offset, length, array, "data 
bitmap");
+    const size_t count = offset + length;
+    const size_t required = count / 8 + (count % 8 != 0 ? 1 : 0);
+    const size_t available = static_cast<size_t>(buffers[1]->size());
+    if (UNLIKELY(available < required)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "data bitmap too small: {} bytes available, {} 
required", available,
+                required);
+    }
+}
+
+// Validate one variable-width value range after reading offsets and before
+// forming value_data() + offset.
+inline void check_arrow_value_range(const arrow::Array& array, int64_t offset, 
int64_t length,
+                                    size_t buffer_size) {
+    if (UNLIKELY(offset < 0 || length < 0)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "value range has negative offset or length: offset={}, 
length={}", offset,
+                length);
+    }
+    const size_t safe_offset = static_cast<size_t>(offset);
+    const size_t safe_length = static_cast<size_t>(length);
+    if (UNLIKELY(safe_offset > buffer_size || safe_length > buffer_size - 
safe_offset)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "value range exceeds data buffer: offset={}, length={}, 
buffer_size={}",
+                offset, length, buffer_size);
+    }
+}
+
+namespace arrow_validation_detail {
+
+// Offsets buffers may come from external Arrow producers through Buffer::Wrap 
or FFI and are not
+// guaranteed to be aligned to int32_t. Do not use Int32Array::Value() here 
because it performs a
+// typed raw_values()[i] load and can trigger UBSan on misaligned buffers. 
Keep this validation path
+// consistent with the array/map readers below, which load offsets through 
unaligned_load().
+inline int32_t read_int32_offset(const arrow::Int32Array& offsets, int64_t 
index) {
+    const auto* data = reinterpret_cast<const uint8_t*>(offsets.raw_values());
+    return unaligned_load<int32_t>(data + index * sizeof(int32_t));
+}
+
+inline int64_t check_arrow_offsets_range(const arrow::Int32Array& offsets, 
int64_t start,
+                                         int64_t end) {
+    check_arrow_array_range(offsets, 0, offsets.length());
+    check_arrow_fixed_width_buffer(offsets, sizeof(int32_t));
+    if (UNLIKELY(start < 0 || end < start || end >= offsets.length())) {
+        arrow_validation_detail::throw_invalid_arrow(
+                offsets, "offsets read range is invalid: start={}, end={}, 
offsets_length={}",
+                start, end, offsets.length());
+    }
+
+    int64_t previous_offset = read_int32_offset(offsets, start);
+    if (UNLIKELY(previous_offset < 0)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                offsets, "offsets contain negative value: offset[{}]={}", 
start, previous_offset);
+    }
+    for (int64_t i = start + 1; i <= end; ++i) {
+        const int64_t current_offset = read_int32_offset(offsets, i);
+        if (UNLIKELY(current_offset < previous_offset)) {
+            arrow_validation_detail::throw_invalid_arrow(
+                    offsets,
+                    "offsets are not monotonically non-decreasing: 
offset[{}]={} < offset[{}]={}",
+                    i, current_offset, i - 1, previous_offset);
+        }
+        previous_offset = current_offset;
+    }
+    return previous_offset;
+}
+
+} // namespace arrow_validation_detail
+
+// Validate List offsets before reading offsets or recursing into values.
+inline void check_arrow_list_offsets(const arrow::ListArray& array, int64_t 
start, int64_t end) {
+    check_arrow_array_range(array, start, end);
+    const auto offsets = 
arrow_validation_detail::get_int32_offsets_array(array);
+    const int64_t last_offset =
+            arrow_validation_detail::check_arrow_offsets_range(*offsets, 
start, end);
+    const int64_t values_length = array.values() ? array.values()->length() : 
0;
+    if (UNLIKELY(last_offset > values_length)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "offsets exceed values length: last_offset={}, 
values_length={}",
+                last_offset, values_length);
+    }
+}
+
+// Validate Map offsets before reading offsets or recursing into keys/items.
+inline void check_arrow_map_offsets(const arrow::MapArray& array, int64_t 
start, int64_t end) {
+    check_arrow_array_range(array, start, end);
+    const auto offsets = 
arrow_validation_detail::get_int32_offsets_array(array);
+    const int64_t last_offset =
+            arrow_validation_detail::check_arrow_offsets_range(*offsets, 
start, end);
+    const int64_t keys_length = array.keys() ? array.keys()->length() : 0;
+    if (UNLIKELY(last_offset > keys_length)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "offsets exceed keys length: last_offset={}, 
keys_length={}", last_offset,
+                keys_length);
+    }
+    const int64_t items_length = array.items() ? array.items()->length() : 0;
+    if (UNLIKELY(last_offset > items_length)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "offsets exceed items length: last_offset={}, 
items_length={}", last_offset,
+                items_length);
+    }
+}
+
+// Validate String/Binary offsets buffer before value_offset(), value_length(),
+// raw_value_offsets(), or manual offset reads. Variable-width Arrow arrays 
need
+// offset + length + 1 offset entries.
+template <typename ArrowBinaryArray>
+inline void check_arrow_binary_offsets_buffer(const ArrowBinaryArray& array) {
+    check_arrow_validity_bitmap(array);
+    const auto& buffers = array.data()->buffers;
+    if (UNLIKELY(buffers.size() <= 1 || !buffers[1])) {
+        arrow_validation_detail::throw_invalid_arrow(array, "offsets buffer is 
missing");
+    }
+
+    const size_t offset = static_cast<size_t>(array.offset());
+    const size_t length = static_cast<size_t>(array.length());
+    if (UNLIKELY(offset > std::numeric_limits<size_t>::max() - length)) {
+        arrow_validation_detail::throw_invalid_arrow(array, "offsets entry 
count overflow");
+    }
+    const size_t count = offset + length;
+    if (UNLIKELY(count == std::numeric_limits<size_t>::max())) {
+        arrow_validation_detail::throw_invalid_arrow(array, "offsets entry 
count overflow");
+    }
+    const size_t count_plus_one = count + 1;
+    if (UNLIKELY(count_plus_one > std::numeric_limits<size_t>::max() /
+                                          sizeof(typename 
ArrowBinaryArray::offset_type))) {
+        arrow_validation_detail::throw_invalid_arrow(array, "offsets buffer 
size overflow");
+    }
+
+    const size_t required = count_plus_one * sizeof(typename 
ArrowBinaryArray::offset_type);
+    const size_t available = static_cast<size_t>(buffers[1]->size());
+    if (UNLIKELY(available < required)) {
+        arrow_validation_detail::throw_invalid_arrow(
+                array, "offsets buffer too small: {} bytes available, {} 
required", available,
+                required);
+    }
+}
+
+} // namespace doris
diff --git a/be/src/core/data_type_serde/data_type_array_serde.cpp 
b/be/src/core/data_type_serde/data_type_array_serde.cpp
index c54f3e1b46a..dd3a8e8ca73 100644
--- a/be/src/core/data_type_serde/data_type_array_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_array_serde.cpp
@@ -19,6 +19,7 @@
 
 #include <arrow/array/builder_nested.h>
 
+#include "common/config.h"
 #include "common/status.h"
 #include "core/assert_cast.h"
 #include "core/column/column.h"
@@ -27,6 +28,7 @@
 #include "core/data_type/data_type.h"
 #include "core/data_type/data_type_array.h"
 #include "core/data_type/get_least_supertype.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/complex_type_deserialize_util.h"
 #include "core/string_ref.h"
 #include "exprs/function/function_helpers.h"
@@ -323,6 +325,9 @@ Status DataTypeArraySerDe::read_column_from_arrow(IColumn& 
column, const arrow::
     const auto* concrete_array = dynamic_cast<const 
arrow::ListArray*>(arrow_array);
     auto arrow_offsets_array = concrete_array->offsets();
     auto* arrow_offsets = 
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
+    if (config::enable_arrow_input_validation) {
+        check_arrow_list_offsets(*concrete_array, start, end);
+    }
     auto prev_size = offsets_data.back();
     const auto* base_offsets_ptr = reinterpret_cast<const 
uint8_t*>(arrow_offsets->raw_values());
     const size_t offset_element_size = sizeof(int32_t);
diff --git a/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp 
b/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp
index e948529fac3..1ff362c6d5e 100644
--- a/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_date_or_datetime_serde.cpp
@@ -20,10 +20,12 @@
 #include <arrow/builder.h>
 #include <cctz/time_zone.h>
 
+#include "common/config.h"
 #include "common/status.h"
 #include "core/column/column_const.h"
 #include "core/data_type/data_type_decimal.h"
 #include "core/data_type/data_type_number.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/value/vdatetime_value.h"
 #include "exprs/function/cast/cast_base.h"
 #include "exprs/function/cast/cast_to_date_or_datetime_impl.hpp"
@@ -191,6 +193,9 @@ Status 
DataTypeDateSerDe<T>::_read_column_from_arrow(IColumn& column,
                                                      const arrow::Array* 
arrow_array, int64_t start,
                                                      int64_t end,
                                                      const cctz::time_zone& 
ctz) const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     auto& col_data = static_cast<ColumnVector<T>&>(column).get_data();
     int64_t divisor = 1;
     int64_t multiplier = 1;
diff --git a/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp 
b/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
index 0eb5e4d44a3..44402bb5b53 100644
--- a/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
@@ -23,11 +23,13 @@
 #include <chrono> // IWYU pragma: keep
 #include <cstdint>
 
+#include "common/config.h"
 #include "common/status.h"
 #include "core/column/column_const.h"
 #include "core/data_type/data_type_decimal.h"
 #include "core/data_type/data_type_number.h"
 #include "core/data_type/primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/decoded_column_view.h"
 #include "core/types.h"
 #include "core/value/vdatetime_value.h"
@@ -487,6 +489,9 @@ Status 
DataTypeDateTimeV2SerDe::read_column_from_arrow(IColumn& column,
                                                        const arrow::Array* 
arrow_array,
                                                        int64_t start, int64_t 
end,
                                                        const cctz::time_zone& 
ctz) const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     auto& col_data = static_cast<ColumnDateTimeV2&>(column).get_data();
     int64_t divisor = 1;
     if (arrow_array->type()->id() == arrow::Type::TIMESTAMP) {
diff --git a/be/src/core/data_type_serde/data_type_datev2_serde.cpp 
b/be/src/core/data_type_serde/data_type_datev2_serde.cpp
index 33e484ef946..c46ce4d1086 100644
--- a/be/src/core/data_type_serde/data_type_datev2_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_datev2_serde.cpp
@@ -23,10 +23,12 @@
 
 #include <cstdint>
 
+#include "common/config.h"
 #include "core/column/column_const.h"
 #include "core/data_type/data_type_decimal.h"
 #include "core/data_type/data_type_number.h"
 #include "core/data_type/define_primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/decoded_column_view.h"
 #include "core/types.h"
 #include "core/value/vdatetime_value.h"
@@ -110,6 +112,9 @@ Status DataTypeDateV2SerDe::write_column_to_arrow(const 
IColumn& column, const N
 Status DataTypeDateV2SerDe::read_column_from_arrow(IColumn& column, const 
arrow::Array* arrow_array,
                                                    int64_t start, int64_t end,
                                                    const cctz::time_zone& ctz) 
const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     auto& col_data = static_cast<ColumnDateV2&>(column).get_data();
     const auto* concrete_array = dynamic_cast<const 
arrow::Date32Array*>(arrow_array);
     const auto* base_ptr = reinterpret_cast<const 
uint8_t*>(concrete_array->raw_values());
diff --git a/be/src/core/data_type_serde/data_type_decimal_serde.cpp 
b/be/src/core/data_type_serde/data_type_decimal_serde.cpp
index 726263b705a..35208450c39 100644
--- a/be/src/core/data_type_serde/data_type_decimal_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_decimal_serde.cpp
@@ -27,11 +27,13 @@
 
 #include "arrow/type.h"
 #include "common/cast_set.h"
+#include "common/config.h"
 #include "common/consts.h"
 #include "core/column/column.h"
 #include "core/column/column_decimal.h"
 #include "core/data_type/data_type_decimal.h"
 #include "core/data_type/define_primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/decoded_column_view.h"
 #include "core/types.h"
 #include "exec/common/arithmetic_overflow.h"
@@ -456,6 +458,9 @@ Status 
DataTypeDecimalSerDe<T>::read_column_from_arrow(IColumn& column,
                                                        const arrow::Array* 
arrow_array,
                                                        int64_t start, int64_t 
end,
                                                        const cctz::time_zone& 
ctz) const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     auto& column_data = static_cast<ColumnDecimal<T>&>(column).get_data();
     // Decimal<Int128> for decimalv2
     // Decimal<Int128I> for deicmalv3
diff --git a/be/src/core/data_type_serde/data_type_ipv4_serde.cpp 
b/be/src/core/data_type_serde/data_type_ipv4_serde.cpp
index df6174ec319..ef6da635e13 100644
--- a/be/src/core/data_type_serde/data_type_ipv4_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_ipv4_serde.cpp
@@ -19,7 +19,9 @@
 
 #include <arrow/builder.h>
 
+#include "common/config.h"
 #include "core/column/column_const.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/types.h"
 #include "exprs/function/cast/cast_to_ip.h"
 #include "exprs/function/cast/cast_to_string.h"
@@ -113,6 +115,9 @@ Status DataTypeIPv4SerDe::write_column_to_arrow(const 
IColumn& column, const Nul
 Status DataTypeIPv4SerDe::read_column_from_arrow(IColumn& column, const 
arrow::Array* arrow_array,
                                                  int64_t start, int64_t end,
                                                  const cctz::time_zone& ctz) 
const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     auto& col_data = assert_cast<ColumnIPv4&>(column).get_data();
     int64_t row_count = end - start;
     /// buffers[0] is a null bitmap and buffers[1] are actual values
diff --git a/be/src/core/data_type_serde/data_type_ipv6_serde.cpp 
b/be/src/core/data_type_serde/data_type_ipv6_serde.cpp
index 9c9ba1a2972..3239a4da498 100644
--- a/be/src/core/data_type_serde/data_type_ipv6_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_ipv6_serde.cpp
@@ -22,7 +22,9 @@
 #include <cstddef>
 #include <string>
 
+#include "common/config.h"
 #include "core/column/column_const.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/types.h"
 #include "exprs/function/cast/cast_to_ip.h"
 #include "exprs/function/cast/cast_to_string.h"
@@ -142,6 +144,9 @@ Status DataTypeIPv6SerDe::write_column_to_arrow(const 
IColumn& column, const Nul
 Status DataTypeIPv6SerDe::read_column_from_arrow(IColumn& column, const 
arrow::Array* arrow_array,
                                                  int64_t start, int64_t end,
                                                  const cctz::time_zone& ctz) 
const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     auto& col_data = assert_cast<ColumnIPv6&>(column).get_data();
     const auto* concrete_array = assert_cast<const 
arrow::StringArray*>(arrow_array);
     std::shared_ptr<arrow::Buffer> buffer = concrete_array->value_data();
diff --git a/be/src/core/data_type_serde/data_type_jsonb_serde.cpp 
b/be/src/core/data_type_serde/data_type_jsonb_serde.cpp
index d7111a412b2..e53e4d23a74 100644
--- a/be/src/core/data_type_serde/data_type_jsonb_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_jsonb_serde.cpp
@@ -22,8 +22,10 @@
 #include <memory>
 
 #include "arrow/array/builder_binary.h"
+#include "common/config.h"
 #include "common/exception.h"
 #include "common/status.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/value/jsonb_value.h"
 #include "exprs/json_functions.h"
 #include "util/jsonb_parser_simd.h"
@@ -116,6 +118,9 @@ Status DataTypeJsonbSerDe::write_column_to_arrow(const 
IColumn& column, const Nu
 Status DataTypeJsonbSerDe::read_column_from_arrow(IColumn& column, const 
arrow::Array* arrow_array,
                                                   int64_t start, int64_t end,
                                                   const cctz::time_zone& ctz) 
const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     if (arrow_array->type_id() == arrow::Type::STRING ||
         arrow_array->type_id() == arrow::Type::BINARY) {
         const auto* concrete_array = dynamic_cast<const 
arrow::BinaryArray*>(arrow_array);
diff --git a/be/src/core/data_type_serde/data_type_map_serde.cpp 
b/be/src/core/data_type_serde/data_type_map_serde.cpp
index 9721e6eed99..c906e6aa5ad 100644
--- a/be/src/core/data_type_serde/data_type_map_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_map_serde.cpp
@@ -18,11 +18,13 @@
 #include "core/data_type_serde/data_type_map_serde.h"
 
 #include "arrow/array/builder_nested.h"
+#include "common/config.h"
 #include "common/exception.h"
 #include "common/status.h"
 #include "core/column/column.h"
 #include "core/column/column_const.h"
 #include "core/column/column_map.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/complex_type_deserialize_util.h"
 #include "core/string_ref.h"
 #include "util/jsonb_document.h"
@@ -388,6 +390,9 @@ Status DataTypeMapSerDe::read_column_from_arrow(IColumn& 
column, const arrow::Ar
     const auto* concrete_map = dynamic_cast<const 
arrow::MapArray*>(arrow_array);
     auto arrow_offsets_array = concrete_map->offsets();
     auto* arrow_offsets = 
dynamic_cast<arrow::Int32Array*>(arrow_offsets_array.get());
+    if (config::enable_arrow_input_validation) {
+        check_arrow_map_offsets(*concrete_map, start, end);
+    }
     auto prev_size = offsets_data.back();
 
     const auto* base_offsets_ptr = reinterpret_cast<const 
uint8_t*>(arrow_offsets->raw_values());
diff --git a/be/src/core/data_type_serde/data_type_nullable_serde.cpp 
b/be/src/core/data_type_serde/data_type_nullable_serde.cpp
index 7c6ce46e1cd..d41c7a39208 100644
--- a/be/src/core/data_type_serde/data_type_nullable_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_nullable_serde.cpp
@@ -24,11 +24,13 @@
 #include <boost/iterator/iterator_facade.hpp>
 #include <vector>
 
+#include "common/config.h"
 #include "core/assert_cast.h"
 #include "core/column/column.h"
 #include "core/column/column_const.h"
 #include "core/column/column_nullable.h"
 #include "core/column/column_vector.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/data_type_serde.h"
 #include "core/data_type_serde/data_type_string_serde.h"
 #include "core/data_type_serde/decoded_column_view.h"
@@ -342,6 +344,10 @@ Status 
DataTypeNullableSerDe::read_column_from_arrow(IColumn& column,
                                                      const arrow::Array* 
arrow_array, int64_t start,
                                                      int64_t end,
                                                      const cctz::time_zone& 
ctz) const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_array_range(*arrow_array, start, end);
+        check_arrow_validity_bitmap(*arrow_array);
+    }
     auto& col = reinterpret_cast<ColumnNullable&>(column);
     NullMap& map_data = col.get_null_map_data();
     for (auto i = start; i < end; ++i) {
diff --git a/be/src/core/data_type_serde/data_type_number_serde.cpp 
b/be/src/core/data_type_serde/data_type_number_serde.cpp
index d788bbb2981..3900f25a6d6 100644
--- a/be/src/core/data_type_serde/data_type_number_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_number_serde.cpp
@@ -23,11 +23,13 @@
 #include <limits>
 #include <type_traits>
 
+#include "common/config.h"
 #include "common/exception.h"
 #include "common/status.h"
 #include "core/column/column_nullable.h"
 #include "core/data_type/define_primitive_type.h"
 #include "core/data_type/primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/data_type_serde.h"
 #include "core/data_type_serde/decoded_column_view.h"
 #include "core/packed_int128.h"
@@ -404,13 +406,19 @@ Status 
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
                                                       const arrow::Array* 
arrow_array,
                                                       int64_t start, int64_t 
end,
                                                       const cctz::time_zone& 
ctz) const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_array_range(*arrow_array, start, end);
+    }
     auto row_count = end - start;
     auto& col_data = static_cast<ColumnType&>(column).get_data();
 
     // now uint8 for bool
     if constexpr (T == TYPE_BOOLEAN) {
         const auto* concrete_array = dynamic_cast<const 
arrow::BooleanArray*>(arrow_array);
-        for (size_t bool_i = 0; bool_i != 
static_cast<size_t>(concrete_array->length()); ++bool_i) {
+        if (config::enable_arrow_input_validation) {
+            check_arrow_boolean_buffer(*concrete_array);
+        }
+        for (int64_t bool_i = start; bool_i != end; ++bool_i) {
             col_data.emplace_back(concrete_array->Value(bool_i));
         }
         return Status::OK();
@@ -419,7 +427,11 @@ Status 
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
     // only for largeint(int128) type
     if (arrow_array->type_id() == arrow::Type::STRING) {
         const auto* concrete_array = dynamic_cast<const 
arrow::StringArray*>(arrow_array);
+        if (config::enable_arrow_input_validation) {
+            check_arrow_binary_offsets_buffer(*concrete_array);
+        }
         std::shared_ptr<arrow::Buffer> buffer = concrete_array->value_data();
+        const size_t buffer_size = buffer ? 
static_cast<size_t>(buffer->size()) : 0;
         CastParameters params;
 
         const auto* offsets_data = concrete_array->value_offsets()->data();
@@ -430,13 +442,17 @@ Status 
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
                 auto end_offset =
                         unaligned_load<int32_t>(offsets_data + (offset_i + 1) 
* offset_size);
 
-                const auto* raw_data = buffer->data() + start_offset;
                 const auto raw_data_len = end_offset - start_offset;
-
+                if (config::enable_arrow_input_validation) {
+                    check_arrow_value_range(*concrete_array, start_offset, 
raw_data_len,
+                                            buffer_size);
+                }
                 if (raw_data_len == 0) {
                     col_data.emplace_back(
                             typename PrimitiveTypeTraits<T>::CppType()); // 
Int128() is NULL
                 } else {
+                    const auto* raw_data =
+                            reinterpret_cast<const char*>(buffer->data() + 
start_offset);
                     if constexpr (T == TYPE_DATETIMEV2 || T == 
TYPE_TIMESTAMPTZ) {
                         StringRef str_ref(raw_data, raw_data_len);
                         UInt64 val = 0;
@@ -487,6 +503,10 @@ Status 
DataTypeNumberSerDe<T>::read_column_from_arrow(IColumn& column,
     }
 
     /// buffers[0] is a null bitmap and buffers[1] are actual values
+    if (config::enable_arrow_input_validation) {
+        check_arrow_fixed_width_buffer(*arrow_array,
+                                       sizeof(typename 
PrimitiveTypeTraits<T>::CppType));
+    }
     std::shared_ptr<arrow::Buffer> buffer = arrow_array->data()->buffers[1];
 
     // Handle empty array case: buffer can be null when row_count is 0.
diff --git a/be/src/core/data_type_serde/data_type_string_serde.cpp 
b/be/src/core/data_type_serde/data_type_string_serde.cpp
index 4c7c9d02475..bd1c6e5b4f1 100644
--- a/be/src/core/data_type_serde/data_type_string_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_string_serde.cpp
@@ -20,8 +20,10 @@
 #include <array>
 #include <cstring>
 
+#include "common/config.h"
 #include "core/column/column_string.h"
 #include "core/data_type/define_primitive_type.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/decoded_column_view.h"
 #include "util/jsonb_document_cast.h"
 #include "util/jsonb_utils.h"
@@ -406,7 +408,12 @@ Status 
DataTypeStringSerDeBase<ColumnType>::read_column_from_arrow(
     if (arrow_array->type_id() == arrow::Type::STRING ||
         arrow_array->type_id() == arrow::Type::BINARY) {
         const auto* concrete_array = dynamic_cast<const 
arrow::BinaryArray*>(arrow_array);
+        if (config::enable_arrow_input_validation) {
+            check_arrow_array_range(*concrete_array, start, end);
+            check_arrow_binary_offsets_buffer(*concrete_array);
+        }
         std::shared_ptr<arrow::Buffer> buffer = concrete_array->value_data();
+        const size_t buffer_size = buffer ? 
static_cast<size_t>(buffer->size()) : 0;
         const uint8_t* offsets_data = concrete_array->value_offsets()->data();
         const size_t offset_size = sizeof(int32_t);
 
@@ -417,16 +424,23 @@ Status 
DataTypeStringSerDeBase<ColumnType>::read_column_from_arrow(
                         unaligned_load<int32_t>(offsets_data + (offset_i + 1) 
* offset_size);
 
                 int32_t length = end_offset - start_offset;
-                const auto* raw_data = buffer->data() + start_offset;
-
-                assert_cast<ColumnType&>(column).insert_data(
-                        reinterpret_cast<const char*>(raw_data), length);
+                if (config::enable_arrow_input_validation) {
+                    check_arrow_value_range(*concrete_array, start_offset, 
length, buffer_size);
+                }
+                // insert_data() does not read the input pointer when length 
is zero.
+                const auto* raw_data = reinterpret_cast<const 
char*>(buffer->data() + start_offset);
+                assert_cast<ColumnType&>(column).insert_data(raw_data, length);
             } else {
                 assert_cast<ColumnType&>(column).insert_default();
             }
         }
     } else if (arrow_array->type_id() == arrow::Type::FIXED_SIZE_BINARY) {
         const auto* concrete_array = dynamic_cast<const 
arrow::FixedSizeBinaryArray*>(arrow_array);
+        if (config::enable_arrow_input_validation) {
+            check_arrow_array_range(*concrete_array, start, end);
+            check_arrow_fixed_width_buffer(*concrete_array,
+                                           
static_cast<size_t>(concrete_array->byte_width()));
+        }
         uint32_t width = concrete_array->byte_width();
 
         for (auto offset_i = start; offset_i < end; ++offset_i) {
@@ -440,13 +454,24 @@ Status 
DataTypeStringSerDeBase<ColumnType>::read_column_from_arrow(
     } else if (arrow_array->type_id() == arrow::Type::LARGE_STRING ||
                arrow_array->type_id() == arrow::Type::LARGE_BINARY) {
         const auto* concrete_array = dynamic_cast<const 
arrow::LargeBinaryArray*>(arrow_array);
+        if (config::enable_arrow_input_validation) {
+            check_arrow_array_range(*concrete_array, start, end);
+            check_arrow_binary_offsets_buffer(*concrete_array);
+        }
         std::shared_ptr<arrow::Buffer> buffer = concrete_array->value_data();
+        const size_t buffer_size = buffer ? 
static_cast<size_t>(buffer->size()) : 0;
 
         for (auto offset_i = start; offset_i < end; ++offset_i) {
             if (!concrete_array->IsNull(offset_i)) {
-                const auto* raw_data = buffer->data() + 
concrete_array->value_offset(offset_i);
-                assert_cast<ColumnType&>(column).insert_data(
-                        (char*)raw_data, 
concrete_array->value_length(offset_i));
+                const auto value_offset = 
concrete_array->value_offset(offset_i);
+                const auto value_length = 
concrete_array->value_length(offset_i);
+                if (config::enable_arrow_input_validation) {
+                    check_arrow_value_range(*concrete_array, value_offset, 
value_length,
+                                            buffer_size);
+                }
+                // insert_data() does not read the input pointer when length 
is zero.
+                const auto* raw_data = reinterpret_cast<const 
char*>(buffer->data() + value_offset);
+                assert_cast<ColumnType&>(column).insert_data(raw_data, 
value_length);
             } else {
                 assert_cast<ColumnType&>(column).insert_default();
             }
diff --git a/be/src/core/data_type_serde/data_type_struct_serde.cpp 
b/be/src/core/data_type_serde/data_type_struct_serde.cpp
index 0530e1fce66..15960382c87 100644
--- a/be/src/core/data_type_serde/data_type_struct_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_struct_serde.cpp
@@ -20,10 +20,12 @@
 #include <algorithm>
 
 #include "arrow/array/builder_nested.h"
+#include "common/config.h"
 #include "common/status.h"
 #include "core/column/column.h"
 #include "core/column/column_const.h"
 #include "core/column/column_struct.h"
+#include "core/data_type_serde/arrow_validation.h"
 #include "core/data_type_serde/complex_type_deserialize_util.h"
 #include "core/data_type_serde/data_type_serde.h"
 #include "core/string_ref.h"
@@ -417,6 +419,9 @@ Status DataTypeStructSerDe::write_column_to_arrow(const 
IColumn& column, const N
 Status DataTypeStructSerDe::read_column_from_arrow(IColumn& column, const 
arrow::Array* arrow_array,
                                                    int64_t start, int64_t end,
                                                    const cctz::time_zone& ctz) 
const {
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
     auto& struct_column = static_cast<ColumnStruct&>(column);
     const auto* concrete_struct = dynamic_cast<const 
arrow::StructArray*>(arrow_array);
     DCHECK_EQ(struct_column.tuple_size(), concrete_struct->num_fields());
diff --git 
a/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp 
b/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp
new file mode 100644
index 00000000000..fdce7af8f89
--- /dev/null
+++ b/be/test/core/data_type_serde/data_type_serde_arrow_validation_test.cpp
@@ -0,0 +1,275 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <arrow/api.h>
+#include <cctz/time_zone.h>
+#include <gtest/gtest-message.h>
+#include <gtest/gtest-test-part.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "common/config.h"
+#include "common/exception.h"
+#include "core/column/column_array.h"
+#include "core/column/column_map.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/data_type/primitive_type.h"
+#include "core/data_type_serde/data_type_array_serde.h"
+#include "core/data_type_serde/data_type_map_serde.h"
+#include "core/data_type_serde/data_type_nullable_serde.h"
+#include "core/data_type_serde/data_type_number_serde.h"
+#include "core/data_type_serde/data_type_string_serde.h"
+
+namespace doris {
+namespace {
+
+class ScopedArrowInputValidation {
+public:
+    explicit ScopedArrowInputValidation(bool enabled)
+            : _old_value(config::enable_arrow_input_validation) {
+        config::enable_arrow_input_validation = enabled;
+    }
+
+    ~ScopedArrowInputValidation() { config::enable_arrow_input_validation = 
_old_value; }
+
+private:
+    bool _old_value;
+};
+
+template <typename Func>
+void expect_invalid_arrow(Func&& func, std::string_view message) {
+    bool thrown = false;
+    try {
+        std::forward<Func>(func)();
+    } catch (const Exception& e) {
+        thrown = true;
+        EXPECT_EQ(e.code(), ErrorCode::INVALID_ARGUMENT) << e.to_string();
+    }
+    EXPECT_TRUE(thrown) << message;
+}
+
+std::shared_ptr<arrow::Buffer> wrap_offsets(const std::vector<int32_t>& 
offsets) {
+    return arrow::Buffer::Wrap(offsets);
+}
+
+struct StringArrayHolder {
+    StringArrayHolder(std::vector<int32_t> offsets_, std::string_view values_)
+            : offsets(std::move(offsets_)), values(values_) {
+        auto value_buffer = arrow::Buffer::Wrap(values.data(), values.size());
+        array = std::make_shared<arrow::StringArray>(offsets.size() - 1, 
wrap_offsets(offsets),
+                                                     value_buffer);
+    }
+
+    std::vector<int32_t> offsets;
+    std::string values;
+    std::shared_ptr<arrow::StringArray> array;
+};
+
+} // namespace
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortStringOffsetsBuffer) {
+    ScopedArrowInputValidation validation(true);
+
+    std::vector<int32_t> offsets = {0};
+    std::string_view values = "abc";
+    auto value_buffer = arrow::Buffer::Wrap(values.data(), values.size());
+    auto array = std::make_shared<arrow::StringArray>(1, 
wrap_offsets(offsets), value_buffer);
+    auto column = ColumnString::create();
+    DataTypeStringSerDe serde(TYPE_STRING);
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.get(), 0, 1,
+                                                               
cctz::utc_time_zone()));
+            },
+            "short string offsets buffer should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsStringValueRangeBeyondBuffer) {
+    ScopedArrowInputValidation validation(true);
+
+    StringArrayHolder array({0, 8}, "abc");
+    auto column = ColumnString::create();
+    DataTypeStringSerDe serde(TYPE_STRING);
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.array.get(), 0, 1,
+                                                               
cctz::utc_time_zone()));
+            },
+            "string value range beyond data buffer should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsNonMonotonicStringOffsets) {
+    ScopedArrowInputValidation validation(true);
+
+    StringArrayHolder array({3, 1}, "abcd");
+    auto column = ColumnString::create();
+    DataTypeStringSerDe serde(TYPE_STRING);
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.array.get(), 0, 1,
+                                                               
cctz::utc_time_zone()));
+            },
+            "non-monotonic string offsets should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortFixedWidthDataBuffer) {
+    ScopedArrowInputValidation validation(true);
+
+    std::vector<int64_t> values = {1};
+    auto data_buffer = arrow::Buffer::Wrap(values);
+    auto array = std::make_shared<arrow::Int64Array>(2, data_buffer);
+    auto column = ColumnInt64::create();
+    DataTypeNumberSerDe<TYPE_BIGINT> serde;
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.get(), 0, 2,
+                                                               
cctz::utc_time_zone()));
+            },
+            "short int64 data buffer should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsSlicedArrowArray) {
+    ScopedArrowInputValidation validation(true);
+
+    std::vector<int64_t> values = {1, 2, 3};
+    auto original = std::make_shared<arrow::Int64Array>(3, 
arrow::Buffer::Wrap(values));
+    auto sliced = original->Slice(1, 2);
+    auto column = ColumnInt64::create();
+    DataTypeNumberSerDe<TYPE_BIGINT> serde;
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
sliced.get(), 0, 2,
+                                                               
cctz::utc_time_zone()));
+            },
+            "sliced Arrow array should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortBooleanDataBitmap) {
+    ScopedArrowInputValidation validation(true);
+
+    std::vector<uint8_t> bits = {0xFF};
+    auto data_buffer = arrow::Buffer::Wrap(bits);
+    auto array = std::make_shared<arrow::BooleanArray>(9, data_buffer);
+    auto column = ColumnUInt8::create();
+    DataTypeNumberSerDe<TYPE_BOOLEAN> serde;
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.get(), 0, 9,
+                                                               
cctz::utc_time_zone()));
+            },
+            "short boolean data bitmap should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsShortValidityBitmap) {
+    ScopedArrowInputValidation validation(true);
+
+    std::vector<uint8_t> validity = {0xFF};
+    std::vector<int64_t> values(9, 1);
+    auto validity_buffer = arrow::Buffer::Wrap(validity);
+    auto data_buffer = arrow::Buffer::Wrap(values);
+    auto array = std::make_shared<arrow::Int64Array>(9, data_buffer, 
validity_buffer);
+    auto column = ColumnNullable::create(ColumnInt64::create(), 
ColumnUInt8::create());
+    auto nested_serde = std::make_shared<DataTypeNumberSerDe<TYPE_BIGINT>>();
+    DataTypeNullableSerDe serde(nested_serde);
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.get(), 0, 9,
+                                                               
cctz::utc_time_zone()));
+            },
+            "short validity bitmap should be rejected before IsNull");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, 
RejectsMissingValidityBitmapWithNullCount) {
+    ScopedArrowInputValidation validation(true);
+
+    std::vector<int64_t> values = {1, 2};
+    auto data_buffer = arrow::Buffer::Wrap(values);
+    auto array = std::make_shared<arrow::Int64Array>(2, data_buffer,
+                                                     
std::shared_ptr<arrow::Buffer>(), 1);
+    auto column = ColumnNullable::create(ColumnInt64::create(), 
ColumnUInt8::create());
+    auto nested_serde = std::make_shared<DataTypeNumberSerDe<TYPE_BIGINT>>();
+    DataTypeNullableSerDe serde(nested_serde);
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.get(), 0, 2,
+                                                               
cctz::utc_time_zone()));
+            },
+            "missing validity bitmap with positive null_count should be 
rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsListOffsetsBeyondValuesLength) {
+    ScopedArrowInputValidation validation(true);
+
+    StringArrayHolder values({0, 1}, "a");
+    std::vector<int32_t> offsets = {0, 2};
+    auto offsets_buffer = wrap_offsets(offsets);
+    auto array = 
std::make_shared<arrow::ListArray>(arrow::list(arrow::utf8()), 1, 
offsets_buffer,
+                                                    values.array);
+    auto column = ColumnArray::create(
+            ColumnNullable::create(ColumnString::create(), 
ColumnUInt8::create()),
+            ColumnOffset64::create());
+    auto nested_serde = std::make_shared<DataTypeNullableSerDe>(
+            std::make_shared<DataTypeStringSerDe>(TYPE_STRING));
+    DataTypeArraySerDe serde(nested_serde);
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.get(), 0, 1,
+                                                               
cctz::utc_time_zone()));
+            },
+            "list offsets beyond values length should be rejected");
+}
+
+TEST(DataTypeSerDeArrowValidationTest, RejectsMapOffsetsBeyondKeysLength) {
+    ScopedArrowInputValidation validation(true);
+
+    StringArrayHolder keys({0, 1}, "k");
+    std::vector<int64_t> item_values = {1};
+    auto items = std::make_shared<arrow::Int64Array>(1, 
arrow::Buffer::Wrap(item_values));
+    std::vector<int32_t> offsets = {0, 2};
+    auto offsets_buffer = wrap_offsets(offsets);
+    auto array = std::make_shared<arrow::MapArray>(arrow::map(arrow::utf8(), 
arrow::int64()), 1,
+                                                   offsets_buffer, keys.array, 
items);
+    auto column = ColumnMap::create(ColumnString::create(), 
ColumnInt64::create(),
+                                    ColumnOffset64::create());
+    DataTypeMapSerDe serde(std::make_shared<DataTypeStringSerDe>(TYPE_STRING),
+                           
std::make_shared<DataTypeNumberSerDe<TYPE_BIGINT>>());
+
+    expect_invalid_arrow(
+            [&] {
+                static_cast<void>(serde.read_column_from_arrow(*column, 
array.get(), 0, 1,
+                                                               
cctz::utc_time_zone()));
+            },
+            "map offsets beyond keys length should be rejected");
+}
+
+} // namespace doris


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to