Skip to content

Commit a58eb6f

Browse files
authored
MONGOCRYPT-917 fix padding calculation for prefix/suffix (#1177)
Addresses issue ID:2
1 parent 7452521 commit a58eb6f

3 files changed

Lines changed: 48 additions & 5 deletions

File tree

src/mc-text-search-str-encode.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ static mc_affix_set_t *generate_prefix_or_suffix_tree(const mc_utf8_string_with_
3838
BSON_ASSERT_PARAM(base_str);
3939
BSON_ASSERT_PARAM(out_msize);
4040
// We encrypt (unfolded string + 5 bytes of extra BSON info) with a 16-byte block cipher.
41-
uint32_t encrypted_len = 16 * (uint32_t)((unfolded_byte_len + OVERHEAD_BYTES + 15) / 16);
41+
// PKCS7 adds an extra 16-byte padding for 16-byte aligned plaintext lengths.
42+
uint32_t encrypted_len = 16 * (uint32_t)((unfolded_byte_len + OVERHEAD_BYTES + 16) / 16);
4243
// Max len of a string that has this encrypted len.
4344
uint32_t padded_len = encrypted_len - OVERHEAD_BYTES;
4445
if (padded_len < lb) {
@@ -113,7 +114,8 @@ static mc_substring_set_t *generate_substring_tree(const mc_utf8_string_with_bad
113114
BSON_ASSERT_PARAM(spec);
114115
BSON_ASSERT_PARAM(out_msize);
115116
// We encrypt (unfolded string + 5 bytes of extra BSON info) with a 16-byte block cipher.
116-
uint32_t encrypted_len = 16 * (uint32_t)((unfolded_byte_len + OVERHEAD_BYTES + 15) / 16);
117+
// PKCS7 adds an extra 16-byte padding for 16-byte aligned plaintext lengths.
118+
uint32_t encrypted_len = 16 * (uint32_t)((unfolded_byte_len + OVERHEAD_BYTES + 16) / 16);
117119
// Max len of a string that has this encrypted len.
118120
uint32_t padded_len = encrypted_len - OVERHEAD_BYTES;
119121
if (padded_len < spec->lb) {

test/test-mc-text-search-str-encode.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ static uint32_t get_utf8_codepoint_length(const char *buf, uint32_t len) {
3636
return codepoint_len;
3737
}
3838

39+
static uint32_t calculate_padded_length(uint32_t byte_len) {
40+
const uint32_t bson_overhead = 5;
41+
// Calculate length with CBC padding:
42+
uint32_t encrypted_len = _mcFLE2v2AEADAlgorithm()->get_ciphertext_len(byte_len + bson_overhead, NULL)
43+
- MONGOCRYPT_IV_LEN - MONGOCRYPT_HMAC_LEN;
44+
return encrypted_len - bson_overhead;
45+
}
46+
3947
static void test_nofold_suffix_prefix_case(_mongocrypt_tester_t *tester,
4048
const char *str,
4149
uint32_t lb,
@@ -52,7 +60,7 @@ static void test_nofold_suffix_prefix_case(_mongocrypt_tester_t *tester,
5260
uint32_t byte_len = (uint32_t)strlen(str);
5361
uint32_t unfolded_codepoint_len = byte_len == 0 ? 1 : get_utf8_codepoint_length(str, byte_len);
5462
uint32_t folded_codepoint_len = byte_len == 0 ? 0 : unfolded_codepoint_len - foldable_codepoints;
55-
uint32_t padded_len = 16 * (uint32_t)((byte_len + 5 + 15) / 16) - 5;
63+
uint32_t padded_len = calculate_padded_length(byte_len);
5664
uint32_t max_affix_len = BSON_MIN(ub, folded_codepoint_len);
5765
uint32_t n_real_affixes = max_affix_len >= lb ? max_affix_len - lb + 1 : 0;
5866
uint32_t n_affixes = BSON_MIN(ub, padded_len) - lb + 1;
@@ -234,7 +242,7 @@ static void test_nofold_substring_case(_mongocrypt_tester_t *tester,
234242
uint32_t byte_len = (uint32_t)strlen(str);
235243
uint32_t unfolded_codepoint_len = byte_len == 0 ? 1 : get_utf8_codepoint_length(str, byte_len);
236244
uint32_t folded_codepoint_len = byte_len == 0 ? 0 : unfolded_codepoint_len - foldable_codepoints;
237-
uint32_t padded_len = 16 * (uint32_t)((byte_len + 5 + 15) / 16) - 5;
245+
uint32_t padded_len = calculate_padded_length(byte_len);
238246
uint32_t n_substrings = calc_number_of_substrings(BSON_MIN(padded_len, mlen), lb, ub);
239247

240248
mongocrypt_status_t *status = mongocrypt_status_new();
@@ -572,6 +580,9 @@ static void _test_text_search_str_encode_suffix_prefix(_mongocrypt_tester_t *tes
572580
bson_free(short_s);
573581
bson_free(medium_s);
574582
bson_free(long_s);
583+
584+
// Test fixed strings where byte_len+5 is a multiple of 16. Regression test for MONGOCRYPT-917
585+
test_nofold_suffix_prefix_case(tester, "abcdefghijk" /* 11 chars */, 1, 30, false, false, 0);
575586
}
576587

577588
static void substring_run_folding_case(_mongocrypt_tester_t *tester,
@@ -1125,6 +1136,9 @@ static void _test_text_search_str_encode_substring(_mongocrypt_tester_t *tester)
11251136
bson_free(short_s);
11261137
bson_free(medium_s);
11271138
bson_free(long_s);
1139+
1140+
// Test fixed strings where byte_len+5 is a multiple of 16. Regression test for MONGOCRYPT-917.
1141+
test_nofold_substring_case(tester, "abcdefghijk" /* 11 chars */, 1, 30, 16, false, false, 0);
11281142
}
11291143

11301144
static void _test_text_search_str_encode_multiple(_mongocrypt_tester_t *tester) {

test/test-mongocrypt-marking.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,21 @@ static void test_mc_marking_to_ciphertext_fle2_text_search(_mongocrypt_tester_t
20122012
}
20132013
}
20142014

2015+
static size_t count_suffixes(bson_t *ciphertext_bson) {
2016+
bson_iter_t iter;
2017+
// Find TextSearchTokenSets ("b").
2018+
ASSERT(bson_iter_init_find(&iter, ciphertext_bson, "b"));
2019+
ASSERT(bson_iter_recurse(&iter, &iter));
2020+
// Find suffix tokens ("u"):
2021+
ASSERT(bson_iter_find(&iter, "u"));
2022+
ASSERT(bson_iter_recurse(&iter, &iter));
2023+
size_t suffix_count = 0;
2024+
while (bson_iter_next(&iter)) {
2025+
suffix_count++;
2026+
}
2027+
return suffix_count;
2028+
}
2029+
20152030
static void test_ciphertext_len_steps_fle2_text_search(_mongocrypt_tester_t *tester) {
20162031
#define MARKING_JSON_FORMAT \
20172032
RAW_STRING({ \
@@ -2021,12 +2036,13 @@ static void test_ciphertext_len_steps_fle2_text_search(_mongocrypt_tester_t *tes
20212036
'v' : "%s", \
20222037
'casef' : false, \
20232038
'diacf' : false, \
2024-
'suffix' : {'ub' : {'$numberInt' : '2'}, 'lb' : {'$numberInt' : '1'}} \
2039+
'suffix' : {'ub' : {'$numberInt' : '100'}, 'lb' : {'$numberInt' : '1'}} \
20252040
}, \
20262041
'cm' : {'$numberLong' : '2'} \
20272042
})
20282043

20292044
size_t last_len = 0;
2045+
size_t last_suffix_count = 0;
20302046
mongocrypt_binary_t *cmd = TEST_FILE("./test/example/cmd.json");
20312047
mongocrypt_binary_t *key_file = TEST_BIN(16);
20322048
mongocrypt_binary_t *ki = TEST_BIN(16);
@@ -2052,6 +2068,7 @@ static void test_ciphertext_len_steps_fle2_text_search(_mongocrypt_tester_t *tes
20522068
bson_t ciphertext_bson;
20532069
ASSERT(_mongocrypt_buffer_to_bson(&ciphertext.data, &ciphertext_bson));
20542070
iupv2_fields_common res = validate_iupv2_common(&ciphertext_bson);
2071+
size_t suffix_count = count_suffixes(&ciphertext_bson);
20552072
if (str_len != 0) {
20562073
// We expect a step in ciphertext len iff str_len + 5 goes from 16k-1 to 16k. 5 is the number of overhead
20572074
// bytes from the BSON header + null byte.
@@ -2060,8 +2077,18 @@ static void test_ciphertext_len_steps_fle2_text_search(_mongocrypt_tester_t *tes
20602077
} else {
20612078
ASSERT_CMPSIZE_T(res.v.len, ==, last_len);
20622079
}
2080+
2081+
// Similarly, we expect a step in suffix count at the same point:
2082+
if ((str_len + 5) % 16 == 0) {
2083+
size_t expected_suffix_count = BSON_MIN(100 /* ub */, last_suffix_count + 16);
2084+
ASSERT_CMPSIZE_T(suffix_count, ==, expected_suffix_count);
2085+
} else {
2086+
size_t expected_suffix_count = BSON_MIN(100 /* ub */, last_suffix_count);
2087+
ASSERT_CMPSIZE_T(suffix_count, ==, expected_suffix_count);
2088+
}
20632089
}
20642090
last_len = res.v.len;
2091+
last_suffix_count = suffix_count;
20652092

20662093
bson_destroy(&ciphertext_bson);
20672094
bson_free(markingJSON);

0 commit comments

Comments
 (0)