From 632da8bc079e550b496dffa17054d8f22f72ed21 Mon Sep 17 00:00:00 2001 From: abdul rawoof Date: Wed, 22 Jul 2026 12:55:50 +0530 Subject: [PATCH] GH-50592: [C++][Gandiva] fix out-of-bounds read in evaluate_return_char_length --- cpp/src/gandiva/precompiled/string_ops.cc | 13 ++++++++- .../gandiva/precompiled/string_ops_test.cc | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/cpp/src/gandiva/precompiled/string_ops.cc b/cpp/src/gandiva/precompiled/string_ops.cc index 9d771758bb9..9e2b6ebbb51 100644 --- a/cpp/src/gandiva/precompiled/string_ops.cc +++ b/cpp/src/gandiva/precompiled/string_ops.cc @@ -2022,8 +2022,19 @@ gdv_int32 evaluate_return_char_length(gdv_int32 text_len, gdv_int32 actual_text_ gdv_int32 mod = (return_length - actual_text_len) % fill_actual_text_len; gdv_int32 char_len = 0; gdv_int32 fill_index = 0; - for (gdv_int32 i = 0; i < mod; i++) { + for (gdv_int32 i = 0; i < mod && fill_index < fill_text_len; i++) { + // step over the glyph the same way utf8_length_ignore_invalid counted it, so the + // two stay in sync and the walk never leaves the fill buffer char_len = utf8_char_length(fill_text[fill_index]); + if (char_len == 0 || fill_index + char_len > fill_text_len) { + char_len = 1; + } + for (gdv_int32 j = 1; j < char_len; ++j) { + if ((fill_text[fill_index + j] & 0xC0) != 0x80) { + char_len = j; + break; + } + } fill_index += char_len; return_char_length += char_len; } diff --git a/cpp/src/gandiva/precompiled/string_ops_test.cc b/cpp/src/gandiva/precompiled/string_ops_test.cc index 3a16e4076b9..c0a1899fc71 100644 --- a/cpp/src/gandiva/precompiled/string_ops_test.cc +++ b/cpp/src/gandiva/precompiled/string_ops_test.cc @@ -1664,6 +1664,35 @@ TEST(TestStringOps, TestPadMalformedUtf8KeepsValidGlyph) { EXPECT_EQ(std::string(out_str, out_len), text_str + " "); } +TEST(TestStringOps, TestPadMalformedUtf8FillNoOverread) { + gandiva::ExecutionContext ctx; + uint64_t ctx_ptr = reinterpret_cast(&ctx); + gdv_int32 out_len = 0; + + // {0xE0, 'a', 'a'}: a 3-byte lead byte whose continuation bytes are missing. + // utf8_length_ignore_invalid() counts it as three glyphs, so the partial fill + // loop in evaluate_return_char_length() runs twice while its first step used + // to consume all three bytes and read past the end. The fill text is held in + // an exactly-sized heap buffer so any over-read trips AddressSanitizer. + const char bytes[] = {'\xE0', 'a', 'a'}; + const auto fill_len = static_cast(sizeof(bytes)); + std::unique_ptr fill(new char[fill_len]); + std::memcpy(fill.get(), bytes, fill_len); + const std::string fill_str(fill.get(), fill_len); + + // "ab" padded to width 7 takes 5 fill glyphs, each one byte wide here. + const std::string padding = fill_str + fill_str.substr(0, 2); + + const char* out_str = + lpad_utf8_int32_utf8(ctx_ptr, "ab", 2, 7, fill.get(), fill_len, &out_len); + EXPECT_EQ(out_len, 7); + EXPECT_EQ(std::string(out_str, out_len), padding + "ab"); + + out_str = rpad_utf8_int32_utf8(ctx_ptr, "ab", 2, 7, fill.get(), fill_len, &out_len); + EXPECT_EQ(out_len, 7); + EXPECT_EQ(std::string(out_str, out_len), "ab" + padding); +} + TEST(TestStringOps, TestRtrim) { gandiva::ExecutionContext ctx; uint64_t ctx_ptr = reinterpret_cast(&ctx);