Skip to content

Commit 4d7ab17

Browse files
committed
apacheGH-50504: [C++][Gandiva] fix out-of-bounds read in upper/lower/initcap
1 parent 0a6de2d commit 4d7ab17

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

cpp/src/gandiva/gdv_function_stubs_test.cc

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <gtest/gtest.h>
2323

2424
#include <limits>
25+
#include <memory>
2526

2627
#include "arrow/util/logging.h"
2728
#include "gandiva/execution_context.h"
@@ -649,6 +650,20 @@ TEST(TestGdvFnStubs, TestUpper) {
649650

650651
ctx.Reset();
651652

653+
// Truncated trailing multibyte glyph in an exact-sized buffer (no trailing NUL);
654+
// the lead byte claims more bytes than remain. Use new[] so the over-read trips
655+
// ASAN instead of landing on std::string's guaranteed terminator.
656+
{
657+
const int32_t trunc_len = 4;
658+
std::unique_ptr<char[]> trunc(new char[trunc_len]{'a', 'b', 'c', '\xc3'});
659+
out_str = gdv_fn_upper_utf8(ctx_ptr, trunc.get(), trunc_len, &out_len);
660+
EXPECT_EQ(out_len, 0);
661+
EXPECT_THAT(ctx.get_error(),
662+
::testing::HasSubstr(
663+
"unexpected byte \\c3 encountered while decoding utf8 string"));
664+
ctx.Reset();
665+
}
666+
652667
// Max Len Test
653668
out_len = -1;
654669
int32_t bad_len = std::numeric_limits<int32_t>::max() / 2 + 1;
@@ -752,6 +767,19 @@ TEST(TestGdvFnStubs, TestLower) {
752767
"unexpected byte \\c3 encountered while decoding utf8 string"));
753768
ctx.Reset();
754769

770+
// Truncated trailing 3-byte lead in an exact-sized buffer (no trailing NUL); only
771+
// the lead byte is present, so decoding must not read the missing continuation bytes.
772+
{
773+
const int32_t trunc_len = 4;
774+
std::unique_ptr<char[]> trunc(new char[trunc_len]{'a', 'b', 'c', '\xe0'});
775+
out_str = gdv_fn_lower_utf8(ctx_ptr, trunc.get(), trunc_len, &out_len);
776+
EXPECT_EQ(out_len, 0);
777+
EXPECT_THAT(ctx.get_error(),
778+
::testing::HasSubstr(
779+
"unexpected byte \\e0 encountered while decoding utf8 string"));
780+
ctx.Reset();
781+
}
782+
755783
std::string e(
756784
"åbÑg\xe0\xa0"
757785
"åBUå");
@@ -805,7 +833,7 @@ TEST(TestGdvFnStubs, TestInitCap) {
805833
EXPECT_EQ(std::string(out_str, out_len), "{Õhp,Pqśv}Ń+");
806834
EXPECT_FALSE(ctx.has_error());
807835

808-
out_str = gdv_fn_initcap_utf8(ctx_ptr, "sɦasasdsɦsd\"sdsdɦ", 19, &out_len);
836+
out_str = gdv_fn_initcap_utf8(ctx_ptr, "sɦasasdsɦsd\"sdsdɦ", 20, &out_len);
809837
EXPECT_EQ(std::string(out_str, out_len), "Sɦasasdsɦsd\"Sdsdɦ");
810838
EXPECT_FALSE(ctx.has_error());
811839

@@ -842,6 +870,19 @@ TEST(TestGdvFnStubs, TestInitCap) {
842870
"unexpected byte \\c3 encountered while decoding utf8 string"));
843871
ctx.Reset();
844872

873+
// Truncated trailing multibyte glyph in an exact-sized buffer (no trailing NUL);
874+
// the lead byte claims more bytes than remain and must not be decoded past the end.
875+
{
876+
const int32_t trunc_len = 4;
877+
std::unique_ptr<char[]> trunc(new char[trunc_len]{'a', 'b', 'c', '\xc3'});
878+
out_str = gdv_fn_initcap_utf8(ctx_ptr, trunc.get(), trunc_len, &out_len);
879+
EXPECT_EQ(out_len, 0);
880+
EXPECT_THAT(ctx.get_error(),
881+
::testing::HasSubstr(
882+
"unexpected byte \\c3 encountered while decoding utf8 string"));
883+
ctx.Reset();
884+
}
885+
845886
// Max Len Test
846887
out_len = -1;
847888
int32_t bad_len = std::numeric_limits<int32_t>::max() / 2 + 1;

cpp/src/gandiva/gdv_string_function_stubs.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,14 @@ const char* gdv_fn_lower_utf8(int64_t context, const char* data, int32_t data_le
276276
continue;
277277
}
278278

279+
// A truncated trailing glyph must be rejected before decoding, otherwise
280+
// UTF8Decode walks continuation bytes past data[data_len].
281+
if (char_len == 0 || i + char_len > data_len) {
282+
gdv_fn_set_error_for_invalid_utf8(context, data[i]);
283+
*out_len = 0;
284+
return "";
285+
}
286+
279287
// Control reaches here when we encounter a multibyte character
280288
const auto* in_char = (const uint8_t*)(data + i);
281289

@@ -353,6 +361,14 @@ const char* gdv_fn_upper_utf8(int64_t context, const char* data, int32_t data_le
353361
continue;
354362
}
355363

364+
// A truncated trailing glyph must be rejected before decoding, otherwise
365+
// UTF8Decode walks continuation bytes past data[data_len].
366+
if (char_len == 0 || i + char_len > data_len) {
367+
gdv_fn_set_error_for_invalid_utf8(context, data[i]);
368+
*out_len = 0;
369+
return "";
370+
}
371+
356372
// Control reaches here when we encounter a multibyte character
357373
const auto* in_char = (const uint8_t*)(data + i);
358374

@@ -571,6 +587,14 @@ const char* gdv_fn_initcap_utf8(int64_t context, const char* data, int32_t data_
571587

572588
char_len = gdv_fn_utf8_char_length(data[i]);
573589

590+
// A truncated trailing glyph must be rejected before decoding, otherwise
591+
// UTF8Decode walks continuation bytes past data[data_len].
592+
if (char_len == 0 || i + char_len > data_len) {
593+
gdv_fn_set_error_for_invalid_utf8(context, data[i]);
594+
*out_len = 0;
595+
return "";
596+
}
597+
574598
// Control reaches here when we encounter a multibyte character
575599
const auto* in_char = (const uint8_t*)(data + i);
576600

0 commit comments

Comments
 (0)