From 76dc2e042e9e059cc64dfd2b43f31bfdbfbefb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Wed, 23 Sep 2026 17:34:47 +0300 Subject: [PATCH 1/2] fix: validate unaligned offset buffers safely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- src/nanoarrow/common/array.c | 40 +++++++++++++++++++---------- src/nanoarrow/common/array_test.cc | 40 +++++++++++++++++++++++++++++ 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 20daed5ce..aee1d5f7e 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,16 @@ 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 520f6df9d..85b7244c8 100644 --- a/src/nanoarrow/common/array_test.cc +++ b/src/nanoarrow/common/array_test.cc @@ -2907,6 +2907,46 @@ 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 9c2033f06..1c1cd0bbc 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 /// From 46f59e5a4d7b0d05a30f3e184bf65d3afa38de29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Wed, 23 Sep 2026 17:48:20 +0300 Subject: [PATCH 2/2] style: format unaligned offset validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- src/nanoarrow/common/array.c | 10 ++++------ src/nanoarrow/common/array_test.cc | 10 ++++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/nanoarrow/common/array.c b/src/nanoarrow/common/array.c index aee1d5f7e..c6fb1cd9c 100644 --- a/src/nanoarrow/common/array.c +++ b/src/nanoarrow/common/array.c @@ -2120,16 +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_uint8 = - array_view->buffer_views[i].data.as_uint8 + - array_view->offset * sizeof(int32_t); + 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_uint8 = - array_view->buffer_views[i].data.as_uint8 + - array_view->offset * sizeof(int64_t); + 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 85b7244c8..02550a7ef 100644 --- a/src/nanoarrow/common/array_test.cc +++ b/src/nanoarrow/common/array_test.cc @@ -2923,8 +2923,9 @@ TEST(ArrayTest, ArrayViewTestValidateUnalignedOffsets) { 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); + EXPECT_EQ( + ArrowArrayViewValidate(&array_view, NANOARROW_VALIDATION_LEVEL_FULL, &error), + NANOARROW_OK); ArrowArrayViewReset(&array_view); } @@ -2941,8 +2942,9 @@ TEST(ArrayTest, ArrayViewTestValidateUnalignedOffsets) { 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); + EXPECT_EQ( + ArrowArrayViewValidate(&array_view, NANOARROW_VALIDATION_LEVEL_FULL, &error), + NANOARROW_OK); ArrowArrayViewReset(&array_view); } }