From 033569b4417ceb35c616a5ca86efce41971883f4 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 2 Apr 2020 15:25:53 +1300 Subject: [PATCH] libcli ldap: limit recursion depth in ldap_decode_filter_tree Limit the number of recursive calls to ldap_decode_filter_tree to prevent stack overflows. The depth is limited to 253 which is small enough to avoid ASAN detecting a stack overflow, but large enough to be useful. Credit to OSS-Fuzz REF: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=20454 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14334 Signed-off-by: Gary Lockyer --- libcli/ldap/ldap_message.c | 28 ++- .../tests/data/clusterfuzz-ldap_decode-20454 | Bin 0 -> 74583 bytes libcli/ldap/tests/ldap_message_test.c | 193 ++++++++++++++++++ libcli/ldap/wscript_build | 15 ++ source4/selftest/tests.py | 2 + 5 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 libcli/ldap/tests/data/clusterfuzz-ldap_decode-20454 create mode 100644 libcli/ldap/tests/ldap_message_test.c diff --git a/libcli/ldap/ldap_message.c b/libcli/ldap/ldap_message.c index f21598374a1..c6882bf1f70 100644 --- a/libcli/ldap/ldap_message.c +++ b/libcli/ldap/ldap_message.c @@ -26,6 +26,13 @@ #include "../lib/util/asn1.h" #include "../libcli/ldap/ldap_message.h" +/* + * The maximum depth that we'll recursively call ldap_decode_filter_tree + * 253 is chosen as it's small enough to not trigger the ASAN stack overflow + * check, but large enough to be practical in actual use. + */ +#define MAX_FILTER_TREE_DEPTH 250 + _PUBLIC_ struct ldap_message *new_ldap_message(TALLOC_CTX *mem_ctx) { return talloc_zero(mem_ctx, struct ldap_message); @@ -774,12 +781,23 @@ static struct ldb_val **ldap_decode_substring(TALLOC_CTX *mem_ctx, struct ldb_va /* parse the ASN.1 formatted search string into a ldb_parse_tree */ -static struct ldb_parse_tree *ldap_decode_filter_tree(TALLOC_CTX *mem_ctx, - struct asn1_data *data) +static struct ldb_parse_tree *ldap_decode_filter_tree( + TALLOC_CTX *mem_ctx, + struct asn1_data *data, + unsigned int depth) { uint8_t filter_tag; struct ldb_parse_tree *ret; + /* + * Limit the depth of recursion we'll allow to avoid stack overflow + * attacks + */ + depth++; + if (depth > MAX_FILTER_TREE_DEPTH) { + return NULL; + } + if (!asn1_peek_uint8(data, &filter_tag)) { return NULL; } @@ -803,7 +821,7 @@ static struct ldb_parse_tree *ldap_decode_filter_tree(TALLOC_CTX *mem_ctx, while (asn1_tag_remaining(data) > 0) { struct ldb_parse_tree *subtree; - subtree = ldap_decode_filter_tree(ret, data); + subtree = ldap_decode_filter_tree(ret, data, depth); if (subtree == NULL) { goto failed; } @@ -830,7 +848,7 @@ static struct ldb_parse_tree *ldap_decode_filter_tree(TALLOC_CTX *mem_ctx, } ret->operation = LDB_OP_NOT; - ret->u.isnot.child = ldap_decode_filter_tree(ret, data); + ret->u.isnot.child = ldap_decode_filter_tree(ret, data, depth); if (ret->u.isnot.child == NULL) { goto failed; } @@ -1269,7 +1287,7 @@ _PUBLIC_ NTSTATUS ldap_decode(struct asn1_data *data, r->timelimit = timelimit; if (!asn1_read_BOOLEAN(data, &r->attributesonly)) goto prot_err; - r->tree = ldap_decode_filter_tree(msg, data); + r->tree = ldap_decode_filter_tree(msg, data, 0); if (r->tree == NULL) { goto prot_err; } diff --git a/libcli/ldap/tests/data/clusterfuzz-ldap_decode-20454 b/libcli/ldap/tests/data/clusterfuzz-ldap_decode-20454 new file mode 100644 index 0000000000000000000000000000000000000000..2b929069d081831a974b74d37ecbba83c0c743d0 GIT binary patch literal 74583 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 QtGVqz<7Z9Z<^T1602Ntcxc~qF literal 0 HcmV?d00001 diff --git a/libcli/ldap/tests/ldap_message_test.c b/libcli/ldap/tests/ldap_message_test.c new file mode 100644 index 00000000000..17e3e1003ee --- /dev/null +++ b/libcli/ldap/tests/ldap_message_test.c @@ -0,0 +1,193 @@ +/* + * 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(_UNUSED_ 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); + 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); +} + +/* + * reproducer for oss-fuzz issue 20454 + * + * Note that this test needs to be run with ASAN enabled, as it checks a stack + * overflow condition. + */ +static void test_oss_fuzz_20454(_UNUSED_ 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 the oss-fuzz generated test data that triggered a stack + * overflow. + */ + buffer = talloc_zero_array(test_ctx, uint8_t, BUFF_SIZE); + f = fopen( + "./libcli/ldap/tests/data/clusterfuzz-ldap_decode-20454", + "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); + 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_oss_fuzz_20454, + 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/source4/selftest/tests.py b/source4/selftest/tests.py index 81a988ac35c..d49787d2246 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1361,6 +1361,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 -- 2.17.1