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
 ///

Reply via email to