Skip to content
Merged
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
12 changes: 10 additions & 2 deletions src/mc-schema-broker.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,11 @@ bool mc_schema_broker_satisfy_from_collinfo(mc_schema_broker_t *sb,
CLIENT_ERR("failed to find 'name' in collinfo in database: %s", sb->db);
return false;
}
coll = bson_iter_utf8(&name_iter, NULL);
uint32_t coll_len;
coll = bson_iter_utf8(&name_iter, &coll_len);
if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "collection name in collinfo", status)) {
return false;
}
}

// Cache the received collinfo.
Expand Down Expand Up @@ -508,7 +512,11 @@ bool mc_schema_broker_satisfy_from_create_or_collMod(mc_schema_broker_t *sb,
CLIENT_ERR("Failed to get collection name from command");
return false;
}
const char *coll = bson_iter_utf8(&iter, NULL);
uint32_t coll_len;
const char *coll = bson_iter_utf8(&iter, &coll_len);
if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "collection name", status)) {
return false;
}

// Check if schema was requested.
mc_schema_entry_t *found = NULL;
Expand Down
29 changes: 25 additions & 4 deletions src/mongocrypt-ctx-encrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -2303,7 +2303,11 @@ static bool _check_cmd_for_auto_encrypt_bulkWrite(mongocrypt_binary_t *cmd,
return false;
}

const char *target_ns = bson_iter_utf8(&ns_iter, NULL /* length */);
uint32_t target_ns_len;
const char *target_ns = bson_iter_utf8(&ns_iter, &target_ns_len);
if (!_mongocrypt_check_no_embedded_nul(target_ns, target_ns_len, "namespace in `bulkWrite` command", status)) {
return false;
}
// Parse `target_ns` into "<db>.<coll>"
const char *dot = strstr(target_ns, ".");
if (!dot) {
Expand Down Expand Up @@ -2375,7 +2379,12 @@ _check_cmd_for_auto_encrypt(mongocrypt_binary_t *cmd, bool *bypass, char **targe
}

if (BSON_ITER_HOLDS_UTF8(&target_coll_iter)) {
*target_coll = bson_strdup(bson_iter_utf8(&target_coll_iter, NULL));
uint32_t target_coll_len;
const char *target_coll_str = bson_iter_utf8(&target_coll_iter, &target_coll_len);
if (!_mongocrypt_check_no_embedded_nul(target_coll_str, target_coll_len, "collection name", status)) {
return false;
}
*target_coll = bson_strdup(target_coll_str);
} else {
*target_coll = NULL;
}
Expand Down Expand Up @@ -2572,7 +2581,11 @@ static bool find_collections_in_pipeline(mc_schema_broker_t *sb,
stage_key);
return false;
}
const char *from = bson_iter_utf8(&lookup_iter, NULL);
uint32_t from_len;
const char *from = bson_iter_utf8(&lookup_iter, &from_len);
if (!_mongocrypt_check_no_embedded_nul(from, from_len, "'from' collection name", status)) {
return false;
}
if (!mc_schema_broker_request(sb, db, from, status)) {
return false;
}
Expand Down Expand Up @@ -2631,7 +2644,11 @@ static bool find_collections_in_pipeline(mc_schema_broker_t *sb,
stage_key);
return false;
}
const char *coll = bson_iter_utf8(&unionWith_iter, NULL);
uint32_t coll_len;
const char *coll = bson_iter_utf8(&unionWith_iter, &coll_len);
if (!_mongocrypt_check_no_embedded_nul(coll, coll_len, "'coll' collection name", status)) {
return false;
}
if (!mc_schema_broker_request(sb, db, coll, status)) {
return false;
}
Expand Down Expand Up @@ -2722,6 +2739,10 @@ bool mongocrypt_ctx_encrypt_init(mongocrypt_ctx_t *ctx, const char *db, int32_t
return _mongocrypt_ctx_fail_w_msg(ctx, "invalid db");
}

if (strchr(ectx->cmd_db, '.')) {
return _mongocrypt_ctx_fail_w_msg(ctx, "invalid db: must not contain a dot");
}

