Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions cpp/src/gandiva/gdv_function_stubs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <boost/crc.hpp>
#include <cstdio>
#include <limits>
#include <sstream>
#include <string>
#include <vector>
Expand All @@ -44,6 +45,9 @@ extern "C" {

ARROW_SUPPRESS_MISSING_DECLARATIONS_WARNING

// AES operates on 16 byte blocks for every supported key length.
static constexpr int64_t kAesBlockSize = 16;

static char mask_array[256] = {
(char)0, (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7,
(char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15,
Expand Down Expand Up @@ -366,19 +370,23 @@ const char* gdv_fn_aes_encrypt(int64_t context, const char* data, int32_t data_l
return "";
}

int64_t kAesBlockSize = 0;
if (key_data_len == 16 || key_data_len == 24 || key_data_len == 32) {
kAesBlockSize = static_cast<int64_t>(key_data_len);
} else {
if (key_data_len != 16 && key_data_len != 24 && key_data_len != 32) {
std::ostringstream oss;
oss << "invalid key length: " << key_data_len;
gdv_fn_context_set_error_msg(context, oss.str().c_str());
*out_len = 0;
return nullptr;
}

*out_len =
static_cast<int32_t>(arrow::bit_util::RoundUpToPowerOf2(data_len, kAesBlockSize));
int64_t alloc_len =
arrow::bit_util::RoundUpToPowerOf2(data_len, kAesBlockSize) + kAesBlockSize;
if (ARROW_PREDICT_FALSE(alloc_len > std::numeric_limits<int32_t>::max())) {
gdv_fn_context_set_error_msg(context,
"AES_ENCRYPT: would overflow maximum output size");
*out_len = 0;
return nullptr;
}
*out_len = static_cast<int32_t>(alloc_len);
char* ret = reinterpret_cast<char*>(gdv_fn_context_arena_malloc(context, *out_len));
if (ret == nullptr) {
std::string err_msg = "AES_ENCRYPT: could not allocate memory for ciphertext output";
Expand Down Expand Up @@ -412,19 +420,23 @@ const char* gdv_fn_aes_decrypt(int64_t context, const char* data, int32_t data_l
return "";
}

int64_t kAesBlockSize = 0;
if (key_data_len == 16 || key_data_len == 24 || key_data_len == 32) {
kAesBlockSize = static_cast<int64_t>(key_data_len);
} else {
if (key_data_len != 16 && key_data_len != 24 && key_data_len != 32) {
std::ostringstream oss;
oss << "invalid key length: " << key_data_len;
gdv_fn_context_set_error_msg(context, oss.str().c_str());
*out_len = 0;
return nullptr;
}

*out_len =
static_cast<int32_t>(arrow::bit_util::RoundUpToPowerOf2(data_len, kAesBlockSize));
int64_t alloc_len =
arrow::bit_util::RoundUpToPowerOf2(data_len, kAesBlockSize) + kAesBlockSize;
if (ARROW_PREDICT_FALSE(alloc_len > std::numeric_limits<int32_t>::max())) {
gdv_fn_context_set_error_msg(context,
"AES_DECRYPT: would overflow maximum output size");
*out_len = 0;
return nullptr;
}
*out_len = static_cast<int32_t>(alloc_len);
char* ret = reinterpret_cast<char*>(gdv_fn_context_arena_malloc(context, *out_len));
if (ret == nullptr) {
std::string err_msg = "Could not allocate memory for returning aes decrypt plaintext";
Expand Down
62 changes: 62 additions & 0 deletions cpp/src/gandiva/gdv_function_stubs_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1621,4 +1621,66 @@ TEST(TestGdvFnStubs, TestAesEncryptDecryptValidation) {
EXPECT_THAT(ctx.get_error(), ::testing::HasSubstr("invalid key length"));
ctx.Reset();
}

TEST(TestGdvFnStubs, TestAesEncryptBlockAlignedInput) {
gandiva::ExecutionContext ctx;
std::string key16 = "12345678abcdefgh";
auto key16_len = static_cast<int32_t>(key16.length());
int64_t ctx_ptr = reinterpret_cast<int64_t>(&ctx);

// A block-aligned input still gets a whole block of padding appended, so the
// ciphertext is one block longer than the input. Encrypting twice from the same
// context makes an undersized output buffer observable, because the second
// ciphertext lands on top of the first one.
std::string first = "aaaaaaaaaaaaaaaa";
std::string second = "bbbbbbbbbbbbbbbb";
auto first_len = static_cast<int32_t>(first.length());
auto second_len = static_cast<int32_t>(second.length());

int32_t first_cipher_len = 0;
const char* first_cipher = gdv_fn_aes_encrypt(
ctx_ptr, first.c_str(), first_len, key16.c_str(), key16_len, &first_cipher_len);
ASSERT_FALSE(ctx.has_error());
EXPECT_EQ(first_cipher_len, first_len + 16);

int32_t second_cipher_len = 0;
gdv_fn_aes_encrypt(ctx_ptr, second.c_str(), second_len, key16.c_str(), key16_len,
&second_cipher_len);
ASSERT_FALSE(ctx.has_error());

int32_t decrypted_len = 0;
const char* decrypted = gdv_fn_aes_decrypt(ctx_ptr, first_cipher, first_cipher_len,
key16.c_str(), key16_len, &decrypted_len);
ASSERT_FALSE(ctx.has_error());
EXPECT_EQ(first, std::string(decrypted, decrypted_len));
}

TEST(TestGdvFnStubs, TestAesOutputSizeOverflow) {
gandiva::ExecutionContext ctx;
std::string key16 = "12345678abcdefgh";
auto key16_len = static_cast<int32_t>(key16.length());
int64_t ctx_ptr = reinterpret_cast<int64_t>(&ctx);
std::string data = "a";

// Rounding up and adding the padding block pushes these lengths past INT32_MAX.
// The size check runs before the input is read, so the short buffer is fine.
for (int32_t data_len :
{std::numeric_limits<int32_t>::max() - 15, std::numeric_limits<int32_t>::max()}) {
int32_t out_len = -1;
const char* ret = gdv_fn_aes_encrypt(ctx_ptr, data.c_str(), data_len, key16.c_str(),
key16_len, &out_len);
EXPECT_EQ(ret, nullptr);
EXPECT_EQ(out_len, 0);
EXPECT_THAT(ctx.get_error(), ::testing::HasSubstr("would overflow"));
ctx.Reset();

out_len = -1;
ret = gdv_fn_aes_decrypt(ctx_ptr, data.c_str(), data_len, key16.c_str(), key16_len,
&out_len);
EXPECT_EQ(ret, nullptr);
EXPECT_EQ(out_len, 0);
EXPECT_THAT(ctx.get_error(), ::testing::HasSubstr("would overflow"));
ctx.Reset();
}
}
} // namespace gandiva
Loading