From f9a12b6433f38cfab97c0057d0e9b0d5719d864a Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 6 Mar 2018 15:27:51 +1300 Subject: [PATCH] ldb_mdb: Apply LMDB key length restrictions at key-value layer We need to enforce the GUID index mode so end-users do not get a supprise in mid-operation and we enforce a max key length of 511 so that the index key trunctation is done correctly. Otherwise the DB will appear to work until a very long key (DN or index) is used, after which it will be sad. Because the previous ldb_lmdb_test confirmed the key length by creating a large DN, those tests are re-worked to use the GUID index mode. In turn, new tests are written that create a special DN around the maximum key length. Finally a test is included that demonstrates that adding entries to the LMDB DB without GUID index mode fails. Signed-off-by: Gary Lockyer Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam --- lib/ldb/ldb_mdb/ldb_mdb.c | 19 ++++ lib/ldb/tests/ldb_lmdb_test.c | 166 +++++++++++++++++++++++++++++++++- 2 files changed, 182 insertions(+), 3 deletions(-) diff --git a/lib/ldb/ldb_mdb/ldb_mdb.c b/lib/ldb/ldb_mdb/ldb_mdb.c index 20315dfa557..fa81d9bd61e 100644 --- a/lib/ldb/ldb_mdb/ldb_mdb.c +++ b/lib/ldb/ldb_mdb/ldb_mdb.c @@ -29,6 +29,8 @@ #define MDB_URL_PREFIX "mdb://" #define MDB_URL_PREFIX_SIZE (sizeof(MDB_URL_PREFIX)-1) +#define LDB_MDB_MAX_KEY_LENGTH 511 + #define GIGABYTE (1024*1024*1024) int ldb_mdb_err_map(int lmdb_err) @@ -663,6 +665,7 @@ static int lmdb_pvt_open(TALLOC_CTX *mem_ctx, int ret; unsigned int mdb_flags; const size_t mmap_size = 8LL * GIGABYTE; + int lmdb_max_key_length; if (flags & LDB_FLG_DONT_CREATE_DB) { struct stat st; @@ -720,6 +723,14 @@ static int lmdb_pvt_open(TALLOC_CTX *mem_ctx, /* Store the original pid during the LMDB open */ lmdb->pid = getpid(); + lmdb_max_key_length = mdb_env_get_maxkeysize(lmdb->env); + + /* This will never happen, but if it does make sure to freak out */ + if (lmdb_max_key_length < LDB_MDB_MAX_KEY_LENGTH) { + talloc_free(lmdb); + return ldb_operr(ldb); + } + return LDB_SUCCESS; } @@ -771,5 +782,13 @@ int lmdb_connect(struct ldb_context *ldb, ltdb->read_only = true; } + /* + * This maximum length becomes encoded in the index values so + * must never change even if LMDB starts to allow longer keys. + * The override option is max_key_len_for_self_test, and is + * used for testing only. + */ + ltdb->max_key_length = LDB_MDB_MAX_KEY_LENGTH; + return init_store(ltdb, "ldb_mdb backend", ldb, options, _module); } diff --git a/lib/ldb/tests/ldb_lmdb_test.c b/lib/ldb/tests/ldb_lmdb_test.c index e47c7dbc70f..eece2be8057 100644 --- a/lib/ldb/tests/ldb_lmdb_test.c +++ b/lib/ldb/tests/ldb_lmdb_test.c @@ -132,12 +132,22 @@ static int ldbtest_setup(void **state) { struct ldbtest_ctx *test_ctx; int ret; + struct ldb_ldif *ldif; + const char *index_ldif = \ + "dn: @INDEXLIST\n" + "@IDXGUID: objectUUID\n" + "@IDX_DN_GUID: GUID\n" + "\n"; ldbtest_noconn_setup((void **) &test_ctx); ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); assert_int_equal(ret, 0); + while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) { + ret = ldb_add(test_ctx->ldb, ldif->msg); + assert_int_equal(ret, LDB_SUCCESS); + } *state = test_ctx; return 0; } @@ -167,7 +177,8 @@ static void test_ldb_add_key_len_gt_max(void **state) assert_non_null(msg); /* - * The zero terminator is part of the key + * The zero terminator is part of the key if we were not in + * GUID mode */ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */ @@ -181,8 +192,11 @@ static void test_ldb_add_key_len_gt_max(void **state) ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); assert_int_equal(ret, 0); + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + ret = ldb_add(test_ctx->ldb, msg); - assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR); + assert_int_equal(ret, LDB_SUCCESS); talloc_free(tmp_ctx); } @@ -204,7 +218,8 @@ static void test_ldb_add_key_len_eq_max(void **state) assert_non_null(msg); /* - * The zero terminator is part of the key + * The zero terminator is part of the key if we were not in + * GUID mode */ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */ @@ -217,12 +232,145 @@ static void test_ldb_add_key_len_eq_max(void **state) ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); assert_int_equal(ret, 0); + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + ret = ldb_add(test_ctx->ldb, msg); assert_int_equal(ret, 0); talloc_free(tmp_ctx); } +static int ldbtest_setup_noguid(void **state) +{ + struct ldbtest_ctx *test_ctx; + int ret; + + ldbtest_noconn_setup((void **) &test_ctx); + + ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(ret, 0); + + *state = test_ctx; + return 0; +} + +static void test_ldb_add_special_key_len_gt_max(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */ + xs_size += 1; /* want key on char too long */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR); + + talloc_free(tmp_ctx); +} + +static void test_ldb_add_special_key_len_eq_max(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_SUCCESS); + + talloc_free(tmp_ctx); +} + +static void test_ldb_add_dn_no_guid_mode(void **state) +{ + int ret; + int xs_size = 0; + struct ldb_message *msg; + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + char *xs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(test_ctx); + assert_non_null(tmp_ctx); + + msg = ldb_msg_new(tmp_ctx); + assert_non_null(msg); + + /* + * The zero terminator is part of the key if we were not in + * GUID mode + */ + + xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */ + xs_size += 1; /* want key on char too long */ + xs = talloc_zero_size(tmp_ctx, (xs_size + 1)); + memset(xs, 'x', xs_size); + + msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs); + assert_non_null(msg->dn); + + ret = ldb_msg_add_string(msg, "cn", "test_cn_val"); + assert_int_equal(ret, 0); + + ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef"); + assert_int_equal(ret, 0); + + ret = ldb_add(test_ctx->ldb, msg); + assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM); + + talloc_free(tmp_ctx); +} + int main(int argc, const char **argv) { const struct CMUnitTest tests[] = { @@ -234,6 +382,18 @@ int main(int argc, const char **argv) test_ldb_add_key_len_gt_max, ldbtest_setup, ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_special_key_len_eq_max, + ldbtest_setup_noguid, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_special_key_len_gt_max, + ldbtest_setup_noguid, + ldbtest_teardown), + cmocka_unit_test_setup_teardown( + test_ldb_add_dn_no_guid_mode, + ldbtest_setup_noguid, + ldbtest_teardown), }; return cmocka_run_group_tests(tests, NULL, NULL);