if (0 == strcmp(ectx->cmd_name, "bulkWrite")) {
// Handle `bulkWrite` as a special case.
// `bulkWrite` includes the target namespaces in an `nsInfo` field.
Expand Down
5 changes: 5 additions & 0 deletions src/mongocrypt-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ typedef enum {

bool _mongocrypt_validate_and_copy_string(const char *in, int32_t in_len, char **out) MONGOCRYPT_WARN_UNUSED_RESULT;

/* _mongocrypt_check_no_embedded_nul returns false and sets @status if @str contains an embedded null byte. See
* MONGOCRYPT-977. */
bool _mongocrypt_check_no_embedded_nul(const char *str, uint32_t len, const char *what, mongocrypt_status_t *status)
MONGOCRYPT_WARN_UNUSED_RESULT;

char *_mongocrypt_new_string_from_bytes(const void *in, int len);

/* _mongocrypt_needs_credentials returns true if @crypt was configured to
Expand Down
11 changes: 11 additions & 0 deletions src/mongocrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,17 @@ uint64_t mongocrypt_crypt_shared_lib_version(const mongocrypt_t *crypt) {
return crypt->csfle.get_version();
}

bool _mongocrypt_check_no_embedded_nul(const char *str, uint32_t len, const char *what, mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(str);
BSON_ASSERT_PARAM(what);

if (strlen(str) != (size_t)len) {
CLIENT_ERR("%s must not contain an embedded null byte", what);
return false;
}
return true;
}

bool _mongocrypt_validate_and_copy_string(const char *in, int32_t in_len, char **out) {
BSON_ASSERT_PARAM(out);

Expand Down
114 changes: 114 additions & 0 deletions test/test-mongocrypt-ctx-encrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,119 @@ static void _test_encrypt_init(_mongocrypt_tester_t *tester) {
mongocrypt_destroy(crypt);
}

/* Test that a db name containing a dot or an embedded NUL is rejected.
* A db name like "a.b" would otherwise produce the namespace "a.b.coll",
* retargeting the operation at database "a" and collection "b.coll".
* Regression test for MONGOCRYPT-977. */
static void _test_encrypt_init_invalid_db_collection(_mongocrypt_tester_t *tester) {
mongocrypt_t *crypt = _mongocrypt_tester_mongocrypt(TESTER_MONGOCRYPT_DEFAULT);

{
/* Dot in db name. */
mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt);
ASSERT_FAILS(mongocrypt_ctx_encrypt_init(ctx, "a.b", -1, TEST_FILE("./test/example/cmd.json")),
ctx,
"invalid db");
mongocrypt_ctx_destroy(ctx);
}

{
/* Embedded NUL in db name. */
const char db[] = "a\0b";
mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt);
ASSERT_FAILS(mongocrypt_ctx_encrypt_init(ctx, db, 3, TEST_FILE("./test/example/cmd.json")), ctx, "invalid db");
mongocrypt_ctx_destroy(ctx);
}

{
/* A dot in a collection name is legal, and must keep working. */
mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt);
ASSERT_OK(mongocrypt_ctx_encrypt_init(ctx, "test", -1, TEST_BSON("{'find': 'a.b'}")), ctx);
mongocrypt_ctx_destroy(ctx);
}

{
/* Embedded NUL in the collection name of the command. */
bson_t *cmd = bson_new();
BSON_ASSERT(bson_append_utf8(cmd, "find", -1, "a\0b", 3));
mongocrypt_binary_t *bin = mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(cmd), cmd->len);
mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt);
ASSERT_FAILS(mongocrypt_ctx_encrypt_init(ctx, "test", -1, bin),
ctx,
"collection name must not contain an embedded null byte");
mongocrypt_ctx_destroy(ctx);
mongocrypt_binary_destroy(bin);
bson_destroy(cmd);
}

