diff --git a/libcli/ldap/tests/data/10000-or.dat b/libcli/ldap/tests/data/10000-or.dat new file mode 100644 index 00000000000..e2d6de2ce33 Binary files /dev/null and b/libcli/ldap/tests/data/10000-or.dat differ diff --git a/libcli/ldap/tests/data/ldap-recursive.dat b/libcli/ldap/tests/data/ldap-recursive.dat new file mode 100644 index 00000000000..dd18d857660 Binary files /dev/null and b/libcli/ldap/tests/data/ldap-recursive.dat differ diff --git a/libcli/ldap/tests/ldap_message_test.c b/libcli/ldap/tests/ldap_message_test.c new file mode 100644 index 00000000000..9cc9cc5d8a0 --- /dev/null +++ b/libcli/ldap/tests/ldap_message_test.c @@ -0,0 +1,271 @@ +/* + * Unit tests for ldap_message. + * + * Copyright (C) Catalyst.NET Ltd 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ +#include +#include +#include +#include + +#include "lib/util/attr.h" +#include "includes.h" +#include "lib/util/asn1.h" +#include "libcli/ldap/ldap_message.h" +#include "libcli/ldap/ldap_proto.h" + +/* + * declare the internal cmocka cm_print so we can output messages in + * sub unit format + */ +void cm_print_error(const char * const format, ...); +/* + * helper function and macro to compare an ldap error code constant with the + * coresponding nt_status code + */ +#define NT_STATUS_LDAP_V(code) (0xF2000000 | code) +static void _assert_ldap_status_equal( + int a, + NTSTATUS b, + const char * const file, + const int line) +{ + _assert_int_equal(NT_STATUS_LDAP_V(a), NT_STATUS_V(b), file, line); +} + +#define assert_ldap_status_equal(a, b) \ + _assert_ldap_status_equal((a), (b), __FILE__, __LINE__) + +/* + * helper function and macro to assert there were no errors in the last + * file operation + */ +static void _assert_not_ferror( + FILE *f, + const char * const file, + const int line) +{ + if (f == NULL || ferror(f)) { + cm_print_error("ferror (%d) %s\n", errno, strerror(errno)); + _fail(file, line); + } +} + +#define assert_not_ferror(f) \ + _assert_not_ferror((f), __FILE__, __LINE__) + +struct test_ctx { +}; + +static int setup(void **state) +{ + struct test_ctx *test_ctx; + + test_ctx = talloc_zero(NULL, struct test_ctx); + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort(*state, + struct test_ctx); + + TALLOC_FREE(test_ctx); + return 0; +} + +/* + * Test that an empty request is handled correctly + */ +static void test_empty_input(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct asn1_data *asn1; + struct ldap_message *ldap_msg; + NTSTATUS status; + uint8_t buf[0]; + size_t len = 0; + + + asn1 = asn1_init(test_ctx, ASN1_MAX_TREE_DEPTH); + assert_non_null(asn1); + + asn1_load_nocopy(asn1, buf, len); + + ldap_msg = talloc(test_ctx, struct ldap_message); + assert_non_null(ldap_msg); + + status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + assert_ldap_status_equal(LDAP_PROTOCOL_ERROR, status); +} + +/* + * Check that a request is rejected it it's recursion depth exceeds + * the maximum value specified. This test uses a very deeply nested query, + * 10,000 or clauses. + * + */ +static void test_recursion_depth_large(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct asn1_data *asn1; + struct ldap_message *ldap_msg; + NTSTATUS status; + FILE *f = NULL; + uint8_t *buffer = NULL; + const size_t BUFF_SIZE = 1048576; + size_t len; + + + /* + * Load a test data file containg 10,000 or clauses in encoded as + * an ASN.1 packet. + */ + buffer = talloc_zero_array(test_ctx, uint8_t, BUFF_SIZE); + f = fopen("./libcli/ldap/tests/data/10000-or.dat", "r"); + assert_not_ferror(f); + len = fread(buffer, sizeof(uint8_t), BUFF_SIZE, f); + assert_not_ferror(f); + assert_true(len > 0); + + asn1 = asn1_init(test_ctx, ASN1_MAX_TREE_DEPTH); + assert_non_null(asn1); + asn1_load_nocopy(asn1, buffer, len); + + ldap_msg = talloc(test_ctx, struct ldap_message); + assert_non_null(ldap_msg); + + status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + assert_ldap_status_equal(LDAP_PROTOCOL_ERROR, status); +} + +/* + * Check that a request is not rejected it it's recursion depth equals the + * maximum value + */ +static void test_recursion_depth_equals_max(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct asn1_data *asn1; + struct ldap_message *ldap_msg; + NTSTATUS status; + FILE *f = NULL; + uint8_t *buffer = NULL; + const size_t BUFF_SIZE = 1048576; + size_t len; + + + buffer = talloc_zero_array(test_ctx, uint8_t, BUFF_SIZE); + f = fopen("./libcli/ldap/tests/data/ldap-recursive.dat", "r"); + assert_not_ferror(f); + len = fread(buffer, sizeof(uint8_t), BUFF_SIZE, f); + assert_not_ferror(f); + assert_true(len > 0); + + asn1 = asn1_init(test_ctx, 4); + assert_non_null(asn1); + asn1_load_nocopy(asn1, buffer, len); + + ldap_msg = talloc(test_ctx, struct ldap_message); + assert_non_null(ldap_msg); + + status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + assert_true(NT_STATUS_IS_OK(status)); +} + +/* + * Check that a request is rejected it it's recursion depth is greater than the + * maximum value + */ +static void test_recursion_depth_greater_than_max(void **state) +{ + struct test_ctx *test_ctx = talloc_get_type_abort( + *state, + struct test_ctx); + struct asn1_data *asn1; + struct ldap_message *ldap_msg; + NTSTATUS status; + FILE *f = NULL; + uint8_t *buffer = NULL; + const size_t BUFF_SIZE = 1048576; + size_t len; + + + buffer = talloc_zero_array(test_ctx, uint8_t, BUFF_SIZE); + f = fopen("./libcli/ldap/tests/data/ldap-recursive.dat", "r"); + assert_not_ferror(f); + len = fread(buffer, sizeof(uint8_t), BUFF_SIZE, f); + assert_not_ferror(f); + assert_true(len > 0); + + asn1 = asn1_init(test_ctx, 3); + assert_non_null(asn1); + asn1_load_nocopy(asn1, buffer, len); + + ldap_msg = talloc(test_ctx, struct ldap_message); + assert_non_null(ldap_msg); + + status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + assert_ldap_status_equal(LDAP_PROTOCOL_ERROR, status); +} + +int main(_UNUSED_ int argc, _UNUSED_ const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_empty_input, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_recursion_depth_large, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_recursion_depth_equals_max, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_recursion_depth_greater_than_max, + setup, + teardown), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/libcli/ldap/wscript_build b/libcli/ldap/wscript_build index db5b1df497a..a646685c751 100644 --- a/libcli/ldap/wscript_build +++ b/libcli/ldap/wscript_build @@ -6,3 +6,18 @@ bld.SAMBA_LIBRARY('cli-ldap-common', private_headers='ldap_message.h ldap_errors.h ldap_ndr.h', deps='samba-util asn1util NDR_SECURITY tevent', private_library=True) + +bld.SAMBA_BINARY( + 'test_ldap_message', + source='tests/ldap_message_test.c', + deps=''' + cmocka + talloc + ldb + samba-util + asn1util + NDR_SECURITY + cli-ldap + ''', + for_selftest=True +) diff --git a/selftest/knownfail.d/ldap_message b/selftest/knownfail.d/ldap_message new file mode 100644 index 00000000000..242eff45e59 --- /dev/null +++ b/selftest/knownfail.d/ldap_message @@ -0,0 +1,2 @@ +^libcli.ldap.ldap_message.test_recursion_depth_greater_than_max\(none\) +^libcli.ldap.ldap_message.test_recursion_depth_large\(none\) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 82c846dbab1..0a806b8fed6 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1376,6 +1376,8 @@ plantestsuite("librpc.ndr.ndr", "none", [os.path.join(bindir(), "test_ndr")]) plantestsuite("librpc.ndr.ndr_macros", "none", [os.path.join(bindir(), "test_ndr_macros")]) +plantestsuite("libcli.ldap.ldap_message", "none", + [os.path.join(bindir(), "test_ldap_message")]) # process restart and limit tests, these break the environment so need to run # in their own specific environment