This is an automated email from the ASF dual-hosted git repository.
paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-nanoarrow.git
The following commit(s) were added to refs/heads/main by this push:
new 8f81815c fix: validate unaligned offset buffers safely (#946)
8f81815c is described below
commit 8f81815c7a233d3fc697f43c7aed42e82a4f33be
Author: Efe <[email protected]>
AuthorDate: Fri Sep 25 17:19:26 2026 +0300
fix: validate unaligned offset buffers safely (#946)
## Summary
Fixes #945 by making validation of Arrow offset buffers safe for
unaligned C Data Interface buffers.
## Changes
- Add byte-based `memcpy` helpers for reading 32-bit and 64-bit buffer
values.
- Use the helpers for default and full offset validation, including
string, list, and large variants.
- Add regression coverage for unaligned string and large-string offset
buffers.
## Testing
- Upstream reproducer with Clang UBSan: reproduced misaligned-load
failure.
- Patched bundle with Clang UBSan: passed; `ArrowArrayViewValidate: 0`
with no UBSan diagnostics.
- `cc -std=c99 -Wall -Wextra -Werror
-I/tmp/nanoarrow945-dist-fixed/include -Isrc -fsyntax-only
src/nanoarrow/common/array.c`: passed.
- `git diff --check`: passed.
- Full CMake/GoogleTest suite: not run because CMake and GoogleTest are
not installed in the local environment.
## Notes
The change keeps the existing validation results and avoids typed
dereferences of externally supplied offset buffers. AI assistance was
used during investigation and implementation; the submitter reviewed the
changed lines and validation results.
---------
Signed-off-by: Efe Gökdemir <[email protected]>
---
src/nanoarrow/common/array.c | 38 ++++++++++++++++++++-------------
src/nanoarrow/common/array_test.cc | 42 +++++++++++++++++++++++++++++++++++++
src/nanoarrow/common/inline_types.h | 14 +++++++++++++
3 files changed, 80 insertions(+), 14 deletions(-)
diff --git a/src/nanoarrow/common/array.c b/src/nanoarrow/common/array.c
index 20daed5c..c6fb1cd9 100644
--- a/src/nanoarrow/common/array.c
+++ b/src/nanoarrow/common/array.c
@@ -1820,14 +1820,16 @@ static int ArrowArrayViewValidateDefault(struct
ArrowArrayView* array_view,
case NANOARROW_TYPE_STRING:
case NANOARROW_TYPE_BINARY:
if (array_view->buffer_views[1].size_bytes != 0) {
- first_offset =
array_view->buffer_views[1].data.as_int32[array_view->offset];
+ first_offset =
ArrowBufferViewGetInt32Unsafe(&array_view->buffer_views[1],
+ array_view->offset);
if (first_offset < 0) {
ArrowErrorSet(error, "Expected first offset >= 0 but found %" PRId64,
first_offset);
return EINVAL;
}
- last_offset =
array_view->buffer_views[1].data.as_int32[offset_plus_length];
+ last_offset =
ArrowBufferViewGetInt32Unsafe(&array_view->buffer_views[1],
+ offset_plus_length);
if (last_offset < 0) {
ArrowErrorSet(error, "Expected last offset >= 0 but found %" PRId64,
last_offset);
@@ -1856,14 +1858,16 @@ static int ArrowArrayViewValidateDefault(struct
ArrowArrayView* array_view,
case NANOARROW_TYPE_LARGE_STRING:
case NANOARROW_TYPE_LARGE_BINARY:
if (array_view->buffer_views[1].size_bytes != 0) {
- first_offset =
array_view->buffer_views[1].data.as_int64[array_view->offset];
+ first_offset =
ArrowBufferViewGetInt64Unsafe(&array_view->buffer_views[1],
+ array_view->offset);
if (first_offset < 0) {
ArrowErrorSet(error, "Expected first offset >= 0 but found %" PRId64,
first_offset);
return EINVAL;
}
- last_offset =
array_view->buffer_views[1].data.as_int64[offset_plus_length];
+ last_offset =
ArrowBufferViewGetInt64Unsafe(&array_view->buffer_views[1],
+ offset_plus_length);
if (last_offset < 0) {
ArrowErrorSet(error, "Expected last offset >= 0 but found %" PRId64,
last_offset);
@@ -1905,14 +1909,16 @@ static int ArrowArrayViewValidateDefault(struct
ArrowArrayView* array_view,
case NANOARROW_TYPE_LIST:
case NANOARROW_TYPE_MAP:
if (array_view->buffer_views[1].size_bytes != 0) {
- first_offset =
array_view->buffer_views[1].data.as_int32[array_view->offset];
+ first_offset =
ArrowBufferViewGetInt32Unsafe(&array_view->buffer_views[1],
+ array_view->offset);
if (first_offset < 0) {
ArrowErrorSet(error, "Expected first offset >= 0 but found %" PRId64,
first_offset);
return EINVAL;
}
- last_offset =
array_view->buffer_views[1].data.as_int32[offset_plus_length];
+ last_offset =
ArrowBufferViewGetInt32Unsafe(&array_view->buffer_views[1],
+ offset_plus_length);
if (last_offset < 0) {
ArrowErrorSet(error, "Expected last offset >= 0 but found %" PRId64,
last_offset);
@@ -1933,14 +1939,16 @@ static int ArrowArrayViewValidateDefault(struct
ArrowArrayView* array_view,
case NANOARROW_TYPE_LARGE_LIST:
if (array_view->buffer_views[1].size_bytes != 0) {
- first_offset =
array_view->buffer_views[1].data.as_int64[array_view->offset];
+ first_offset =
ArrowBufferViewGetInt64Unsafe(&array_view->buffer_views[1],
+ array_view->offset);
if (first_offset < 0) {
ArrowErrorSet(error, "Expected first offset >= 0 but found %" PRId64,
first_offset);
return EINVAL;
}
- last_offset =
array_view->buffer_views[1].data.as_int64[offset_plus_length];
+ last_offset =
ArrowBufferViewGetInt64Unsafe(&array_view->buffer_views[1],
+ offset_plus_length);
if (last_offset < 0) {
ArrowErrorSet(error, "Expected last offset >= 0 but found %" PRId64,
last_offset);
@@ -2037,7 +2045,8 @@ static int ArrowAssertIncreasingInt32(struct
ArrowBufferView view,
}
for (int64_t i = 1; i < view.size_bytes / (int64_t)sizeof(int32_t); i++) {
- if (view.data.as_int32[i] < view.data.as_int32[i - 1]) {
+ if (ArrowBufferViewGetInt32Unsafe(&view, i) <
+ ArrowBufferViewGetInt32Unsafe(&view, i - 1)) {
ArrowErrorSet(error, "[%" PRId64 "] Expected element size >= 0", i);
return EINVAL;
}
@@ -2053,7 +2062,8 @@ static int ArrowAssertIncreasingInt64(struct
ArrowBufferView view,
}
for (int64_t i = 1; i < view.size_bytes / (int64_t)sizeof(int64_t); i++) {
- if (view.data.as_int64[i] < view.data.as_int64[i - 1]) {
+ if (ArrowBufferViewGetInt64Unsafe(&view, i) <
+ ArrowBufferViewGetInt64Unsafe(&view, i - 1)) {
ArrowErrorSet(error, "[%" PRId64 "] Expected element size >= 0", i);
return EINVAL;
}
@@ -2110,14 +2120,14 @@ static int ArrowArrayViewValidateFull(struct
ArrowArrayView* array_view,
}
if (array_view->layout.element_size_bits[i] == 32) {
struct ArrowBufferView sliced_offsets;
- sliced_offsets.data.as_int32 =
- array_view->buffer_views[i].data.as_int32 + array_view->offset;
+ sliced_offsets.data.as_uint8 =
array_view->buffer_views[i].data.as_uint8 +
+ array_view->offset * sizeof(int32_t);
sliced_offsets.size_bytes = (array_view->length + 1) *
sizeof(int32_t);
NANOARROW_RETURN_NOT_OK(ArrowAssertIncreasingInt32(sliced_offsets,
error));
} else {
struct ArrowBufferView sliced_offsets;
- sliced_offsets.data.as_int64 =
- array_view->buffer_views[i].data.as_int64 + array_view->offset;
+ sliced_offsets.data.as_uint8 =
array_view->buffer_views[i].data.as_uint8 +
+ array_view->offset * sizeof(int64_t);
sliced_offsets.size_bytes = (array_view->length + 1) *
sizeof(int64_t);
NANOARROW_RETURN_NOT_OK(ArrowAssertIncreasingInt64(sliced_offsets,
error));
}
diff --git a/src/nanoarrow/common/array_test.cc
b/src/nanoarrow/common/array_test.cc
index 520f6df9..02550a7e 100644
--- a/src/nanoarrow/common/array_test.cc
+++ b/src/nanoarrow/common/array_test.cc
@@ -2907,6 +2907,48 @@ TEST(ArrayTest, ArrayViewTestString) {
ArrowArrayViewReset(&array_view);
}
+TEST(ArrayTest, ArrayViewTestValidateUnalignedOffsets) {
+ struct ArrowError error;
+
+ {
+ struct ArrowArrayView array_view;
+ uint8_t offsets[1 + 3 * sizeof(int32_t)] = {};
+ const int32_t expected[] = {0, 1, 3};
+ memcpy(offsets + 1, expected, sizeof(expected));
+
+ ArrowArrayViewInitFromType(&array_view, NANOARROW_TYPE_STRING);
+ ArrowArrayViewSetLength(&array_view, 2);
+ array_view.buffer_views[1].data.as_uint8 = offsets + 1;
+ array_view.buffer_views[1].size_bytes = sizeof(expected);
+ array_view.buffer_views[2].data.as_uint8 = offsets;
+ array_view.buffer_views[2].size_bytes = 3;
+
+ EXPECT_EQ(
+ ArrowArrayViewValidate(&array_view, NANOARROW_VALIDATION_LEVEL_FULL,
&error),
+ NANOARROW_OK);
+ ArrowArrayViewReset(&array_view);
+ }
+
+ {
+ struct ArrowArrayView array_view;
+ uint8_t offsets[1 + 3 * sizeof(int64_t)] = {};
+ const int64_t expected[] = {0, 1, 3};
+ memcpy(offsets + 1, expected, sizeof(expected));
+
+ ArrowArrayViewInitFromType(&array_view, NANOARROW_TYPE_LARGE_STRING);
+ ArrowArrayViewSetLength(&array_view, 2);
+ array_view.buffer_views[1].data.as_uint8 = offsets + 1;
+ array_view.buffer_views[1].size_bytes = sizeof(expected);
+ array_view.buffer_views[2].data.as_uint8 = offsets;
+ array_view.buffer_views[2].size_bytes = 3;
+
+ EXPECT_EQ(
+ ArrowArrayViewValidate(&array_view, NANOARROW_VALIDATION_LEVEL_FULL,
&error),
+ NANOARROW_OK);
+ ArrowArrayViewReset(&array_view);
+ }
+}
+
TEST(ArrayTest, ArrayViewTestLargeString) {
struct ArrowArrayView array_view;
struct ArrowError error;
diff --git a/src/nanoarrow/common/inline_types.h
b/src/nanoarrow/common/inline_types.h
index 9c2033f0..1c1cd0bb 100644
--- a/src/nanoarrow/common/inline_types.h
+++ b/src/nanoarrow/common/inline_types.h
@@ -714,6 +714,20 @@ struct ArrowBufferView {
int64_t size_bytes;
};
+static inline int32_t ArrowBufferViewGetInt32Unsafe(const struct
ArrowBufferView* view,
+ int64_t i) {
+ int32_t value;
+ memcpy(&value, view->data.as_uint8 + i * sizeof(int32_t), sizeof(value));
+ return value;
+}
+
+static inline int64_t ArrowBufferViewGetInt64Unsafe(const struct
ArrowBufferView* view,
+ int64_t i) {
+ int64_t value;
+ memcpy(&value, view->data.as_uint8 + i * sizeof(int64_t), sizeof(value));
+ return value;
+}
+
/// \brief Array buffer allocation and deallocation
/// \ingroup nanoarrow-buffer
///