From ba518a1debbe2dd8231ba2fb9bbb07eef743d86f Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 30 Jan 2020 08:49:07 +1300 Subject: [PATCH] librpc ndr: add recursion check macros Add macros to check the recursion depth. Credit to OSS-Fuzz REF: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=19280 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14254 Signed-off-by: Gary Lockyer Reviewed-by: Andrew Bartlett --- librpc/ndr/libndr.h | 37 ++++++++- librpc/ndr/ndr.c | 2 + librpc/tests/test_ndr_macros.c | 138 +++++++++++++++++++++++++++++++++ librpc/wscript_build | 9 +++ source4/selftest/tests.py | 2 + 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 librpc/tests/test_ndr_macros.c diff --git a/librpc/ndr/libndr.h b/librpc/ndr/libndr.h index 8d407c40e43..fd87db928ed 100644 --- a/librpc/ndr/libndr.h +++ b/librpc/ndr/libndr.h @@ -79,6 +79,14 @@ struct ndr_pull { /* this is used to ensure we generate unique reference IDs between request and reply */ uint32_t ptr_count; + uint32_t recursion_depth; + /* + * The global maximum depth for recursion. When set it overrides the + * value supplied by the max_recursion idl attribute. This is needed + * for fuzzing as ASAN uses a low threshold for stack depth to check + * for stack overflow. + */ + uint32_t global_max_recursion; }; /* structure passed to functions that generate NDR formatted data */ @@ -249,7 +257,9 @@ enum ndr_err_code { NDR_ERR_UNREAD_BYTES, NDR_ERR_NDR64, NDR_ERR_FLAGS, - NDR_ERR_INCOMPLETE_BUFFER + NDR_ERR_INCOMPLETE_BUFFER, + NDR_ERR_MAX_RECURSION_EXCEEDED, + NDR_ERR_UNDERFLOW }; #define NDR_ERR_CODE_IS_SUCCESS(x) (x == NDR_ERR_SUCCESS) @@ -357,6 +367,31 @@ enum ndr_compression_alg { } \ } while(0) +#define NDR_RECURSION_CHECK(ndr, d) do { \ + uint32_t _ndr_min_ = (d); \ + if (ndr->global_max_recursion && ndr->global_max_recursion < (d)) { \ + _ndr_min_ = ndr->global_max_recursion; \ + } \ + ndr->recursion_depth++; \ + if (unlikely(ndr->recursion_depth > _ndr_min_)) { \ + return ndr_pull_error( \ + ndr, \ + NDR_ERR_MAX_RECURSION_EXCEEDED, \ + "Depth of recursion exceeds (%u)", \ + (unsigned) d); \ + } \ +} while (0) + +#define NDR_RECURSION_UNWIND(ndr) do { \ + if (unlikely(ndr->recursion_depth == 0)) { \ + return ndr_pull_error( \ + ndr, \ + NDR_ERR_UNDERFLOW, \ + "ndr_pull.recursion_depth is 0"); \ + } \ + ndr->recursion_depth--; \ +} while (0) + /* these are used to make the error checking on each element in libndr less tedious, hopefully making the code more readable */ #define NDR_CHECK(call) do { \ diff --git a/librpc/ndr/ndr.c b/librpc/ndr/ndr.c index f96a0bca08b..afe22a28602 100644 --- a/librpc/ndr/ndr.c +++ b/librpc/ndr/ndr.c @@ -1950,6 +1950,8 @@ static const struct { { NDR_ERR_UNREAD_BYTES, "Unread Bytes" }, { NDR_ERR_NDR64, "NDR64 assertion error" }, { NDR_ERR_INCOMPLETE_BUFFER, "Incomplete Buffer" }, + { NDR_ERR_MAX_RECURSION_EXCEEDED, "Maximum Recursion Exceeded" }, + { NDR_ERR_UNDERFLOW, "Underflow" }, { 0, NULL } }; diff --git a/librpc/tests/test_ndr_macros.c b/librpc/tests/test_ndr_macros.c new file mode 100644 index 00000000000..0cd20d3e8f3 --- /dev/null +++ b/librpc/tests/test_ndr_macros.c @@ -0,0 +1,138 @@ +/* + * Tests for librpc ndr functions + * + * 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 + +#include "librpc/ndr/libndr.h" + +/* + * Test NDR_RECURSION_CHECK. + */ +static enum ndr_err_code wrap_NDR_RECURSION_CHECK( + struct ndr_pull *ndr, + uint32_t bytes) { + + NDR_RECURSION_CHECK(ndr, bytes); + return NDR_ERR_SUCCESS; +} + +static void test_NDR_RECURSION_CHECK(void **state) +{ + struct ndr_pull ndr = {0}; + enum ndr_err_code err; + + + ndr.global_max_recursion = 0; + ndr.recursion_depth = 42; + err = wrap_NDR_RECURSION_CHECK(&ndr, 43); + assert_int_equal(NDR_ERR_SUCCESS, err); + assert_int_equal(43, ndr.recursion_depth); + + ndr.global_max_recursion = 0; + ndr.recursion_depth = 43; + err = wrap_NDR_RECURSION_CHECK(&ndr, 43); + assert_int_equal(NDR_ERR_MAX_RECURSION_EXCEEDED, err); + assert_int_equal(44, ndr.recursion_depth); + + ndr.global_max_recursion = 0; + ndr.recursion_depth = 44; + err = wrap_NDR_RECURSION_CHECK(&ndr, 43); + assert_int_equal(NDR_ERR_MAX_RECURSION_EXCEEDED, err); + assert_int_equal(45, ndr.recursion_depth); + + ndr.global_max_recursion = 5; + ndr.recursion_depth = 5; + err = wrap_NDR_RECURSION_CHECK(&ndr, 20); + assert_int_equal(NDR_ERR_MAX_RECURSION_EXCEEDED, err); + assert_int_equal(6, ndr.recursion_depth); + + ndr.global_max_recursion = 5; + ndr.recursion_depth = 4; + err = wrap_NDR_RECURSION_CHECK(&ndr, 20); + assert_int_equal(NDR_ERR_SUCCESS, err); + assert_int_equal(5, ndr.recursion_depth); + + ndr.global_max_recursion = 20; + ndr.recursion_depth = 5; + err = wrap_NDR_RECURSION_CHECK(&ndr, 5); + assert_int_equal(NDR_ERR_MAX_RECURSION_EXCEEDED, err); + assert_int_equal(6, ndr.recursion_depth); + + ndr.global_max_recursion = 20; + ndr.recursion_depth = 4; + err = wrap_NDR_RECURSION_CHECK(&ndr, 5); + assert_int_equal(NDR_ERR_SUCCESS, err); + assert_int_equal(5, ndr.recursion_depth); +} + +/* + * Test NDR_RECURSION_RETURN. + */ +static enum ndr_err_code wrap_NDR_RECURSION_UNWIND( + struct ndr_pull *ndr) { + + NDR_RECURSION_UNWIND(ndr); + return NDR_ERR_SUCCESS; +} + +static void test_NDR_RECURSION_UNWIND(void **state) +{ + struct ndr_pull ndr = {0}; + enum ndr_err_code err; + + ndr.recursion_depth = 5; + err = wrap_NDR_RECURSION_UNWIND(&ndr); + assert_int_equal(NDR_ERR_SUCCESS, err); + assert_int_equal(4, ndr.recursion_depth); + + ndr.recursion_depth = 0; + err = wrap_NDR_RECURSION_UNWIND(&ndr); + assert_int_equal(NDR_ERR_UNDERFLOW, err); + assert_int_equal(0, ndr.recursion_depth); + +} +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_NDR_RECURSION_CHECK), + cmocka_unit_test(test_NDR_RECURSION_UNWIND), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/librpc/wscript_build b/librpc/wscript_build index ec8697fbcc5..f0bf7f7785e 100644 --- a/librpc/wscript_build +++ b/librpc/wscript_build @@ -690,6 +690,14 @@ bld.SAMBA_SUBSYSTEM('NDR_FSRVP_STATE', # # Cmocka tests # +bld.SAMBA_BINARY('test_ndr_macros', + source='tests/test_ndr_macros.c', + deps=''' + cmocka + ndr + ''', + for_selftest=True) + bld.SAMBA_BINARY('test_ndr_string', source='tests/test_ndr_string.c', deps=''' diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 5cdb3d27b77..389a142db7d 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1346,6 +1346,8 @@ plantestsuite("librpc.ndr.ndr_string", "none", [os.path.join(bindir(), "test_ndr_dns_nbt")]) plantestsuite("libcli.ldap.ldap_message", "none", [os.path.join(bindir(), "test_ldap_message")]) +plantestsuite("librpc.ndr.ndr_macros", "none", + [os.path.join(bindir(), "test_ndr_macros")]) # process restart and limit tests, these break the environment so need to run # in their own specific environment -- GitLab