{
/* Embedded NUL in the `bulkWrite` nsInfo namespace. */
bson_t *cmd = bson_new();
bson_array_builder_t *nsInfo;
bson_t ns0;
BSON_ASSERT(BSON_APPEND_INT32(cmd, "bulkWrite", 1));
BSON_ASSERT(BSON_APPEND_ARRAY_BUILDER_BEGIN(cmd, "nsInfo", &nsInfo));
BSON_ASSERT(bson_array_builder_append_document_begin(nsInfo, &ns0));
BSON_ASSERT(bson_append_utf8(&ns0, "ns", -1, "db.coll\0evil", 12));
BSON_ASSERT(bson_array_builder_append_document_end(nsInfo, &ns0));
BSON_ASSERT(bson_append_array_builder_end(cmd, nsInfo));
mongocrypt_binary_t *bin = mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(cmd), cmd->len);
mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt);
ASSERT_FAILS(mongocrypt_ctx_encrypt_init(ctx, "test", -1, bin), ctx, "must not contain an embedded null byte");
mongocrypt_ctx_destroy(ctx);
mongocrypt_binary_destroy(bin);
bson_destroy(cmd);
}

{
/* Embedded NUL in a $lookup 'from' collection name. */
bson_t *cmd = bson_new();
bson_array_builder_t *pipeline;
bson_t stage, lookup;
BSON_ASSERT(BSON_APPEND_UTF8(cmd, "aggregate", "coll"));
BSON_ASSERT(BSON_APPEND_ARRAY_BUILDER_BEGIN(cmd, "pipeline", &pipeline));
BSON_ASSERT(bson_array_builder_append_document_begin(pipeline, &stage));
BSON_ASSERT(BSON_APPEND_DOCUMENT_BEGIN(&stage, "$lookup", &lookup));
BSON_ASSERT(bson_append_utf8(&lookup, "from", -1, "a\0b", 3));
BSON_ASSERT(bson_append_document_end(&stage, &lookup));
BSON_ASSERT(bson_array_builder_append_document_end(pipeline, &stage));
BSON_ASSERT(bson_append_array_builder_end(cmd, pipeline));
mongocrypt_binary_t *bin = mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(cmd), cmd->len);
mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt);
ASSERT_FAILS(mongocrypt_ctx_encrypt_init(ctx, "test", -1, bin),
ctx,
"'from' collection name must not contain an embedded null byte");
mongocrypt_ctx_destroy(ctx);
mongocrypt_binary_destroy(bin);
bson_destroy(cmd);
}

{
/* Embedded NUL in a $unionWith 'coll' collection name. */
bson_t *cmd = bson_new();
bson_array_builder_t *pipeline;
bson_t stage, unionWith;
BSON_ASSERT(BSON_APPEND_UTF8(cmd, "aggregate", "coll"));
BSON_ASSERT(BSON_APPEND_ARRAY_BUILDER_BEGIN(cmd, "pipeline", &pipeline));
BSON_ASSERT(bson_array_builder_append_document_begin(pipeline, &stage));
BSON_ASSERT(BSON_APPEND_DOCUMENT_BEGIN(&stage, "$unionWith", &unionWith));
BSON_ASSERT(bson_append_utf8(&unionWith, "coll", -1, "a\0b", 3));
BSON_ASSERT(bson_append_document_end(&stage, &unionWith));
BSON_ASSERT(bson_array_builder_append_document_end(pipeline, &stage));
BSON_ASSERT(bson_append_array_builder_end(cmd, pipeline));
mongocrypt_binary_t *bin = mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(cmd), cmd->len);
mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt);
ASSERT_FAILS(mongocrypt_ctx_encrypt_init(ctx, "test", -1, bin),
ctx,
"'coll' collection name must not contain an embedded null byte");
mongocrypt_ctx_destroy(ctx);
mongocrypt_binary_destroy(bin);
bson_destroy(cmd);
}

mongocrypt_destroy(crypt);
}

static void _test_encrypt_need_collinfo(_mongocrypt_tester_t *tester) {
mongocrypt_t *crypt;
mongocrypt_ctx_t *ctx;
Expand Down Expand Up @@ -6830,6 +6943,7 @@ static void _test_qe_keyAltName_kms(_mongocrypt_tester_t *tester) {
void _mongocrypt_tester_install_ctx_encrypt(_mongocrypt_tester_t *tester) {
INSTALL_TEST(_test_explicit_encrypt_init);
INSTALL_TEST(_test_encrypt_init);
INSTALL_TEST(_test_encrypt_init_invalid_db_collection);
INSTALL_TEST(_test_encrypt_need_collinfo);
INSTALL_TEST(_test_encrypt_need_markings);
INSTALL_TEST(_test_encrypt_csfle_no_needs_markings);
Expand Down