From 5d28fe888ec60042ac080fc6bd00b1d69ace27a4 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Fri, 3 Apr 2020 12:18:03 +1300 Subject: [PATCH 1/9] CVE-2020-10704: lib util asn1: Add ASN.1 max tree depth Add maximum parse tree depth to the call to asn1_init, which will be used to limit the depth of the ASN.1 parse tree. 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 Reviewed-by: Andrew Bartlett --- auth/gensec/gensec_util.c | 2 +- lib/util/asn1.c | 17 +++++++++- lib/util/asn1.h | 9 +++++- lib/util/tests/asn1_tests.c | 2 +- libcli/auth/spnego_parse.c | 6 ++-- libcli/cldap/cldap.c | 2 +- libcli/ldap/ldap_message.c | 2 +- source3/lib/tldap.c | 4 +-- source3/lib/tldap_util.c | 4 +-- source3/libsmb/clispnego.c | 4 +-- source4/auth/gensec/gensec_krb5.c | 4 +-- source4/ldap_server/ldap_server.c | 2 +- source4/libcli/ldap/ldap_client.c | 2 +- source4/libcli/ldap/ldap_controls.c | 48 ++++++++++++++--------------- 14 files changed, 65 insertions(+), 43 deletions(-) diff --git a/auth/gensec/gensec_util.c b/auth/gensec/gensec_util.c index 20c9c2a1fbb..e185acc0c20 100644 --- a/auth/gensec/gensec_util.c +++ b/auth/gensec/gensec_util.c @@ -76,7 +76,7 @@ NTSTATUS gensec_generate_session_info_pac(TALLOC_CTX *mem_ctx, static bool gensec_gssapi_check_oid(const DATA_BLOB *blob, const char *oid) { bool ret = false; - struct asn1_data *data = asn1_init(NULL); + struct asn1_data *data = asn1_init(NULL, ASN1_MAX_TREE_DEPTH); if (!data) return false; diff --git a/lib/util/asn1.c b/lib/util/asn1.c index 60ddfa09bcf..c6c2f3bbec2 100644 --- a/lib/util/asn1.c +++ b/lib/util/asn1.c @@ -36,15 +36,19 @@ struct asn1_data { off_t ofs; struct nesting *nesting; bool has_error; + unsigned depth; + unsigned max_depth; }; /* allocate an asn1 structure */ -struct asn1_data *asn1_init(TALLOC_CTX *mem_ctx) +struct asn1_data *asn1_init(TALLOC_CTX *mem_ctx, unsigned max_depth) { struct asn1_data *ret = talloc_zero(mem_ctx, struct asn1_data); if (ret == NULL) { DEBUG(0,("asn1_init failed! out of memory\n")); + return ret; } + ret->max_depth = max_depth; return ret; } @@ -473,6 +477,11 @@ bool asn1_check_BOOLEAN(struct asn1_data *data, bool v) /* load a struct asn1_data structure with a lump of data, ready to be parsed */ bool asn1_load(struct asn1_data *data, DATA_BLOB blob) { + /* + * Save the maximum depth + */ + unsigned max_depth = data->max_depth; + ZERO_STRUCTP(data); data->data = (uint8_t *)talloc_memdup(data, blob.data, blob.length); if (!data->data) { @@ -480,6 +489,7 @@ bool asn1_load(struct asn1_data *data, DATA_BLOB blob) return false; } data->length = blob.length; + data->max_depth = max_depth; return true; } @@ -1096,9 +1106,14 @@ bool asn1_extract_blob(struct asn1_data *asn1, TALLOC_CTX *mem_ctx, */ void asn1_load_nocopy(struct asn1_data *data, uint8_t *buf, size_t len) { + /* + * Save max_depth + */ + unsigned max_depth = data->max_depth; ZERO_STRUCTP(data); data->data = buf; data->length = len; + data->max_depth = max_depth; } int asn1_peek_full_tag(DATA_BLOB blob, uint8_t tag, size_t *packet_size) diff --git a/lib/util/asn1.h b/lib/util/asn1.h index ddd69863574..fc365724e93 100644 --- a/lib/util/asn1.h +++ b/lib/util/asn1.h @@ -45,7 +45,14 @@ typedef struct asn1_data ASN1_DATA; #define ASN1_MAX_OIDS 20 -struct asn1_data *asn1_init(TALLOC_CTX *mem_ctx); +/* + * The maximum permitted depth for an ASN.1 parse tree, the limit is chosen + * to align with the value for windows. Note that this value will trigger + * ASAN stack overflow errors. + */ +#define ASN1_MAX_TREE_DEPTH 512 + +struct asn1_data *asn1_init(TALLOC_CTX *mem_ctx, unsigned max_depth); void asn1_free(struct asn1_data *data); bool asn1_has_error(const struct asn1_data *data); void asn1_set_error(struct asn1_data *data); diff --git a/lib/util/tests/asn1_tests.c b/lib/util/tests/asn1_tests.c index e4b386ad785..ab5262c4ffb 100644 --- a/lib/util/tests/asn1_tests.c +++ b/lib/util/tests/asn1_tests.c @@ -330,7 +330,7 @@ static bool test_asn1_Integer(struct torture_context *tctx) DATA_BLOB blob; int val; - data = asn1_init(mem_ctx); + data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) { goto err; } diff --git a/libcli/auth/spnego_parse.c b/libcli/auth/spnego_parse.c index f538b44552c..f7f19b10778 100644 --- a/libcli/auth/spnego_parse.c +++ b/libcli/auth/spnego_parse.c @@ -296,7 +296,7 @@ ssize_t spnego_read_data(TALLOC_CTX *mem_ctx, DATA_BLOB data, struct spnego_data return ret; } - asn1 = asn1_init(mem_ctx); + asn1 = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (asn1 == NULL) { return -1; } @@ -339,7 +339,7 @@ ssize_t spnego_read_data(TALLOC_CTX *mem_ctx, DATA_BLOB data, struct spnego_data ssize_t spnego_write_data(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct spnego_data *spnego) { - struct asn1_data *asn1 = asn1_init(mem_ctx); + struct asn1_data *asn1 = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); ssize_t ret = -1; if (asn1 == NULL) { @@ -411,7 +411,7 @@ bool spnego_write_mech_types(TALLOC_CTX *mem_ctx, DATA_BLOB *blob) { bool ret = false; - struct asn1_data *asn1 = asn1_init(mem_ctx); + struct asn1_data *asn1 = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (asn1 == NULL) { return false; diff --git a/libcli/cldap/cldap.c b/libcli/cldap/cldap.c index daba37a21d7..8fa9ce0b273 100644 --- a/libcli/cldap/cldap.c +++ b/libcli/cldap/cldap.c @@ -229,7 +229,7 @@ static bool cldap_socket_recv_dgram(struct cldap_socket *c, goto error; } - asn1 = asn1_init(in); + asn1 = asn1_init(in, ASN1_MAX_TREE_DEPTH); if (!asn1) { goto nomem; } diff --git a/libcli/ldap/ldap_message.c b/libcli/ldap/ldap_message.c index f21598374a1..ba82bddeab1 100644 --- a/libcli/ldap/ldap_message.c +++ b/libcli/ldap/ldap_message.c @@ -390,7 +390,7 @@ _PUBLIC_ bool ldap_encode(struct ldap_message *msg, const struct ldap_control_handler *control_handlers, DATA_BLOB *result, TALLOC_CTX *mem_ctx) { - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); int i, j; if (!data) return false; diff --git a/source3/lib/tldap.c b/source3/lib/tldap.c index d6c6e8859a6..bf5fc05d785 100644 --- a/source3/lib/tldap.c +++ b/source3/lib/tldap.c @@ -632,7 +632,7 @@ static void tldap_msg_received(struct tevent_req *subreq) goto fail; } - data = asn1_init(talloc_tos()); + data = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH); if (data == NULL) { status = TLDAP_NO_MEMORY; goto fail; @@ -763,7 +763,7 @@ static struct tevent_req *tldap_req_create(TALLOC_CTX *mem_ctx, if (req == NULL) { return NULL; } - state->out = asn1_init(state); + state->out = asn1_init(state, ASN1_MAX_TREE_DEPTH); if (state->out == NULL) { goto err; } diff --git a/source3/lib/tldap_util.c b/source3/lib/tldap_util.c index 54a9eb30bbe..9841f1ee6eb 100644 --- a/source3/lib/tldap_util.c +++ b/source3/lib/tldap_util.c @@ -631,7 +631,7 @@ static struct tevent_req *tldap_ship_paged_search( struct tldap_control *pgctrl; struct asn1_data *asn1 = NULL; - asn1 = asn1_init(state); + asn1 = asn1_init(state, ASN1_MAX_TREE_DEPTH); if (asn1 == NULL) { return NULL; } @@ -770,7 +770,7 @@ static void tldap_search_paged_done(struct tevent_req *subreq) TALLOC_FREE(state->cookie.data); - asn1 = asn1_init(talloc_tos()); + asn1 = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH); if (tevent_req_nomem(asn1, req)) { return; } diff --git a/source3/libsmb/clispnego.c b/source3/libsmb/clispnego.c index 4a0fbcd73af..1608f6a9960 100644 --- a/source3/libsmb/clispnego.c +++ b/source3/libsmb/clispnego.c @@ -50,7 +50,7 @@ bool spnego_parse_negTokenInit(TALLOC_CTX *ctx, *secblob = data_blob_null; } - data = asn1_init(talloc_tos()); + data = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH); if (data == NULL) { return false; } @@ -171,7 +171,7 @@ DATA_BLOB spnego_gen_krb5_wrap(TALLOC_CTX *ctx, const DATA_BLOB ticket, const ui ASN1_DATA *data; DATA_BLOB ret = data_blob_null; - data = asn1_init(talloc_tos()); + data = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH); if (data == NULL) { return data_blob_null; } diff --git a/source4/auth/gensec/gensec_krb5.c b/source4/auth/gensec/gensec_krb5.c index 0323da87d29..b735063656a 100644 --- a/source4/auth/gensec/gensec_krb5.c +++ b/source4/auth/gensec/gensec_krb5.c @@ -444,7 +444,7 @@ static DATA_BLOB gensec_gssapi_gen_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLO struct asn1_data *data; DATA_BLOB ret = data_blob_null; - data = asn1_init(mem_ctx); + data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data || !ticket->data) { return ret; } @@ -478,7 +478,7 @@ static DATA_BLOB gensec_gssapi_gen_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLO static bool gensec_gssapi_parse_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, DATA_BLOB *ticket, uint8_t tok_id[2]) { bool ret = false; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); int data_remaining; if (!data) { diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c index 90d32c6833a..ebd81127f37 100644 --- a/source4/ldap_server/ldap_server.c +++ b/source4/ldap_server/ldap_server.c @@ -560,7 +560,7 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq) return; } - asn1 = asn1_init(call); + asn1 = asn1_init(call, ASN1_MAX_TREE_DEPTH); if (asn1 == NULL) { ldapsrv_terminate_connection(conn, "no memory"); return; diff --git a/source4/libcli/ldap/ldap_client.c b/source4/libcli/ldap/ldap_client.c index 1cbcd0d42d5..1113af14f92 100644 --- a/source4/libcli/ldap/ldap_client.c +++ b/source4/libcli/ldap/ldap_client.c @@ -284,7 +284,7 @@ static void ldap_connection_recv_done(struct tevent_req *subreq) return; } - asn1 = asn1_init(conn); + asn1 = asn1_init(conn, ASN1_MAX_TREE_DEPTH); if (asn1 == NULL) { TALLOC_FREE(msg); ldap_error_handler(conn, NT_STATUS_NO_MEMORY); diff --git a/source4/libcli/ldap/ldap_controls.c b/source4/libcli/ldap/ldap_controls.c index 716ca148308..df012a158e0 100644 --- a/source4/libcli/ldap/ldap_controls.c +++ b/source4/libcli/ldap/ldap_controls.c @@ -32,7 +32,7 @@ static bool decode_server_sort_response(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; DATA_BLOB attr; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_sort_resp_control *lsrc; if (!data) return false; @@ -79,7 +79,7 @@ static bool decode_server_sort_request(void *mem_ctx, DATA_BLOB in, void *_out) void **out = (void **)_out; DATA_BLOB attr; DATA_BLOB rule; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_server_sort_control **lssc; int num; @@ -166,7 +166,7 @@ static bool decode_extended_dn_request(void *mem_ctx, DATA_BLOB in, void *_out) return true; } - data = asn1_init(mem_ctx); + data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; if (!asn1_load(data, in)) { @@ -198,7 +198,7 @@ static bool decode_extended_dn_request(void *mem_ctx, DATA_BLOB in, void *_out) static bool decode_sd_flags_request(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_sd_flags_control *lsdfc; if (!data) return false; @@ -232,7 +232,7 @@ static bool decode_sd_flags_request(void *mem_ctx, DATA_BLOB in, void *_out) static bool decode_search_options_request(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_search_options_control *lsoc; if (!data) return false; @@ -267,7 +267,7 @@ static bool decode_paged_results_request(void *mem_ctx, DATA_BLOB in, void *_out { void **out = (void **)_out; DATA_BLOB cookie; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_paged_control *lprc; if (!data) return false; @@ -316,7 +316,7 @@ static bool decode_dirsync_request(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; DATA_BLOB cookie; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_dirsync_control *ldc; if (!data) return false; @@ -372,7 +372,7 @@ static bool decode_asq_control(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; DATA_BLOB source_attribute; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_asq_control *lac; if (!data) return false; @@ -433,7 +433,7 @@ static bool decode_verify_name_request(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; DATA_BLOB name; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_verify_name_control *lvnc; int len; @@ -485,7 +485,7 @@ static bool decode_verify_name_request(void *mem_ctx, DATA_BLOB in, void *_out) static bool encode_verify_name_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_verify_name_control *lvnc = talloc_get_type(in, struct ldb_verify_name_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); DATA_BLOB gc_utf16; if (!data) return false; @@ -528,7 +528,7 @@ static bool decode_vlv_request(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; DATA_BLOB assertion_value, context_id; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_vlv_req_control *lvrc; if (!data) return false; @@ -626,7 +626,7 @@ static bool decode_vlv_response(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; DATA_BLOB context_id; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct ldb_vlv_resp_control *lvrc; if (!data) return false; @@ -682,7 +682,7 @@ static bool decode_vlv_response(void *mem_ctx, DATA_BLOB in, void *_out) static bool encode_server_sort_response(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_sort_resp_control *lsrc = talloc_get_type(in, struct ldb_sort_resp_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -716,7 +716,7 @@ static bool encode_server_sort_response(void *mem_ctx, void *in, DATA_BLOB *out) static bool encode_server_sort_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_server_sort_control **lssc = talloc_get_type(in, struct ldb_server_sort_control *); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); int num; if (!data) return false; @@ -782,7 +782,7 @@ static bool encode_extended_dn_request(void *mem_ctx, void *in, DATA_BLOB *out) return true; } - data = asn1_init(mem_ctx); + data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -810,7 +810,7 @@ static bool encode_extended_dn_request(void *mem_ctx, void *in, DATA_BLOB *out) static bool encode_sd_flags_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_sd_flags_control *lsdfc = talloc_get_type(in, struct ldb_sd_flags_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -838,7 +838,7 @@ static bool encode_sd_flags_request(void *mem_ctx, void *in, DATA_BLOB *out) static bool encode_search_options_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_search_options_control *lsoc = talloc_get_type(in, struct ldb_search_options_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -866,7 +866,7 @@ static bool encode_search_options_request(void *mem_ctx, void *in, DATA_BLOB *ou static bool encode_paged_results_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_paged_control *lprc = talloc_get_type(in, struct ldb_paged_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -901,7 +901,7 @@ static bool encode_paged_results_request(void *mem_ctx, void *in, DATA_BLOB *out static bool encode_asq_control(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_asq_control *lac = talloc_get_type(in, struct ldb_asq_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -936,7 +936,7 @@ static bool encode_asq_control(void *mem_ctx, void *in, DATA_BLOB *out) static bool encode_dirsync_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_dirsync_control *ldc = talloc_get_type(in, struct ldb_dirsync_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -972,7 +972,7 @@ static bool encode_dirsync_request(void *mem_ctx, void *in, DATA_BLOB *out) static bool encode_vlv_request(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_vlv_req_control *lvrc = talloc_get_type(in, struct ldb_vlv_req_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -1040,7 +1040,7 @@ static bool encode_vlv_request(void *mem_ctx, void *in, DATA_BLOB *out) static bool encode_vlv_response(void *mem_ctx, void *in, DATA_BLOB *out) { struct ldb_vlv_resp_control *lvrc = talloc_get_type(in, struct ldb_vlv_resp_control); - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -1083,7 +1083,7 @@ static bool encode_openldap_dereference(void *mem_ctx, void *in, DATA_BLOB *out) { struct dsdb_openldap_dereference_control *control = talloc_get_type(in, struct dsdb_openldap_dereference_control); int i,j; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); if (!data) return false; @@ -1132,7 +1132,7 @@ static bool encode_openldap_dereference(void *mem_ctx, void *in, DATA_BLOB *out) static bool decode_openldap_dereference(void *mem_ctx, DATA_BLOB in, void *_out) { void **out = (void **)_out; - struct asn1_data *data = asn1_init(mem_ctx); + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); struct dsdb_openldap_dereference_result_control *control; struct dsdb_openldap_dereference_result **r = NULL; int i = 0; -- 2.17.1 From 400f03fc3fbe36de463cf19c8270d21a783d5909 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 2 Apr 2020 15:25:53 +1300 Subject: [PATCH 2/9] CVE-2020-10704: libcli ldap: test recursion depth in ldap_decode_filter_tree Add tests to check that ASN.1 ldap requests with deeply nested elements are rejected. Previously there was no check on the on the depth of nesting and excessive nesting could cause a stack overflow. 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 Reviewed-by: Andrew Bartlett --- libcli/ldap/tests/data/10000-or.dat | Bin 0 -> 39875 bytes libcli/ldap/tests/data/ldap-recursive.dat | Bin 0 -> 970 bytes libcli/ldap/tests/ldap_message_test.c | 271 ++++++++++++++++++++++ libcli/ldap/wscript_build | 15 ++ selftest/knownfail.d/ldap_message | 2 + source4/selftest/tests.py | 2 + 6 files changed, 290 insertions(+) create mode 100644 libcli/ldap/tests/data/10000-or.dat create mode 100644 libcli/ldap/tests/data/ldap-recursive.dat create mode 100644 libcli/ldap/tests/ldap_message_test.c create mode 100644 selftest/knownfail.d/ldap_message diff --git a/libcli/ldap/tests/data/10000-or.dat b/libcli/ldap/tests/data/10000-or.dat new file mode 100644 index 0000000000000000000000000000000000000000..e2d6de2ce330e80d1c2f28854b3542b789de9834 GIT binary patch literal 39875 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 literal 0 HcmV?d00001 diff --git a/libcli/ldap/tests/data/ldap-recursive.dat b/libcli/ldap/tests/data/ldap-recursive.dat new file mode 100644 index 0000000000000000000000000000000000000000..dd18d857660df41c30024264d4623680a9404613 GIT binary patch literal 970 zcmXqLVm`*i$ei57yq|@EixGsFz=)BNVWC1hcYab. + * + */ + +/* + * 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..d0aa7c11ced 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 + ''', + install=False +) 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 c672637e2e0..4504646d9a5 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1286,6 +1286,8 @@ plantestsuite("samba4.dsdb.samdb.ldb_modules.group_audit.errors", "none", [os.path.join(bindir(), "test_group_audit_errors")]) plantestsuite("samba4.dcerpc.dnsserver.dnsutils", "none", [os.path.join(bindir(), "test_rpc_dns_server_dnsutils")]) +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 From 7030f7b9d3984964f3eae279ec6307a79885e2b7 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Wed, 8 Apr 2020 15:30:52 +1200 Subject: [PATCH 3/9] CVE-2020-10704: lib util asn1: Check parse tree depth Check the current depth of the parse tree and reject the input if the depth exceeds that passed to asn1_init 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 Reviewed-by: Andrew Bartlett --- lib/util/asn1.c | 13 +++++++++++++ selftest/knownfail.d/ldap_message | 2 -- 2 files changed, 13 insertions(+), 2 deletions(-) delete mode 100644 selftest/knownfail.d/ldap_message diff --git a/lib/util/asn1.c b/lib/util/asn1.c index c6c2f3bbec2..def71585d38 100644 --- a/lib/util/asn1.c +++ b/lib/util/asn1.c @@ -640,6 +640,16 @@ bool asn1_start_tag(struct asn1_data *data, uint8_t tag) uint8_t b; struct nesting *nesting; + /* + * Check the depth of the parse tree and prevent it from growing + * too large. + */ + data->depth++; + if (data->depth > data->max_depth) { + data->has_error = true; + return false; + } + if (!asn1_read_uint8(data, &b)) return false; @@ -696,6 +706,9 @@ bool asn1_end_tag(struct asn1_data *data) { struct nesting *nesting; + if (data->depth > 0) { + data->depth--; + } /* make sure we read it all */ if (asn1_tag_remaining(data) != 0) { data->has_error = true; diff --git a/selftest/knownfail.d/ldap_message b/selftest/knownfail.d/ldap_message deleted file mode 100644 index 242eff45e59..00000000000 --- a/selftest/knownfail.d/ldap_message +++ /dev/null @@ -1,2 +0,0 @@ -^libcli.ldap.ldap_message.test_recursion_depth_greater_than_max\(none\) -^libcli.ldap.ldap_message.test_recursion_depth_large\(none\) -- 2.17.1 From 6ee4b469bf69899e742bfb59b0a880901ee9ee83 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 14 Apr 2020 13:32:32 +1200 Subject: [PATCH 4/9] CVE-2020-10704: ldapserver tests: Limit search request sizes Add tests to ensure that overly long (> 256000 bytes) LDAP search requests are rejected. 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 Reviewed-by: Andrew Bartlett --- python/samba/tests/ldap_raw.py | 234 +++++++++++++++++++++++++++++++++ selftest/knownfail.d/ldap_raw | 1 + source4/selftest/tests.py | 6 + 3 files changed, 241 insertions(+) create mode 100644 python/samba/tests/ldap_raw.py create mode 100644 selftest/knownfail.d/ldap_raw diff --git a/python/samba/tests/ldap_raw.py b/python/samba/tests/ldap_raw.py new file mode 100644 index 00000000000..334fabce230 --- /dev/null +++ b/python/samba/tests/ldap_raw.py @@ -0,0 +1,234 @@ +# Integration tests for the ldap server, using raw socket IO +# +# Tests for handling of malformed or large packets. +# +# 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 . +# + +import socket + +import samba.tests +from samba.tests import TestCase + + +# +# LDAP Operations +# +SEARCH = b'\x63' + +EQUALS = b'\xa3' + + +# +# ASN.1 Element types +# +BOOLEAN = b'\x01' +INTEGER = b'\x02' +OCTET_STRING = b'\x04' +NULL = b'\x05' +ENUMERATED = b'\x0a' +SEQUENCE = b'\x30' +SET = b'\x31' + + +# +# ASN.1 Helper functions. +# +def encode_element(ber_type, data): + ''' Encode an ASN.1 BER element. ''' + if data is None: + return ber_type + encode_length(0) + return ber_type + encode_length(len(data)) + data + + +def encode_length(length): + ''' Encode the length of an ASN.1 BER element. ''' + + if length > 0xFFFFFF: + return b'\x84' + length.to_bytes(4, "big") + if length > 0xFFFF: + return b'\x83' + length.to_bytes(3, "big") + if length > 0xFF: + return b'\x82' + length.to_bytes(2, "big") + if length > 0x7F: + return b'\x81' + length.to_bytes(1, "big") + return length.to_bytes(1, "big") + + +def encode_string(string): + ''' Encode an octet string ''' + return encode_element(OCTET_STRING, string) + + +def encode_boolean(boolean): + ''' Encode a boolean value ''' + if boolean: + return encode_element(BOOLEAN, b'\xFF') + return encode_element(BOOLEAN, b'\x00') + + +def encode_integer(integer): + ''' Encode an integer value ''' + bit_len = integer.bit_length() + byte_len = (bit_len // 8) + 1 + return encode_element(INTEGER, integer.to_bytes(byte_len, "big")) + + +def encode_enumerated(enum): + ''' Encode an enumerated value ''' + return encode_element(ENUMERATED, enum.to_bytes(1, "big")) + + +def encode_sequence(sequence): + ''' Encode a sequence ''' + return encode_element(SEQUENCE, sequence) + + +class RawLdapTest(TestCase): + """A raw Ldap Test case.""" + + def setUp(self): + super(RawLdapTest, self).setUp() + + self.host = samba.tests.env_get_var_value('SERVER') + self.port = 389 + self.socket = None + self.connect() + + def tearDown(self): + self.disconnect() + super(RawLdapTest, self).tearDown() + + def disconnect(self): + ''' Disconnect from and clean up the connection to the server ''' + if self.socket is None: + return + self.socket.close() + self.socket = None + + def connect(self): + ''' Open a socket stream connection to the server ''' + try: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.settimeout(10) + self.socket.connect((self.host, self.port)) + except socket.error: + self.socket.close() + raise + + def send(self, req): + ''' Send the request to the server ''' + try: + self.socket.sendall(req) + except socket.error: + self.disconnect() + raise + + def recv(self, num_recv=0xffff, timeout=None): + ''' recv an array of bytes from the server ''' + data = None + try: + if timeout is not None: + self.socket.settimeout(timeout) + data = self.socket.recv(num_recv, 0) + self.socket.settimeout(10) + if len(data) == 0: + self.disconnect() + return None + except socket.timeout: + # We ignore timeout's as the ldap server will drop the connection + # on the errors we're testing. So returning None on a timeout is + # the desired behaviour. + self.socket.settimeout(10) + except socket.error: + self.disconnect() + raise + return data + + def test_search_equals_maximum_permitted_size(self): + ''' + Check that an LDAP search request equal to the maximum size is accepted + ''' + + # Lets build an ldap search packet to query the RootDSE + header = encode_string(None) # Base DN, "" + header += encode_enumerated(0) # Enumeration scope + header += encode_enumerated(0) # Enumeration dereference + header += encode_integer(0) # Integer size limit + header += encode_integer(0) # Integer time limit + header += encode_boolean(False) # Boolean attributes only + + # + # build an equality search of the form x...x=y...y + # With the length of x...x and y...y chosen to generate an + # ldap request of 256000 bytes. + x = encode_string(b'x' * 127974) + y = encode_string(b'y' * 127979) + equals = encode_element(EQUALS, x + y) + trailer = encode_sequence(None) + search = encode_element(SEARCH, header + equals + trailer) + + msg_no = encode_integer(1) + packet = encode_sequence(msg_no + search) + # + # The length of the packet should be equal to the + # Maximum length of a search query + self.assertEqual(256000, len(packet)) + + self.send(packet) + data = self.recv() + self.assertIsNotNone(data) + + # Should be a sequence + self.assertEqual(SEQUENCE, data[0:1]) + + def test_search_exceeds_maximum_permitted_size(self): + ''' + Test that a search query longer than the maximum permitted + size is rejected. + ''' + + # Lets build an ldap search packet to query the RootDSE + header = encode_string(None) # Base DN, "" + header += encode_enumerated(0) # Enumeration scope + header += encode_enumerated(0) # Enumeration dereference + header += encode_integer(0) # Integer size limit + header += encode_integer(0) # Integer time limit + header += encode_boolean(False) # Boolean attributes only + + # + # build an equality search of the form x...x=y...y + # With the length of x...x and y...y chosen to generate an + # ldap request of 256001 bytes. + x = encode_string(b'x' * 127979) + y = encode_string(b'y' * 127975) + equals = encode_element(EQUALS, x + y) + trailer = encode_sequence(None) + search = encode_element(SEARCH, header + equals + trailer) + + msg_no = encode_integer(1) + packet = encode_sequence(msg_no + search) + # + # The length of the sequence data should be one greater than the + # Maximum length of a search query + self.assertEqual(256001, len(packet)) + + self.send(packet) + data = self.recv() + # + # The connection should be closed by the server and we should not + # see any data. + self.assertIsNone(data) diff --git a/selftest/knownfail.d/ldap_raw b/selftest/knownfail.d/ldap_raw new file mode 100644 index 00000000000..8bd2ee55166 --- /dev/null +++ b/selftest/knownfail.d/ldap_raw @@ -0,0 +1 @@ +^samba.tests.ldap_raw.samba.tests.ldap_raw.RawLdapTest.test_search_exceeds_maximum_permitted_size\(ad_dc\) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 4504646d9a5..db268acc2ab 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -864,6 +864,12 @@ for env in ["ad_dc_ntvfs:local", "ad_dc:local", "promoted_dc:local"]: planoldpythontestsuite(env, "samba.tests.blackbox.smbcontrol", py3_compatible=True) +planoldpythontestsuite("ad_dc", + "samba.tests.ldap_raw", + py3_compatible= True, + extra_args=['-U"$USERNAME%$PASSWORD"'], + environ={'TEST_ENV': 'ad_dc'}) + planoldpythontestsuite("none", "samba.tests.blackbox.undoguididx") plantestsuite_loadlist("samba4.ldap.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) -- 2.17.1 From 0100dbbd2db3daf629defda8d0b61f0fdd13dee1 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Thu, 16 Apr 2020 10:49:29 +1200 Subject: [PATCH 5/9] CVE-2020-10704: ldapserver tests: Python 2 comaptibility The test python/samba/tests/ldap_raw.py does not run under python 3 which means the CI task build_ad_dc_py2 fails. The test is run and passes in the CI task build_ad_dc. This patch adds a check for the Python version and skips the tests if running under python 2, allowing CI to run for V4.10. This patch is only applied to version 4.10. Signed-off-by: Gary Lockyer --- python/samba/tests/ldap_raw.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/python/samba/tests/ldap_raw.py b/python/samba/tests/ldap_raw.py index 334fabce230..2e55f785c88 100644 --- a/python/samba/tests/ldap_raw.py +++ b/python/samba/tests/ldap_raw.py @@ -19,6 +19,7 @@ # import socket +import sys import samba.tests from samba.tests import TestCase @@ -162,6 +163,13 @@ class RawLdapTest(TestCase): ''' Check that an LDAP search request equal to the maximum size is accepted ''' + # + # Test is not compatable with python 2 so skip it for the + # backports. + # + if sys.version_info < (3, 0): + self.skipTest("Test is not python2 compatable") + return # Lets build an ldap search packet to query the RootDSE header = encode_string(None) # Base DN, "" @@ -200,6 +208,13 @@ class RawLdapTest(TestCase): Test that a search query longer than the maximum permitted size is rejected. ''' + # + # Test is not compatable with python 2 so skip it for the + # backports. + # + if sys.version_info < (3, 0): + self.skipTest("Test is not python2 compatable") + return # Lets build an ldap search packet to query the RootDSE header = encode_string(None) # Base DN, "" -- 2.17.1 From 20bb5d4297017539916a2f40be518d27f72f26bf Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 7 Apr 2020 09:09:01 +1200 Subject: [PATCH 6/9] CVE-2020-10704: smb.conf: Add max ldap request sizes Add two new smb.conf parameters to control the maximum permitted ldap request size. Adds: ldap max anonymous request size default 250Kb ldap max authenticated request size default 16Mb 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 Reviewed-by: Andrew Bartlett --- .../smbdotconf/ldap/ldapmaxanonrequest.xml | 18 ++++++++++++++++++ .../smbdotconf/ldap/ldapmaxauthrequest.xml | 18 ++++++++++++++++++ lib/param/loadparm.c | 5 +++++ source3/param/loadparm.c | 3 +++ 4 files changed, 44 insertions(+) create mode 100644 docs-xml/smbdotconf/ldap/ldapmaxanonrequest.xml create mode 100644 docs-xml/smbdotconf/ldap/ldapmaxauthrequest.xml diff --git a/docs-xml/smbdotconf/ldap/ldapmaxanonrequest.xml b/docs-xml/smbdotconf/ldap/ldapmaxanonrequest.xml new file mode 100644 index 00000000000..61bdcec674d --- /dev/null +++ b/docs-xml/smbdotconf/ldap/ldapmaxanonrequest.xml @@ -0,0 +1,18 @@ + + + + This parameter specifies the maximum permitted size (in bytes) + for an LDAP request received on an anonymous connection. + + + + If the request size exceeds this limit the request will be + rejected. + + +256000 +500000 + diff --git a/docs-xml/smbdotconf/ldap/ldapmaxauthrequest.xml b/docs-xml/smbdotconf/ldap/ldapmaxauthrequest.xml new file mode 100644 index 00000000000..c5934f73f95 --- /dev/null +++ b/docs-xml/smbdotconf/ldap/ldapmaxauthrequest.xml @@ -0,0 +1,18 @@ + + + + This parameter specifies the maximum permitted size (in bytes) + for an LDAP request received on an authenticated connection. + + + + If the request size exceeds this limit the request will be + rejected. + + +16777216 +4194304 + diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c index e4d27cae8ea..a63e665f985 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -3008,6 +3008,11 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) lpcfg_do_global_parameter(lp_ctx, "store dos attributes", "yes"); + lpcfg_do_global_parameter( + lp_ctx, "ldap max anonymous request size", "256000"); + lpcfg_do_global_parameter( + lp_ctx, "ldap max authenticated request size", "16777216"); + for (i = 0; parm_table[i].label; i++) { if (!(lp_ctx->flags[i] & FLAG_CMDLINE)) { lp_ctx->flags[i] |= FLAG_DEFAULT; diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index 12f32192bfe..b324e58cb4e 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -959,6 +959,9 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) Globals.prefork_backoff_increment = 10; Globals.prefork_maximum_backoff = 120; + Globals.ldap_max_anonymous_request_size = 256000; + Globals.ldap_max_authenticated_request_size = 16777216; + /* Now put back the settings that were set with lp_set_cmdline() */ apply_lp_set_cmdline(); } -- 2.17.1 From e78c4cc89033abd68b8b09a8d580b518262e5967 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Wed, 8 Apr 2020 15:32:22 +1200 Subject: [PATCH 7/9] CVE-2020-10704: S4 ldap server: Limit request sizes Check the size of authenticated and anonymous ldap requests and reject them if they exceed the limits in smb.conf 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 Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/ldap_raw | 1 - source4/ldap_server/ldap_server.c | 96 ++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) delete mode 100644 selftest/knownfail.d/ldap_raw diff --git a/selftest/knownfail.d/ldap_raw b/selftest/knownfail.d/ldap_raw deleted file mode 100644 index 8bd2ee55166..00000000000 --- a/selftest/knownfail.d/ldap_raw +++ /dev/null @@ -1 +0,0 @@ -^samba.tests.ldap_raw.samba.tests.ldap_raw.RawLdapTest.test_search_exceeds_maximum_permitted_size\(ad_dc\) diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c index ebd81127f37..c9ac271c4a9 100644 --- a/source4/ldap_server/ldap_server.c +++ b/source4/ldap_server/ldap_server.c @@ -441,6 +441,10 @@ static void ldapsrv_accept_tls_done(struct tevent_req *subreq) } static void ldapsrv_call_read_done(struct tevent_req *subreq); +static NTSTATUS ldapsrv_packet_check( + void *private_data, + DATA_BLOB blob, + size_t *packet_size); static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn) { @@ -494,7 +498,7 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn) conn->connection->event.ctx, conn->sockets.active, 7, /* initial_read_size */ - ldap_full_packet, + ldapsrv_packet_check, conn); if (subreq == NULL) { ldapsrv_terminate_connection(conn, "ldapsrv_call_read_next: " @@ -520,6 +524,9 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn) } static void ldapsrv_call_process_done(struct tevent_req *subreq); +static int ldapsrv_check_packet_size( + struct ldapsrv_connection *conn, + size_t size); static void ldapsrv_call_read_done(struct tevent_req *subreq) { @@ -530,6 +537,7 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq) struct ldapsrv_call *call; struct asn1_data *asn1; DATA_BLOB blob; + int ret = LDAP_SUCCESS; conn->sockets.read_req = NULL; @@ -560,6 +568,14 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq) return; } + ret = ldapsrv_check_packet_size(conn, blob.length); + if (ret != LDAP_SUCCESS) { + ldapsrv_terminate_connection( + conn, + "Request packet too large"); + return; + } + asn1 = asn1_init(call, ASN1_MAX_TREE_DEPTH); if (asn1 == NULL) { ldapsrv_terminate_connection(conn, "no memory"); @@ -1292,6 +1308,84 @@ failed: } +/* + * Check the size of an ldap request packet. + * + * For authenticated connections the maximum packet size is controlled by + * the smb.conf parameter "ldap max authenticated request size" + * + * For anonymous connections the maximum packet size is controlled by + * the smb.conf parameter "ldap max anonymous request size" + */ +static int ldapsrv_check_packet_size( + struct ldapsrv_connection *conn, + size_t size) +{ + bool is_anonymous = false; + size_t max_size = 0; + + max_size = lpcfg_ldap_max_anonymous_request_size(conn->lp_ctx); + if (size <= max_size) { + return LDAP_SUCCESS; + } + + /* + * Request is larger than the maximum unauthenticated request size. + * As this code is called frequently we avoid calling + * security_token_is_anonymous if possible + */ + if (conn->session_info != NULL && + conn->session_info->security_token != NULL) { + is_anonymous = security_token_is_anonymous( + conn->session_info->security_token); + } + + if (is_anonymous) { + DBG_WARNING( + "LDAP request size (%zu) exceeds (%zu)\n", + size, + max_size); + return LDAP_UNWILLING_TO_PERFORM; + } + + max_size = lpcfg_ldap_max_authenticated_request_size(conn->lp_ctx); + if (size > max_size) { + DBG_WARNING( + "LDAP request size (%zu) exceeds (%zu)\n", + size, + max_size); + return LDAP_UNWILLING_TO_PERFORM; + } + return LDAP_SUCCESS; + +} + +/* + * Check that the blob contains enough data to be a valid packet + * If there is a packet header check the size to ensure that it does not + * exceed the maximum sizes. + * + */ +static NTSTATUS ldapsrv_packet_check( + void *private_data, + DATA_BLOB blob, + size_t *packet_size) +{ + NTSTATUS ret; + struct ldapsrv_connection *conn = private_data; + int result = LDB_SUCCESS; + + ret = ldap_full_packet(private_data, blob, packet_size); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + result = ldapsrv_check_packet_size(conn, *packet_size); + if (result != LDAP_SUCCESS) { + return NT_STATUS_LDAP(result); + } + return NT_STATUS_OK; +} + NTSTATUS server_service_ldap_init(TALLOC_CTX *ctx) { static const struct service_details details = { -- 2.17.1 From 34a8a8673c8c75d9599f08466311cc49fe4824c7 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Wed, 8 Apr 2020 08:49:23 +1200 Subject: [PATCH 8/9] CVE-2020-10704: libcli ldap_message: Add search size limits to ldap_decode Add search request size limits to ldap_decode calls. The ldap server uses the smb.conf variable "ldap max search request size" which defaults to 250Kb. For cldap the limit is hard coded as 4096. 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 Reviewed-by: Andrew Bartlett --- .../smbdotconf/ldap/ldapmaxsearchrequest.xml | 18 ++++++++++++++ lib/param/loadparm.c | 2 ++ libcli/cldap/cldap.c | 18 +++++++++++--- libcli/ldap/ldap_message.c | 1 + libcli/ldap/ldap_message.h | 5 ++++ libcli/ldap/tests/ldap_message_test.c | 24 +++++++++++++++---- source3/param/loadparm.c | 1 + source4/ldap_server/ldap_server.c | 10 ++++++-- source4/libcli/ldap/ldap_client.c | 3 ++- 9 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 docs-xml/smbdotconf/ldap/ldapmaxsearchrequest.xml diff --git a/docs-xml/smbdotconf/ldap/ldapmaxsearchrequest.xml b/docs-xml/smbdotconf/ldap/ldapmaxsearchrequest.xml new file mode 100644 index 00000000000..ebeb0816c01 --- /dev/null +++ b/docs-xml/smbdotconf/ldap/ldapmaxsearchrequest.xml @@ -0,0 +1,18 @@ + + + + This parameter specifies the maximum permitted size (in bytes) + for an LDAP search request. + + + + If the request size exceeds this limit the request will be + rejected. + + +256000 +4194304 + diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c index a63e665f985..4c3dfff24f3 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -3012,6 +3012,8 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) lp_ctx, "ldap max anonymous request size", "256000"); lpcfg_do_global_parameter( lp_ctx, "ldap max authenticated request size", "16777216"); + lpcfg_do_global_parameter( + lp_ctx, "ldap max search request size", "256000"); for (i = 0; parm_table[i].label; i++) { if (!(lp_ctx->flags[i] & FLAG_CMDLINE)) { diff --git a/libcli/cldap/cldap.c b/libcli/cldap/cldap.c index 8fa9ce0b273..25c1b40f8d9 100644 --- a/libcli/cldap/cldap.c +++ b/libcli/cldap/cldap.c @@ -111,6 +111,11 @@ struct cldap_search_state { struct tevent_req *req; }; +/* + * For CLDAP we limit the maximum search request size to 4kb + */ +#define MAX_SEARCH_REQUEST 4096 + static int cldap_socket_destructor(struct cldap_socket *c) { while (c->searches.list) { @@ -224,6 +229,9 @@ static bool cldap_socket_recv_dgram(struct cldap_socket *c, void *p; struct cldap_search_state *search; NTSTATUS status; + struct ldap_request_limits limits = { + .max_search_size = MAX_SEARCH_REQUEST + }; if (in->recv_errno != 0) { goto error; @@ -242,7 +250,7 @@ static bool cldap_socket_recv_dgram(struct cldap_socket *c, } /* this initial decode is used to find the message id */ - status = ldap_decode(asn1, NULL, in->ldap_msg); + status = ldap_decode(asn1, &limits, NULL, in->ldap_msg); if (!NT_STATUS_IS_OK(status)) { goto nterror; } @@ -770,6 +778,9 @@ NTSTATUS cldap_search_recv(struct tevent_req *req, struct cldap_search_state); struct ldap_message *ldap_msg; NTSTATUS status; + struct ldap_request_limits limits = { + .max_search_size = MAX_SEARCH_REQUEST + }; if (tevent_req_is_nterror(req, &status)) { goto failed; @@ -780,7 +791,7 @@ NTSTATUS cldap_search_recv(struct tevent_req *req, goto nomem; } - status = ldap_decode(state->response.asn1, NULL, ldap_msg); + status = ldap_decode(state->response.asn1, &limits, NULL, ldap_msg); if (!NT_STATUS_IS_OK(status)) { goto failed; } @@ -796,7 +807,8 @@ NTSTATUS cldap_search_recv(struct tevent_req *req, *io->out.response = ldap_msg->r.SearchResultEntry; /* decode the 2nd part */ - status = ldap_decode(state->response.asn1, NULL, ldap_msg); + status = ldap_decode( + state->response.asn1, &limits, NULL, ldap_msg); if (!NT_STATUS_IS_OK(status)) { goto failed; } diff --git a/libcli/ldap/ldap_message.c b/libcli/ldap/ldap_message.c index ba82bddeab1..d38fa0b3b61 100644 --- a/libcli/ldap/ldap_message.c +++ b/libcli/ldap/ldap_message.c @@ -1162,6 +1162,7 @@ static bool ldap_decode_attribs(TALLOC_CTX *mem_ctx, struct asn1_data *data, /* This routine returns LDAP status codes */ _PUBLIC_ NTSTATUS ldap_decode(struct asn1_data *data, + const struct ldap_request_limits *limits, const struct ldap_control_handler *control_handlers, struct ldap_message *msg) { diff --git a/libcli/ldap/ldap_message.h b/libcli/ldap/ldap_message.h index 2f64881c053..19bfb99ac97 100644 --- a/libcli/ldap/ldap_message.h +++ b/libcli/ldap/ldap_message.h @@ -213,10 +213,15 @@ struct ldap_control_handler { bool (*encode)(void *mem_ctx, void *in, DATA_BLOB *out); }; +struct ldap_request_limits { + unsigned max_search_size; +}; + struct asn1_data; struct ldap_message *new_ldap_message(TALLOC_CTX *mem_ctx); NTSTATUS ldap_decode(struct asn1_data *data, + const struct ldap_request_limits *limits, const struct ldap_control_handler *control_handlers, struct ldap_message *msg); bool ldap_encode(struct ldap_message *msg, diff --git a/libcli/ldap/tests/ldap_message_test.c b/libcli/ldap/tests/ldap_message_test.c index 9cc9cc5d8a0..c5aacd4bc6b 100644 --- a/libcli/ldap/tests/ldap_message_test.c +++ b/libcli/ldap/tests/ldap_message_test.c @@ -117,6 +117,9 @@ static void test_empty_input(void **state) NTSTATUS status; uint8_t buf[0]; size_t len = 0; + struct ldap_request_limits limits = { + .max_search_size = 256000, + }; asn1 = asn1_init(test_ctx, ASN1_MAX_TREE_DEPTH); @@ -127,7 +130,8 @@ static void test_empty_input(void **state) ldap_msg = talloc(test_ctx, struct ldap_message); assert_non_null(ldap_msg); - status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + status = ldap_decode( + asn1, &limits, samba_ldap_control_handlers(), ldap_msg); assert_ldap_status_equal(LDAP_PROTOCOL_ERROR, status); } @@ -149,6 +153,9 @@ static void test_recursion_depth_large(void **state) uint8_t *buffer = NULL; const size_t BUFF_SIZE = 1048576; size_t len; + struct ldap_request_limits limits = { + .max_search_size = 256000, + }; /* @@ -169,7 +176,8 @@ static void test_recursion_depth_large(void **state) ldap_msg = talloc(test_ctx, struct ldap_message); assert_non_null(ldap_msg); - status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + status = ldap_decode( + asn1, &limits, samba_ldap_control_handlers(), ldap_msg); assert_ldap_status_equal(LDAP_PROTOCOL_ERROR, status); } @@ -189,6 +197,9 @@ static void test_recursion_depth_equals_max(void **state) uint8_t *buffer = NULL; const size_t BUFF_SIZE = 1048576; size_t len; + struct ldap_request_limits limits = { + .max_search_size = 256000, + }; buffer = talloc_zero_array(test_ctx, uint8_t, BUFF_SIZE); @@ -205,7 +216,8 @@ static void test_recursion_depth_equals_max(void **state) ldap_msg = talloc(test_ctx, struct ldap_message); assert_non_null(ldap_msg); - status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + status = ldap_decode( + asn1, &limits, samba_ldap_control_handlers(), ldap_msg); assert_true(NT_STATUS_IS_OK(status)); } @@ -225,6 +237,9 @@ static void test_recursion_depth_greater_than_max(void **state) uint8_t *buffer = NULL; const size_t BUFF_SIZE = 1048576; size_t len; + struct ldap_request_limits limits = { + .max_search_size = 256000, + }; buffer = talloc_zero_array(test_ctx, uint8_t, BUFF_SIZE); @@ -241,7 +256,8 @@ static void test_recursion_depth_greater_than_max(void **state) ldap_msg = talloc(test_ctx, struct ldap_message); assert_non_null(ldap_msg); - status = ldap_decode(asn1, samba_ldap_control_handlers(), ldap_msg); + status = ldap_decode( + asn1, &limits, samba_ldap_control_handlers(), ldap_msg); assert_ldap_status_equal(LDAP_PROTOCOL_ERROR, status); } diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index b324e58cb4e..0db44e92d19 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -961,6 +961,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) Globals.ldap_max_anonymous_request_size = 256000; Globals.ldap_max_authenticated_request_size = 16777216; + Globals.ldap_max_search_request_size = 256000; /* Now put back the settings that were set with lp_set_cmdline() */ apply_lp_set_cmdline(); diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c index c9ac271c4a9..25c3b624abc 100644 --- a/source4/ldap_server/ldap_server.c +++ b/source4/ldap_server/ldap_server.c @@ -538,6 +538,7 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq) struct asn1_data *asn1; DATA_BLOB blob; int ret = LDAP_SUCCESS; + struct ldap_request_limits limits = {0}; conn->sockets.read_req = NULL; @@ -593,8 +594,13 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq) return; } - status = ldap_decode(asn1, samba_ldap_control_handlers(), - call->request); + limits.max_search_size = + lpcfg_ldap_max_search_request_size(conn->lp_ctx); + status = ldap_decode( + asn1, + &limits, + samba_ldap_control_handlers(), + call->request); if (!NT_STATUS_IS_OK(status)) { ldapsrv_terminate_connection(conn, nt_errstr(status)); return; diff --git a/source4/libcli/ldap/ldap_client.c b/source4/libcli/ldap/ldap_client.c index 1113af14f92..90a46937842 100644 --- a/source4/libcli/ldap/ldap_client.c +++ b/source4/libcli/ldap/ldap_client.c @@ -277,6 +277,7 @@ static void ldap_connection_recv_done(struct tevent_req *subreq) struct ldap_message *msg; struct asn1_data *asn1; DATA_BLOB blob; + struct ldap_request_limits limits = {0}; msg = talloc_zero(conn, struct ldap_message); if (msg == NULL) { @@ -306,7 +307,7 @@ static void ldap_connection_recv_done(struct tevent_req *subreq) asn1_load_nocopy(asn1, blob.data, blob.length); - status = ldap_decode(asn1, samba_ldap_control_handlers(), msg); + status = ldap_decode(asn1, &limits, samba_ldap_control_handlers(), msg); asn1_free(asn1); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(msg); -- 2.17.1 From 3e6bafdfe28fcac7878917116b77dfb2a753c662 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Wed, 8 Apr 2020 10:46:44 +1200 Subject: [PATCH 9/9] CVE-2020-10704 libcli ldap: Check search request lengths. Check the search request lengths against the limits passed to ldap_decode. 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 Reviewed-by: Andrew Bartlett --- lib/util/asn1.c | 7 +++++++ lib/util/asn1.h | 1 + libcli/ldap/ldap_message.c | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/lib/util/asn1.c b/lib/util/asn1.c index def71585d38..03d417d8104 100644 --- a/lib/util/asn1.c +++ b/lib/util/asn1.c @@ -1152,3 +1152,10 @@ int asn1_peek_full_tag(DATA_BLOB blob, uint8_t tag, size_t *packet_size) *packet_size = size; return 0; } + +/* + * Get the length of the ASN.1 data + */ +size_t asn1_get_length(const struct asn1_data *asn1) { + return asn1->length; +} diff --git a/lib/util/asn1.h b/lib/util/asn1.h index fc365724e93..de92a767f14 100644 --- a/lib/util/asn1.h +++ b/lib/util/asn1.h @@ -106,5 +106,6 @@ bool asn1_extract_blob(struct asn1_data *asn1, TALLOC_CTX *mem_ctx, DATA_BLOB *pblob); void asn1_load_nocopy(struct asn1_data *data, uint8_t *buf, size_t len); int asn1_peek_full_tag(DATA_BLOB blob, uint8_t tag, size_t *packet_size); +size_t asn1_get_length(const struct asn1_data *asn1); #endif /* _ASN_1_H */ diff --git a/libcli/ldap/ldap_message.c b/libcli/ldap/ldap_message.c index d38fa0b3b61..69a48279532 100644 --- a/libcli/ldap/ldap_message.c +++ b/libcli/ldap/ldap_message.c @@ -1259,7 +1259,11 @@ _PUBLIC_ NTSTATUS ldap_decode(struct asn1_data *data, struct ldap_SearchRequest *r = &msg->r.SearchRequest; int sizelimit, timelimit; const char **attrs = NULL; + size_t request_size = asn1_get_length(data); msg->type = LDAP_TAG_SearchRequest; + if (request_size > limits->max_search_size) { + goto prot_err; + } if (!asn1_start_tag(data, tag)) goto prot_err; if (!asn1_read_OctetString_talloc(msg, data, &r->basedn)) goto prot_err; if (!asn1_read_enumerated(data, (int *)(void *)&(r->scope))) goto prot_err; -- 2.17.1