github-actions[bot] commented on code in PR #65715:
URL: https://github.com/apache/doris/pull/65715#discussion_r3594834924
##########
be/src/core/data_type_serde/data_type_array_serde.cpp:
##########
@@ -322,29 +322,59 @@ Status
DataTypeArraySerDe::read_column_from_arrow(IColumn& column, const arrow::
const cctz::time_zone& ctz)
const {
auto& column_array = static_cast<ColumnArray&>(column);
auto& offsets_data = column_array.get_offsets();
- 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);
- 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);
- // convert to doris offset, start from offsets.back()
- offsets_data.emplace_back(prev_size + current_offset -
arrow_nested_start_offset);
- }
- return nested_serde->read_column_from_arrow(
- column_array.get_data(), concrete_array->values().get(),
arrow_nested_start_offset,
- arrow_nested_end_offset, ctz);
+
+ const auto read_list = [&](const auto* concrete_array) -> Status {
+ const int64_t arrow_nested_start_offset =
concrete_array->value_offset(start);
+ const int64_t arrow_nested_end_offset =
concrete_array->value_offset(end);
+ const auto prev_size = offsets_data.back();
+ for (int64_t i = start + 1; i <= end; ++i) {
+ // Convert Arrow offsets to Doris offsets, starting at
offsets.back().
+ offsets_data.emplace_back(prev_size +
concrete_array->value_offset(i) -
+ arrow_nested_start_offset);
+ }
+ return nested_serde->read_column_from_arrow(
+ column_array.get_data(), concrete_array->values().get(),
arrow_nested_start_offset,
+ arrow_nested_end_offset, ctz);
+ };
+
+ switch (arrow_array->type_id()) {
+ case arrow::Type::LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::ListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow ListArray, got {}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_list_offsets(*concrete_array, start, end);
+ }
+ return read_list(concrete_array);
+ }
+ case arrow::Type::LARGE_LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::LargeListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow LargeListArray, got
{}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
Review Comment:
[P1] Validate LargeList offsets before reading them
check_arrow_array_range() only validates the logical start/end; it does not
verify LargeList's offsets-buffer capacity, monotonic/non-negative values, or
final child bound. For example, a length-1 LargeListArray backed by the single
int64 offset {0} passes this check, after which read_list calls
value_offset(end = 1) past the buffer instead of returning INVALID_ARGUMENT.
Please add a 64-bit analogue of check_arrow_list_offsets before entering the
lambda and cover short, non-monotonic, negative, and child-overrun offsets with
malformed-input tests.
##########
be/src/core/data_type_serde/data_type_array_serde.cpp:
##########
@@ -322,29 +322,59 @@ Status
DataTypeArraySerDe::read_column_from_arrow(IColumn& column, const arrow::
const cctz::time_zone& ctz)
const {
auto& column_array = static_cast<ColumnArray&>(column);
auto& offsets_data = column_array.get_offsets();
- 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);
- 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);
- // convert to doris offset, start from offsets.back()
- offsets_data.emplace_back(prev_size + current_offset -
arrow_nested_start_offset);
- }
- return nested_serde->read_column_from_arrow(
- column_array.get_data(), concrete_array->values().get(),
arrow_nested_start_offset,
- arrow_nested_end_offset, ctz);
+
+ const auto read_list = [&](const auto* concrete_array) -> Status {
+ const int64_t arrow_nested_start_offset =
concrete_array->value_offset(start);
Review Comment:
[P1] Keep list offset loads unaligned-safe
This lambda regresses the existing LIST path from explicit unaligned loads
to Arrow's typed value_offset() accessor. Doris's own arrow_validation.h notes
that Buffer::Wrap/FFI offsets need not be aligned, while Arrow 17 implements
value_offset() as a direct typed dereference. A valid one-row ListArray whose
{0, 1} offsets start at an odd address therefore passes validation and then
hits alignment UB here; LargeList has the same problem. Please load the
validated 32/64-bit offsets with unaligned_load and add a valid
misaligned-buffer test.
##########
be/src/core/data_type_serde/data_type_varbinary_serde.cpp:
##########
@@ -115,6 +118,76 @@ Status DataTypeVarbinarySerDe::write_column_to_arrow(const
IColumn& column, cons
return Status::OK();
}
+Status DataTypeVarbinarySerDe::read_column_from_arrow(IColumn& column,
+ const arrow::Array*
arrow_array,
+ int64_t start, int64_t
end,
+ const cctz::time_zone&
ctz) const {
+ auto& varbinary_column = assert_cast<ColumnVarbinary&>(column);
+ if (arrow_array->type_id() == arrow::Type::STRING ||
+ arrow_array->type_id() == arrow::Type::BINARY) {
+ const auto* concrete_array = assert_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);
+ }
+ const auto& buffer = concrete_array->value_data();
Review Comment:
[P1] Validate VARBINARY value buffers and ranges
The enabled checks only prove that the offsets buffer has enough entries. A
one-row BinaryArray with offsets {0, 8} and a one-byte values buffer copies
eight bytes here, while {0, 0} with a missing values buffer dereferences
buffer->data(); LargeBinary/LargeString have the same gaps. Extreme signed
endpoints can also overflow while deriving length before a range check. Please
require value_data() to be present, load both endpoints safely, and validate
them as non-negative, non-decreasing, and within the values-buffer bound before
subtracting or forming pointers. Cover missing, oversized, reversed, and
extreme endpoints for both widths.
##########
be/src/core/data_type_serde/data_type_varbinary_serde.cpp:
##########
@@ -115,6 +118,76 @@ Status DataTypeVarbinarySerDe::write_column_to_arrow(const
IColumn& column, cons
return Status::OK();
}
+Status DataTypeVarbinarySerDe::read_column_from_arrow(IColumn& column,
+ const arrow::Array*
arrow_array,
+ int64_t start, int64_t
end,
+ const cctz::time_zone&
ctz) const {
+ auto& varbinary_column = assert_cast<ColumnVarbinary&>(column);
+ if (arrow_array->type_id() == arrow::Type::STRING ||
+ arrow_array->type_id() == arrow::Type::BINARY) {
+ const auto* concrete_array = assert_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);
+ }
+ const auto& buffer = concrete_array->value_data();
+ const uint8_t* offsets_data = concrete_array->value_offsets()->data();
+ constexpr size_t offset_size = sizeof(int32_t);
+
+ for (int64_t offset_i = start; offset_i < end; ++offset_i) {
+ if (concrete_array->IsNull(offset_i)) {
+ varbinary_column.insert_default();
+ continue;
+ }
+ int32_t start_offset = 0;
+ int32_t end_offset = 0;
+ memcpy(&start_offset, offsets_data + offset_i * offset_size,
offset_size);
+ memcpy(&end_offset, offsets_data + (offset_i + 1) * offset_size,
offset_size);
+ const auto length = end_offset - start_offset;
+ varbinary_column.insert_data(
+ reinterpret_cast<const char*>(buffer->data() +
start_offset), length);
+ }
+ } else if (arrow_array->type_id() == arrow::Type::FIXED_SIZE_BINARY) {
+ const auto* concrete_array = assert_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()));
+ }
+ const auto width = concrete_array->byte_width();
+ for (int64_t offset_i = start; offset_i < end; ++offset_i) {
+ if (concrete_array->IsNull(offset_i)) {
+ varbinary_column.insert_default();
+ } else {
+ varbinary_column.insert_data(
+ reinterpret_cast<const
char*>(concrete_array->GetValue(offset_i)), width);
+ }
+ }
+ } else if (arrow_array->type_id() == arrow::Type::LARGE_STRING ||
+ arrow_array->type_id() == arrow::Type::LARGE_BINARY) {
+ const auto* concrete_array = assert_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);
+ }
+ const auto& buffer = concrete_array->value_data();
+ for (int64_t offset_i = start; offset_i < end; ++offset_i) {
+ if (concrete_array->IsNull(offset_i)) {
+ varbinary_column.insert_default();
+ continue;
+ }
+ const auto value_offset = concrete_array->value_offset(offset_i);
Review Comment:
[P1] Read LargeBinary offsets without alignment assumptions
Unlike the 32-bit branch above, this calls Arrow's typed
value_offset()/value_length() accessors. In Arrow 17 those directly dereference
an int64_t pointer, but Doris explicitly documents that external
Buffer::Wrap/FFI offsets may be unaligned. Thus even valid {0, 1} offsets at an
odd address can hit alignment UB despite the existing capacity check. Please
load both int64 offsets from value_offsets()->data() with unaligned_load (or an
equivalent shared helper) and add a valid misaligned LargeBinary test.
##########
be/src/core/data_type_serde/data_type_array_serde.cpp:
##########
@@ -322,29 +322,59 @@ Status
DataTypeArraySerDe::read_column_from_arrow(IColumn& column, const arrow::
const cctz::time_zone& ctz)
const {
auto& column_array = static_cast<ColumnArray&>(column);
auto& offsets_data = column_array.get_offsets();
- 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);
- 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);
- // convert to doris offset, start from offsets.back()
- offsets_data.emplace_back(prev_size + current_offset -
arrow_nested_start_offset);
- }
- return nested_serde->read_column_from_arrow(
- column_array.get_data(), concrete_array->values().get(),
arrow_nested_start_offset,
- arrow_nested_end_offset, ctz);
+
+ const auto read_list = [&](const auto* concrete_array) -> Status {
+ const int64_t arrow_nested_start_offset =
concrete_array->value_offset(start);
+ const int64_t arrow_nested_end_offset =
concrete_array->value_offset(end);
+ const auto prev_size = offsets_data.back();
+ for (int64_t i = start + 1; i <= end; ++i) {
+ // Convert Arrow offsets to Doris offsets, starting at
offsets.back().
+ offsets_data.emplace_back(prev_size +
concrete_array->value_offset(i) -
+ arrow_nested_start_offset);
+ }
+ return nested_serde->read_column_from_arrow(
+ column_array.get_data(), concrete_array->values().get(),
arrow_nested_start_offset,
+ arrow_nested_end_offset, ctz);
+ };
+
+ switch (arrow_array->type_id()) {
+ case arrow::Type::LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::ListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow ListArray, got {}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_list_offsets(*concrete_array, start, end);
+ }
+ return read_list(concrete_array);
+ }
+ case arrow::Type::LARGE_LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::LargeListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow LargeListArray, got
{}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
+ }
+ return read_list(concrete_array);
+ }
+ case arrow::Type::FIXED_SIZE_LIST: {
+ const auto* concrete_array = dynamic_cast<const
arrow::FixedSizeListArray*>(arrow_array);
+ if (concrete_array == nullptr) {
+ return Status::InvalidArgument("Expected Arrow FixedSizeListArray,
got {}",
+ arrow_array->type()->name());
+ }
+ if (config::enable_arrow_input_validation) {
+ check_arrow_array_range(*concrete_array, start, end);
Review Comment:
[P1] Validate FixedSizeList offset arithmetic
This only checks the parent row range, but Arrow computes value_offset(i) as
signed list_size * i with no overflow check. A constructible FixedSizeList with
list_size = INT32_MAX and logical length/end 4,294,967,299 passes this check,
then overflows in value_offset(end) before the nested serde can reject the
empty/truncated child. Please compute start/end offsets with overflow-safe
arithmetic and validate child coverage before read_list, with a
huge-logical-length malformed test that requires no large allocation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]