From a39367577efcbe7e0837e2888ba12638e0b09c12 Mon Sep 17 00:00:00 2001 From: abdul rawoof Date: Thu, 9 Jul 2026 12:21:11 +0530 Subject: [PATCH 1/3] GH-50440: [C++][Gandiva] fix out-of-bounds read in set_error_for_date --- cpp/src/gandiva/precompiled/time.cc | 2 +- cpp/src/gandiva/precompiled/time_test.cc | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/cpp/src/gandiva/precompiled/time.cc b/cpp/src/gandiva/precompiled/time.cc index 2b60f63651db..9a66e41bbb13 100644 --- a/cpp/src/gandiva/precompiled/time.cc +++ b/cpp/src/gandiva/precompiled/time.cc @@ -631,7 +631,7 @@ void set_error_for_date(gdv_int32 length, const char* input, const char* msg, int64_t execution_context) { int size = length + static_cast(strlen(msg)) + 1; char* error = reinterpret_cast(malloc(size)); - snprintf(error, size, "%s%s", msg, input); + snprintf(error, size, "%s%.*s", msg, length, input); gdv_fn_context_set_error_msg(execution_context, error); free(error); } diff --git a/cpp/src/gandiva/precompiled/time_test.cc b/cpp/src/gandiva/precompiled/time_test.cc index 4310490a228b..d2bd673b724e 100644 --- a/cpp/src/gandiva/precompiled/time_test.cc +++ b/cpp/src/gandiva/precompiled/time_test.cc @@ -18,6 +18,10 @@ #include #include +#include +#include +#include + #include "arrow/util/logging_internal.h" #include "gandiva/execution_context.h" #include "gandiva/precompiled/testing.h" @@ -59,6 +63,24 @@ TEST(TestTime, TestCastDate) { EXPECT_EQ(castDATE_date32(1), 86400000); } +TEST(TestTime, TestCastDateInvalidUnterminated) { + ExecutionContext context; + int64_t context_ptr = reinterpret_cast(&context); + + // The invalid-value error message is built from the raw input pointer. Hold + // the input in an exactly-sized heap buffer with no trailing NUL so that + // formatting the error must respect `length` instead of scanning for a NUL; + // any over-read past the buffer trips AddressSanitizer. + const char bytes[] = {'1', '9', '7', '2', '2', '2', '2', '2', '2', '2'}; + const auto length = static_cast(sizeof(bytes)); + std::unique_ptr input(new char[length]); + std::memcpy(input.get(), bytes, length); + + EXPECT_EQ(castDATE_utf8(context_ptr, input.get(), length), 0); + EXPECT_EQ(context.get_error(), "Not a valid date value 1972222222"); + context.Reset(); +} + TEST(TestTime, TestCastTimestamp) { ExecutionContext context; int64_t context_ptr = reinterpret_cast(&context); From 0f5c3ebaff9d5f8a807ea683dd9d1592c7e1144c Mon Sep 17 00:00:00 2001 From: abdul rawoof Date: Tue, 14 Jul 2026 10:28:39 +0530 Subject: [PATCH 2/3] GH-50440: [C++][Gandiva] cover timestamp and time cast error paths Signed-off-by: abdul rawoof --- cpp/src/gandiva/precompiled/time_test.cc | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cpp/src/gandiva/precompiled/time_test.cc b/cpp/src/gandiva/precompiled/time_test.cc index d2bd673b724e..eae43aa8c0fe 100644 --- a/cpp/src/gandiva/precompiled/time_test.cc +++ b/cpp/src/gandiva/precompiled/time_test.cc @@ -81,6 +81,38 @@ TEST(TestTime, TestCastDateInvalidUnterminated) { context.Reset(); } +TEST(TestTime, TestCastTimestampInvalidUnterminated) { + ExecutionContext context; + int64_t context_ptr = reinterpret_cast(&context); + + // castTIMESTAMP_utf8 reaches the same set_error_for_date helper, so exercise + // the over-read path from that entry point with an unterminated buffer too. + const std::string value = "2000-01-01 24:00:00"; + const auto length = static_cast(value.size()); + std::unique_ptr input(new char[length]); + std::memcpy(input.get(), value.data(), length); + + EXPECT_EQ(castTIMESTAMP_utf8(context_ptr, input.get(), length), 0); + EXPECT_EQ(context.get_error(), + "Not a valid time for timestamp value 2000-01-01 24:00:00"); + context.Reset(); +} + +TEST(TestTime, TestCastTimeInvalidUnterminated) { + ExecutionContext context; + int64_t context_ptr = reinterpret_cast(&context); + + // castTIME_utf8 reaches set_error_for_date as well. + const std::string value = "24H00H00"; + const auto length = static_cast(value.size()); + std::unique_ptr input(new char[length]); + std::memcpy(input.get(), value.data(), length); + + EXPECT_EQ(castTIME_utf8(context_ptr, input.get(), length), 0); + EXPECT_EQ(context.get_error(), "Invalid character in time 24H00H00"); + context.Reset(); +} + TEST(TestTime, TestCastTimestamp) { ExecutionContext context; int64_t context_ptr = reinterpret_cast(&context); From d7b626443cd717ca4a9c064f54b727843b580042 Mon Sep 17 00:00:00 2001 From: abdul rawoof Date: Wed, 15 Jul 2026 12:24:11 +0530 Subject: [PATCH 3/3] Simplify invalid-value tests with bounded input Signed-off-by: abdul rawoof --- cpp/src/gandiva/precompiled/time_test.cc | 44 ++++++++++-------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/cpp/src/gandiva/precompiled/time_test.cc b/cpp/src/gandiva/precompiled/time_test.cc index eae43aa8c0fe..514d2e468dc5 100644 --- a/cpp/src/gandiva/precompiled/time_test.cc +++ b/cpp/src/gandiva/precompiled/time_test.cc @@ -18,8 +18,6 @@ #include #include -#include -#include #include #include "arrow/util/logging_internal.h" @@ -67,17 +65,15 @@ TEST(TestTime, TestCastDateInvalidUnterminated) { ExecutionContext context; int64_t context_ptr = reinterpret_cast(&context); - // The invalid-value error message is built from the raw input pointer. Hold - // the input in an exactly-sized heap buffer with no trailing NUL so that - // formatting the error must respect `length` instead of scanning for a NUL; - // any over-read past the buffer trips AddressSanitizer. - const char bytes[] = {'1', '9', '7', '2', '2', '2', '2', '2', '2', '2'}; - const auto length = static_cast(sizeof(bytes)); - std::unique_ptr input(new char[length]); - std::memcpy(input.get(), bytes, length); - - EXPECT_EQ(castDATE_utf8(context_ptr, input.get(), length), 0); - EXPECT_EQ(context.get_error(), "Not a valid date value 1972222222"); + // The invalid-value error message is built from the raw input pointer. Pass a + // length that stops one byte short of the trailing sentinel so the formatter + // must respect `length` rather than scanning for a NUL; without the fix the + // sentinel leaks into the error message. + const std::string input = "197201234X"; + EXPECT_EQ(castDATE_utf8(context_ptr, input.data(), + static_cast(input.length()) - 1), + 0); + EXPECT_EQ(context.get_error(), "Not a valid date value 197201234"); context.Reset(); } @@ -86,13 +82,11 @@ TEST(TestTime, TestCastTimestampInvalidUnterminated) { int64_t context_ptr = reinterpret_cast(&context); // castTIMESTAMP_utf8 reaches the same set_error_for_date helper, so exercise - // the over-read path from that entry point with an unterminated buffer too. - const std::string value = "2000-01-01 24:00:00"; - const auto length = static_cast(value.size()); - std::unique_ptr input(new char[length]); - std::memcpy(input.get(), value.data(), length); - - EXPECT_EQ(castTIMESTAMP_utf8(context_ptr, input.get(), length), 0); + // the over-read path from that entry point too. + const std::string input = "2000-01-01 24:00:00X"; + EXPECT_EQ(castTIMESTAMP_utf8(context_ptr, input.data(), + static_cast(input.length()) - 1), + 0); EXPECT_EQ(context.get_error(), "Not a valid time for timestamp value 2000-01-01 24:00:00"); context.Reset(); @@ -103,12 +97,10 @@ TEST(TestTime, TestCastTimeInvalidUnterminated) { int64_t context_ptr = reinterpret_cast(&context); // castTIME_utf8 reaches set_error_for_date as well. - const std::string value = "24H00H00"; - const auto length = static_cast(value.size()); - std::unique_ptr input(new char[length]); - std::memcpy(input.get(), value.data(), length); - - EXPECT_EQ(castTIME_utf8(context_ptr, input.get(), length), 0); + const std::string input = "24H00H00X"; + EXPECT_EQ(castTIME_utf8(context_ptr, input.data(), + static_cast(input.length()) - 1), + 0); EXPECT_EQ(context.get_error(), "Invalid character in time 24H00H00"); context.Reset(); }