This is an automated email from the ASF dual-hosted git repository.

kou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 45801eb4175 GH-50440: [C++][Gandiva] Fix out-of-bounds read in 
set_error_for_date (#50441)
45801eb4175 is described below

commit 45801eb41757eed06c3ed34eadd92f35e1ce3741
Author: Abdul Rawoof Khan <[email protected]>
AuthorDate: Thu Oct 1 12:18:30 2026 +0530

    GH-50440: [C++][Gandiva] Fix out-of-bounds read in set_error_for_date 
(#50441)
    
    ### Rationale for this change
    
    `set_error_for_date` formats the invalid-date/timestamp message with 
`snprintf(error, size, "%s%s", msg, input)`, but `input` is a Gandiva string 
passed as pointer plus `length` and is not NUL-terminated, so the `%s` 
conversion scans past the `length` bytes looking for a terminator and 
over-reads the values buffer (past its end for the last value). It is reached 
from `castDATE_utf8`/`castTIMESTAMP_utf8` on any invalid `CAST(str AS 
DATE/TIMESTAMP)` input. Switching to the bounded `%.*s [...]
    
    ### What changes are included in this PR?
    
    Use `%.*s` with `length` in `set_error_for_date`, which covers all of its 
call sites in both `castDATE_utf8` and `castTIMESTAMP_utf8`.
    
    ### Are these changes tested?
    
    Yes. Added `TestTime.TestCastDateInvalidUnterminated`, which feeds an 
invalid date held in an exactly-sized heap buffer with no trailing NUL so the 
over-read trips AddressSanitizer, and checks the error string is bounded to the 
input length.
    
    ### Are there any user-facing changes?
    
    No.
    
    **This PR contains a "Critical Fix".** It fixes a heap out-of-bounds read 
reachable from user-supplied CAST-to-date/timestamp input.
    * GitHub Issue: #50440
    
    Authored-by: abdul rawoof <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 cpp/src/gandiva/precompiled/time.cc      |  2 +-
 cpp/src/gandiva/precompiled/time_test.cc | 54 ++++++++++++++++++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/cpp/src/gandiva/precompiled/time.cc 
b/cpp/src/gandiva/precompiled/time.cc
index 2b60f63651d..9a66e41bbb1 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<int>(strlen(msg)) + 1;
   char* error = reinterpret_cast<char*>(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 4310490a228..a45aa353218 100644
--- a/cpp/src/gandiva/precompiled/time_test.cc
+++ b/cpp/src/gandiva/precompiled/time_test.cc
@@ -18,6 +18,10 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <cstring>
+#include <memory>
+#include <string>
+
 #include "arrow/util/logging_internal.h"
 #include "gandiva/execution_context.h"
 #include "gandiva/precompiled/testing.h"
@@ -59,6 +63,56 @@ TEST(TestTime, TestCastDate) {
   EXPECT_EQ(castDATE_date32(1), 86400000);
 }
 
+TEST(TestTime, TestCastDateInvalidUnterminated) {
+  ExecutionContext context;
+  int64_t context_ptr = reinterpret_cast<int64_t>(&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 std::string value = "197201234";
+  const auto length = static_cast<int32_t>(value.size());
+  std::unique_ptr<char[]> input(new char[length]);
+  std::memcpy(input.get(), value.data(), length);
+
+  EXPECT_EQ(castDATE_utf8(context_ptr, input.get(), length), 0);
+  EXPECT_EQ(context.get_error(), "Not a valid date value 197201234");
+  context.Reset();
+}
+
+TEST(TestTime, TestCastTimestampInvalidUnterminated) {
+  ExecutionContext context;
+  int64_t context_ptr = reinterpret_cast<int64_t>(&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<int32_t>(value.size());
+  std::unique_ptr<char[]> 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<int64_t>(&context);
+
+  // castTIME_utf8 reaches set_error_for_date as well.
+  const std::string value = "24H00H00";
+  const auto length = static_cast<int32_t>(value.size());
+  std::unique_ptr<char[]> 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<int64_t>(&context);

Reply via email to