From eb20778b5e66c4e011c9c264ddb8d29180fe6e89 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Mar 2023 14:25:56 +1300 Subject: [PATCH 01/41] CVE-2023-0614 lib/ldb: Avoid allocation and memcpy() for every wildcard match candidate The value can be quite large, the allocation will take much longer than the actual match and is repeated per candidate record. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15331 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton (cherry picked from commit cad96f59a08192df927fb1df4e9787c7f70991a2) [abartlet@samba.org Included in the security release as this makes the new large_ldap.py timeout test more reliable] --- lib/ldb/common/ldb_match.c | 60 +++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c index 2f4d41f3441..51376871b4c 100644 --- a/lib/ldb/common/ldb_match.c +++ b/lib/ldb/common/ldb_match.c @@ -34,6 +34,7 @@ #include "ldb_private.h" #include "dlinklist.h" +#include "ldb_handlers.h" /* check if the scope matches in a search result @@ -259,20 +260,42 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, return LDB_SUCCESS; } - if (a->syntax->canonicalise_fn(ldb, ldb, &value, &val) != 0) { - return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + /* No need to just copy this value for a binary match */ + if (a->syntax->canonicalise_fn != ldb_handler_copy) { + if (a->syntax->canonicalise_fn(ldb, ldb, &value, &val) != 0) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + + /* + * Only set save_p if we allocate (call + * a->syntax->canonicalise_fn()), as we + * talloc_free(save_p) below to clean up + */ + save_p = val.data; + } else { + val = value; } - save_p = val.data; cnk.data = NULL; if ( ! tree->u.substring.start_with_wildcard ) { + uint8_t *cnk_to_free = NULL; chunk = tree->u.substring.chunks[c]; - if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) goto mismatch; + /* No need to just copy this value for a binary match */ + if (a->syntax->canonicalise_fn != ldb_handler_copy) { + if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) { + goto mismatch; + } + + cnk_to_free = cnk.data; + } else { + cnk = *chunk; + } /* This deals with wildcard prefix searches on binary attributes (eg objectGUID) */ if (cnk.length > val.length) { + TALLOC_FREE(cnk_to_free); goto mismatch; } /* @@ -280,32 +303,47 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, * we can cope with this. */ if (cnk.length == 0) { + TALLOC_FREE(cnk_to_free); + goto mismatch; + } + + if (memcmp((char *)val.data, (char *)cnk.data, cnk.length) != 0) { + TALLOC_FREE(cnk_to_free); goto mismatch; } - if (memcmp((char *)val.data, (char *)cnk.data, cnk.length) != 0) goto mismatch; val.length -= cnk.length; val.data += cnk.length; c++; - talloc_free(cnk.data); + TALLOC_FREE(cnk_to_free); cnk.data = NULL; } while (tree->u.substring.chunks[c]) { uint8_t *p; + uint8_t *cnk_to_free = NULL; chunk = tree->u.substring.chunks[c]; - if(a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) { - goto mismatch; + /* No need to just copy this value for a binary match */ + if (a->syntax->canonicalise_fn != ldb_handler_copy) { + if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) { + goto mismatch; + } + + cnk_to_free = cnk.data; + } else { + cnk = *chunk; } /* * Empty strings are returned as length 0. Ensure * we can cope with this. */ if (cnk.length == 0) { + TALLOC_FREE(cnk_to_free); goto mismatch; } if (cnk.length > val.length) { + TALLOC_FREE(cnk_to_free); goto mismatch; } @@ -320,6 +358,8 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, cmp = memcmp(p, cnk.data, cnk.length); + TALLOC_FREE(cnk_to_free); + if (cmp != 0) { goto mismatch; } @@ -331,15 +371,16 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, p = memmem((const void *)val.data, val.length, (const void *)cnk.data, cnk.length); if (p == NULL) { + TALLOC_FREE(cnk_to_free); goto mismatch; } /* move val to the end of the match */ p += cnk.length; val.length -= (p - val.data); val.data = p; + TALLOC_FREE(cnk_to_free); } c++; - TALLOC_FREE(cnk.data); } talloc_free(save_p); @@ -349,7 +390,6 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, mismatch: *matched = false; talloc_free(save_p); - talloc_free(cnk.data); return LDB_SUCCESS; } -- 2.25.1 From a91fc6e9f1def8bed920efba9c1bd1f4713eb3ca Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 13 Mar 2023 17:20:00 +1300 Subject: [PATCH 02/41] CVE-2023-0614 selftest: Use setUpClass() to reduce "make test TESTS=large_ldap" time This reduces the elapsed time to 6m from 20m on my laptop. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15332 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton Autobuild-User(master): Andrew Bartlett Autobuild-Date(master): Tue Mar 14 07:16:04 UTC 2023 on atb-devel-224 (cherry picked from commit b4a6c054ec6acefacd22cb7230a783d20cb07c05) [abartlet@samba.org Included in the security release as this makes working on the large_ldap test practical by reducing the elapsed time taken] --- source4/dsdb/tests/python/large_ldap.py | 69 +++++++++++++------------ 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/source4/dsdb/tests/python/large_ldap.py b/source4/dsdb/tests/python/large_ldap.py index 0805119a700..13729052e65 100644 --- a/source4/dsdb/tests/python/large_ldap.py +++ b/source4/dsdb/tests/python/large_ldap.py @@ -66,30 +66,32 @@ creds = credopts.get_credentials(lp) class ManyLDAPTest(samba.tests.TestCase): - def setUp(self): - super(ManyLDAPTest, self).setUp() - self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) - self.base_dn = self.ldb.domain_dn() - self.OU_NAME_MANY="many_ou" + format(random.randint(0, 99999), "05") - self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME_MANY + "," + str(self.base_dn)) - - samba.tests.delete_force(self.ldb, self.ou_dn, + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) + cls.base_dn = self.ldb.domain_dn() + cls.OU_NAME_MANY="many_ou" + format(random.randint(0, 99999), "05") + cls.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME_MANY + "," + str(self.base_dn)) + + samba.tests.delete_force(cls.ldb, cls.ou_dn, controls=['tree_delete:1']) - self.ldb.add({ - "dn": self.ou_dn, + cls.ldb.add({ + "dn": cls.ou_dn, "objectclass": "organizationalUnit", - "ou": self.OU_NAME_MANY}) + "ou": cls.OU_NAME_MANY}) for x in range(2000): - ou_name = self.OU_NAME_MANY + str(x) - self.ldb.add({ - "dn": "ou=" + ou_name + "," + str(self.ou_dn), + ou_name = cls.OU_NAME_MANY + str(x) + cls.ldb.add({ + "dn": "ou=" + ou_name + "," + str(cls.ou_dn), "objectclass": "organizationalUnit", "ou": ou_name}) - def tearDown(self): - samba.tests.delete_force(self.ldb, self.ou_dn, + @classmethod + def tearDownClass(cls): + samba.tests.delete_force(cls.ldb, self.ou_dn, controls=['tree_delete:1']) def test_unindexed_iterator_search(self): @@ -117,34 +119,35 @@ class ManyLDAPTest(samba.tests.TestCase): class LargeLDAPTest(samba.tests.TestCase): - def setUp(self): - super(LargeLDAPTest, self).setUp() - self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) - self.base_dn = self.ldb.domain_dn() - self.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-" - self.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05") - self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME + "," + str(self.base_dn)) + @classmethod + def setUpClass(cls): + cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) + cls.base_dn = cls.ldb.domain_dn() + cls.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-" + cls.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05") + cls.ou_dn = ldb.Dn(cls.ldb, "ou=" + cls.OU_NAME + "," + str(cls.base_dn)) - samba.tests.delete_force(self.ldb, self.ou_dn, + samba.tests.delete_force(cls.ldb, cls.ou_dn, controls=['tree_delete:1']) - self.ldb.add({ - "dn": self.ou_dn, + cls.ldb.add({ + "dn": cls.ou_dn, "objectclass": "organizationalUnit", - "ou": self.OU_NAME}) + "ou": cls.OU_NAME}) for x in range(200): - user_name = self.USER_NAME + format(x, "03") - self.ldb.add({ - "dn": "cn=" + user_name + "," + str(self.ou_dn), + user_name = cls.USER_NAME + format(x, "03") + cls.ldb.add({ + "dn": "cn=" + user_name + "," + str(cls.ou_dn), "objectclass": "user", "sAMAccountName": user_name, "jpegPhoto": b'a' * (2 * 1024 * 1024)}) - def tearDown(self): + @classmethod + def tearDownClass(cls): # Remake the connection for tear-down (old Samba drops the socket) - self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) - samba.tests.delete_force(self.ldb, self.ou_dn, + cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) + samba.tests.delete_force(cls.ldb, cls.ou_dn, controls=['tree_delete:1']) def test_unindexed_iterator_search(self): -- 2.25.1 From a8c573012f54e74e86deec6ac2bd84e2450dad03 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 3 Mar 2023 10:31:40 +1300 Subject: [PATCH 03/41] CVE-2023-0614 dsdb: Alter timeout test in large_ldap.py to be slower by matching on large objects This changes the slow aspect to be the object matching not the filter parsing. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/dsdb/tests/python/large_ldap.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/tests/python/large_ldap.py b/source4/dsdb/tests/python/large_ldap.py index 13729052e65..0da79da6f73 100644 --- a/source4/dsdb/tests/python/large_ldap.py +++ b/source4/dsdb/tests/python/large_ldap.py @@ -32,7 +32,7 @@ from samba.tests.subunitrun import SubunitOptions, TestProgram import samba.getopt as options from samba.auth import system_session -from samba import ldb +from samba import ldb, sd_utils from samba.samdb import SamDB from samba.ndr import ndr_unpack from samba import gensec @@ -123,10 +123,13 @@ class LargeLDAPTest(samba.tests.TestCase): def setUpClass(cls): cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) cls.base_dn = cls.ldb.domain_dn() + + cls.sd_utils = sd_utils.SDUtils(cls.ldb) cls.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-" cls.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05") cls.ou_dn = ldb.Dn(cls.ldb, "ou=" + cls.OU_NAME + "," + str(cls.base_dn)) + samba.tests.delete_force(cls.ldb, cls.ou_dn, controls=['tree_delete:1']) @@ -249,6 +252,7 @@ class LargeLDAPTest(samba.tests.TestCase): self.assertGreater(count, count_jpeg) def test_timeout(self): + policy_dn = ldb.Dn(self.ldb, 'CN=Default Query Policy,CN=Query-Policies,' 'CN=Directory Service,CN=Windows NT,CN=Services,' @@ -286,9 +290,19 @@ class LargeLDAPTest(samba.tests.TestCase): session_info=system_session(lp), lp=lp) + for x in range(200): + user_name = self.USER_NAME + format(x, "03") + ace = "(OD;;RP;{6bc69afa-7bd9-4184-88f5-28762137eb6a};;S-1-%d)" % x + dn = ldb.Dn(self.ldb, "cn=" + user_name + "," + str(self.ou_dn)) + + # add an ACE that denies access to the above random attr + # for a not-existing user. This makes each SD distinct + # and so will slow SD parsing. + self.sd_utils.dacl_add_ace(dn, ace) + # Create a large search expression that will take a long time to # evaluate. - expression = '(anr=l)' * 10000 + expression = f'(jpegPhoto=*X*)' * 1000 expression = f'(|{expression})' # Perform the LDAP search. -- 2.25.1 From 50a678be1a655dfc08d7bc0f74487b14e79cb0d3 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 27 Jan 2023 07:57:27 +1300 Subject: [PATCH 04/41] CVE-2023-0614 libcli/security: Make some parameters const BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org Updated to add const to sec_access_check_ds() instead of the sec_access_check_ds_implicit_owner() wrapper found in 4.18 and later] --- libcli/security/access_check.c | 10 +++++----- libcli/security/access_check.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libcli/security/access_check.c b/libcli/security/access_check.c index f5051b0fa93..7dd3798703c 100644 --- a/libcli/security/access_check.c +++ b/libcli/security/access_check.c @@ -394,7 +394,7 @@ NTSTATUS se_file_access_check(const struct security_descriptor *sd, return NT_STATUS_OK; } -static const struct GUID *get_ace_object_type(struct security_ace *ace) +static const struct GUID *get_ace_object_type(const struct security_ace *ace) { if (ace->object.object.flags & SEC_ACE_OBJECT_TYPE_PRESENT) { return &ace->object.object.type.type; @@ -412,7 +412,7 @@ static const struct GUID *get_ace_object_type(struct security_ace *ace) * rights to the object/attribute * @returns NT_STATUS_OK, unless access was denied */ -static NTSTATUS check_object_specific_access(struct security_ace *ace, +static NTSTATUS check_object_specific_access(const struct security_ace *ace, struct object_tree *tree, bool *grant_access) { @@ -505,7 +505,7 @@ NTSTATUS sec_access_check_ds(const struct security_descriptor *sd, uint32_t access_desired, uint32_t *access_granted, struct object_tree *tree, - struct dom_sid *replace_sid) + const struct dom_sid *replace_sid) { uint32_t i; uint32_t bits_remaining; @@ -556,8 +556,8 @@ NTSTATUS sec_access_check_ds(const struct security_descriptor *sd, /* check each ace in turn. */ for (i=0; bits_remaining && i < sd->dacl->num_aces; i++) { - struct dom_sid *trustee; - struct security_ace *ace = &sd->dacl->aces[i]; + const struct dom_sid *trustee; + const struct security_ace *ace = &sd->dacl->aces[i]; NTSTATUS status; bool grant_access = false; diff --git a/libcli/security/access_check.h b/libcli/security/access_check.h index 96e33c6624f..37ca078a24e 100644 --- a/libcli/security/access_check.h +++ b/libcli/security/access_check.h @@ -74,7 +74,7 @@ NTSTATUS sec_access_check_ds(const struct security_descriptor *sd, uint32_t access_desired, uint32_t *access_granted, struct object_tree *tree, - struct dom_sid *replace_sid); + const struct dom_sid *replace_sid); bool insert_in_object_tree(TALLOC_CTX *mem_ctx, const struct GUID *guid, -- 2.25.1 From 9c8bbbf3b57319ccf14df76d43096a453e1ebedb Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 7 Feb 2023 09:29:51 +1300 Subject: [PATCH 05/41] CVE-2023-0614 s4:dsdb: Use talloc_get_type_abort() more consistently It is better to explicitly abort than to dereference a NULL pointer or try to read data cast to the wrong type. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/acl_read.c | 4 ++-- source4/dsdb/samdb/ldb_modules/acl_util.c | 2 +- source4/dsdb/samdb/ldb_modules/linked_attributes.c | 2 +- source4/dsdb/samdb/ldb_modules/password_hash.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index b221dcde445..16a1927183c 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -268,7 +268,7 @@ static int aclread_get_sd_from_ldb_message(struct aclread_context *ac, struct ldb_message_element *sd_element; struct ldb_context *ldb = ldb_module_get_ctx(ac->module); struct aclread_private *private_data - = talloc_get_type(ldb_module_get_private(ac->module), + = talloc_get_type_abort(ldb_module_get_private(ac->module), struct aclread_private); enum ndr_err_code ndr_err; @@ -568,7 +568,7 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) const struct dsdb_class *objectclass; bool suppress_result = false; - ac = talloc_get_type(req->context, struct aclread_context); + ac = talloc_get_type_abort(req->context, struct aclread_context); ldb = ldb_module_get_ctx(ac->module); if (!ares) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR ); diff --git a/source4/dsdb/samdb/ldb_modules/acl_util.c b/source4/dsdb/samdb/ldb_modules/acl_util.c index 12f00fbff16..367c11d1ba9 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_util.c +++ b/source4/dsdb/samdb/ldb_modules/acl_util.c @@ -298,7 +298,7 @@ uint32_t dsdb_request_sd_flags(struct ldb_request *req, bool *explicit) sd_control = ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID); if (sd_control != NULL && sd_control->data != NULL) { - struct ldb_sd_flags_control *sdctr = (struct ldb_sd_flags_control *)sd_control->data; + struct ldb_sd_flags_control *sdctr = talloc_get_type_abort(sd_control->data, struct ldb_sd_flags_control); sd_flags = sdctr->secinfo_flags; diff --git a/source4/dsdb/samdb/ldb_modules/linked_attributes.c b/source4/dsdb/samdb/ldb_modules/linked_attributes.c index 5ef075f2037..317df9d3e0e 100644 --- a/source4/dsdb/samdb/ldb_modules/linked_attributes.c +++ b/source4/dsdb/samdb/ldb_modules/linked_attributes.c @@ -104,7 +104,7 @@ static int handle_verify_name_control(TALLOC_CTX *ctx, struct ldb_context *ldb, * If we are a GC let's remove the control, * if there is a specified GC check that is us. */ - struct ldb_verify_name_control *lvnc = (struct ldb_verify_name_control *)control->data; + struct ldb_verify_name_control *lvnc = talloc_get_type_abort(control->data, struct ldb_verify_name_control); if (samdb_is_gc(ldb)) { /* Because we can't easily talloc a struct ldb_dn*/ struct ldb_dn **dn = talloc_array(ctx, struct ldb_dn *, 1); diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index b308226a9f9..6a713b86736 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -4066,7 +4066,7 @@ static void ph_apply_controls(struct ph_context *ac) ctrl = ldb_request_get_control(ac->req, DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID); if (ctrl != NULL) { - ac->change = (struct dsdb_control_password_change *) ctrl->data; + ac->change = talloc_get_type_abort(ctrl->data, struct dsdb_control_password_change); /* Mark the "change" control as uncritical (done) */ ctrl->critical = false; -- 2.25.1 From 7f98e3abdc48195e1ab1b56222d7beae4aa2a215 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 27 Jan 2023 08:00:32 +1300 Subject: [PATCH 06/41] CVE-2023-0614 s4-acl: Make some parameters const BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org Adapted to code without newer acl_check_access_on_attribute_implicit_owner name] --- source4/dsdb/samdb/ldb_modules/acl_util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl_util.c b/source4/dsdb/samdb/ldb_modules/acl_util.c index 367c11d1ba9..56aa4bd7531 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_util.c +++ b/source4/dsdb/samdb/ldb_modules/acl_util.c @@ -97,8 +97,8 @@ int dsdb_module_check_access_on_dn(struct ldb_module *module, int acl_check_access_on_attribute(struct ldb_module *module, TALLOC_CTX *mem_ctx, - struct security_descriptor *sd, - struct dom_sid *rp_sid, + const struct security_descriptor *sd, + const struct dom_sid *rp_sid, uint32_t access_mask, const struct dsdb_attribute *attr, const struct dsdb_class *objectclass) -- 2.25.1 From cbf8f1c2eb80123736ac4f356171639a0754fda4 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 27 Jan 2023 08:28:36 +1300 Subject: [PATCH 07/41] CVE-2023-0614 ldb: Add functions for handling inaccessible message elements BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/common/ldb_msg.c | 26 ++++++++++++++++++++++++++ lib/ldb/include/ldb_module.h | 4 ++++ 2 files changed, 30 insertions(+) diff --git a/lib/ldb/common/ldb_msg.c b/lib/ldb/common/ldb_msg.c index 9cd7998e21c..cbc7e32b2ba 100644 --- a/lib/ldb/common/ldb_msg.c +++ b/lib/ldb/common/ldb_msg.c @@ -795,6 +795,32 @@ int ldb_msg_element_compare_name(struct ldb_message_element *el1, return ldb_attr_cmp(el1->name, el2->name); } +void ldb_msg_element_mark_inaccessible(struct ldb_message_element *el) +{ + el->flags |= LDB_FLAG_INTERNAL_INACCESSIBLE_ATTRIBUTE; +} + +bool ldb_msg_element_is_inaccessible(const struct ldb_message_element *el) +{ + return (el->flags & LDB_FLAG_INTERNAL_INACCESSIBLE_ATTRIBUTE) != 0; +} + +void ldb_msg_remove_inaccessible(struct ldb_message *msg) +{ + unsigned i; + unsigned num_del = 0; + + for (i = 0; i < msg->num_elements; ++i) { + if (ldb_msg_element_is_inaccessible(&msg->elements[i])) { + ++num_del; + } else if (num_del) { + msg->elements[i - num_del] = msg->elements[i]; + } + } + + msg->num_elements -= num_del; +} + /* convenience functions to return common types from a message these return the first value if the attribute is multi-valued diff --git a/lib/ldb/include/ldb_module.h b/lib/ldb/include/ldb_module.h index 4c7c85a17f0..8481fd3991a 100644 --- a/lib/ldb/include/ldb_module.h +++ b/lib/ldb/include/ldb_module.h @@ -513,6 +513,10 @@ struct ldb_extended_match_rule int ldb_register_extended_match_rule(struct ldb_context *ldb, const struct ldb_extended_match_rule *rule); +void ldb_msg_element_mark_inaccessible(struct ldb_message_element *el); +bool ldb_msg_element_is_inaccessible(const struct ldb_message_element *el); +void ldb_msg_remove_inaccessible(struct ldb_message *msg); + /* * these pack/unpack functions are exposed in the library for use by * ldb tools like ldbdump and for use in tests, -- 2.25.1 From 188e988721065cf16565820e7483067947bd40a6 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 27 Jan 2023 08:29:33 +1300 Subject: [PATCH 08/41] CVE-2023-0614 s4-acl: Use ldb functions for handling inaccessible message elements BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/acl_read.c | 62 ++++------------------- 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index 16a1927183c..8814a816797 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -70,14 +70,6 @@ struct aclread_private { struct ldb_val sd_cached_blob; }; -static void aclread_mark_inaccesslible(struct ldb_message_element *el) { - el->flags |= LDB_FLAG_INTERNAL_INACCESSIBLE_ATTRIBUTE; -} - -static bool aclread_is_inaccessible(struct ldb_message_element *el) { - return el->flags & LDB_FLAG_INTERNAL_INACCESSIBLE_ATTRIBUTE; -} - /* * the object has a parent, so we have to check for visibility * @@ -557,11 +549,9 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) { struct ldb_context *ldb; struct aclread_context *ac; - struct ldb_message *ret_msg; struct ldb_message *msg; int ret; - size_t num_of_attrs = 0; - unsigned int i, k = 0; + unsigned int i; struct security_descriptor *sd = NULL; struct dom_sid *sid = NULL; TALLOC_CTX *tmp_ctx; @@ -651,26 +641,26 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) msg->elements[i].name) == 0; /* these attributes were added to perform access checks and must be removed */ if (is_objectsid && ac->added_objectSid) { - aclread_mark_inaccesslible(&msg->elements[i]); + ldb_msg_element_mark_inaccessible(&msg->elements[i]); continue; } if (is_instancetype && ac->added_instanceType) { - aclread_mark_inaccesslible(&msg->elements[i]); + ldb_msg_element_mark_inaccessible(&msg->elements[i]); continue; } if (is_objectclass && ac->added_objectClass) { - aclread_mark_inaccesslible(&msg->elements[i]); + ldb_msg_element_mark_inaccessible(&msg->elements[i]); continue; } if (is_sd && ac->added_nTSecurityDescriptor) { - aclread_mark_inaccesslible(&msg->elements[i]); + ldb_msg_element_mark_inaccessible(&msg->elements[i]); continue; } access_mask = get_attr_access_mask(attr, ac->sd_flags); if (access_mask == 0) { - aclread_mark_inaccesslible(&msg->elements[i]); + ldb_msg_element_mark_inaccessible(&msg->elements[i]); continue; } @@ -714,7 +704,7 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) return LDB_SUCCESS; } } else { - aclread_mark_inaccesslible(&msg->elements[i]); + ldb_msg_element_mark_inaccessible(&msg->elements[i]); } } else if (ret != LDB_SUCCESS) { ldb_debug_set(ldb, LDB_DEBUG_FATAL, @@ -757,44 +747,12 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) } } - for (i=0; i < msg->num_elements; i++) { - if (!aclread_is_inaccessible(&msg->elements[i])) { - num_of_attrs++; - } - } - /*create a new message to return*/ - ret_msg = ldb_msg_new(ac->req); - ret_msg->dn = msg->dn; - talloc_steal(ret_msg, msg->dn); - ret_msg->num_elements = num_of_attrs; - if (num_of_attrs > 0) { - ret_msg->elements = talloc_array(ret_msg, - struct ldb_message_element, - num_of_attrs); - if (ret_msg->elements == NULL) { - return ldb_oom(ldb); - } - for (i=0; i < msg->num_elements; i++) { - bool to_remove = aclread_is_inaccessible(&msg->elements[i]); - if (!to_remove) { - ret_msg->elements[k] = msg->elements[i]; - talloc_steal(ret_msg->elements, msg->elements[i].name); - talloc_steal(ret_msg->elements, msg->elements[i].values); - k++; - } - } - /* - * This should not be needed, but some modules - * may allocate values on the wrong context... - */ - talloc_steal(ret_msg->elements, msg); - } else { - ret_msg->elements = NULL; - } + ldb_msg_remove_inaccessible(msg); + talloc_free(tmp_ctx); ac->num_entries++; - return ldb_module_send_entry(ac->req, ret_msg, ares->controls); + return ldb_module_send_entry(ac->req, msg, ares->controls); case LDB_REPLY_REFERRAL: return ldb_module_send_referral(ac->req, ares->referral); case LDB_REPLY_DONE: -- 2.25.1 From 132028692f3bc491795e96dc6b1b440ed808ee2e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 15 Feb 2023 12:34:51 +1300 Subject: [PATCH 09/41] CVE-2023-0614 ldb:tests: Ensure ldb_val data is zero-terminated If the value of an ldb message element is not zero-terminated, calling ldb_msg_find_attr_as_string() will cause the function to read off the end of the buffer in an attempt to verify that the value is zero-terminated. This can cause unexpected behaviour and make the test randomly fail. To avoid this, we must have a terminating null byte that is *not* counted as part of the length, and so we must calculate the length with strlen() rather than sizeof. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/tests/ldb_filter_attrs_test.c | 171 +++++++++++++------------- 1 file changed, 86 insertions(+), 85 deletions(-) diff --git a/lib/ldb/tests/ldb_filter_attrs_test.c b/lib/ldb/tests/ldb_filter_attrs_test.c index 7d555e0da2e..442d9c77ed2 100644 --- a/lib/ldb/tests/ldb_filter_attrs_test.c +++ b/lib/ldb/tests/ldb_filter_attrs_test.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -96,10 +97,10 @@ static void test_filter_attrs_one_attr_matched(void **state) const char *attrs[] = {"foo", NULL}; - uint8_t value[] = "The value.......end"; + char value[] = "The value.......end"; struct ldb_val value_1 = { - .data = value, - .length = (sizeof(value)) + .data = (uint8_t *)value, + .length = strlen(value) }; struct ldb_message_element element_1 = { .name = "foo", @@ -130,9 +131,9 @@ static void test_filter_attrs_one_attr_matched(void **state) assert_string_equal(filtered_msg->elements[0].name, "foo"); assert_int_equal(filtered_msg->elements[0].num_values, 1); assert_int_equal(filtered_msg->elements[0].values[0].length, - sizeof(value)); + strlen(value)); assert_memory_equal(filtered_msg->elements[0].values[0].data, - value, sizeof(value)); + value, strlen(value)); } /* @@ -148,10 +149,10 @@ static void test_filter_attrs_one_attr_matched_of_many(void **state) const char *attrs[] = {"foo", "bar", "baz", NULL}; - uint8_t value[] = "The value.......end"; + char value[] = "The value.......end"; struct ldb_val value_1 = { - .data = value, - .length = (sizeof(value)) + .data = (uint8_t *)value, + .length = strlen(value) }; struct ldb_message_element element_1 = { .name = "foo", @@ -182,9 +183,9 @@ static void test_filter_attrs_one_attr_matched_of_many(void **state) assert_string_equal(filtered_msg->elements[0].name, "foo"); assert_int_equal(filtered_msg->elements[0].num_values, 1); assert_int_equal(filtered_msg->elements[0].values[0].length, - sizeof(value)); + strlen(value)); assert_memory_equal(filtered_msg->elements[0].values[0].data, - value, sizeof(value)); + value, strlen(value)); } /* @@ -201,15 +202,15 @@ static void test_filter_attrs_two_attr_matched_attrs(void **state) /* deliberatly the other order */ const char *attrs[] = {"bar", "foo", NULL}; - uint8_t value1[] = "The value.......end"; - uint8_t value2[] = "The value..MUST.end"; + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; struct ldb_val value_1 = { - .data = value1, - .length = (sizeof(value1)) + .data = (uint8_t *)value1, + .length = strlen(value1) }; struct ldb_val value_2 = { - .data = value2, - .length = (sizeof(value2)) + .data = (uint8_t *)value2, + .length = strlen(value2) }; /* foo and bar are the other order to in attrs */ @@ -251,15 +252,15 @@ static void test_filter_attrs_two_attr_matched_attrs(void **state) assert_string_equal(filtered_msg->elements[0].name, "foo"); assert_int_equal(filtered_msg->elements[0].num_values, 1); assert_int_equal(filtered_msg->elements[0].values[0].length, - sizeof(value1)); + strlen(value1)); assert_memory_equal(filtered_msg->elements[0].values[0].data, - value1, sizeof(value1)); + value1, strlen(value1)); assert_string_equal(filtered_msg->elements[1].name, "bar"); assert_int_equal(filtered_msg->elements[1].num_values, 1); assert_int_equal(filtered_msg->elements[1].values[0].length, - sizeof(value2)); + strlen(value2)); assert_memory_equal(filtered_msg->elements[1].values[0].data, - value2, sizeof(value2)); + value2, strlen(value2)); } /* @@ -276,15 +277,15 @@ static void test_filter_attrs_two_attr_matched_one_attr(void **state) /* deliberatly the other order */ const char *attrs[] = {"bar", NULL}; - uint8_t value1[] = "The value.......end"; - uint8_t value2[] = "The value..MUST.end"; + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; struct ldb_val value_1 = { - .data = value1, - .length = (sizeof(value1)) + .data = (uint8_t *)value1, + .length = strlen(value1) }; struct ldb_val value_2 = { - .data = value2, - .length = (sizeof(value2)) + .data = (uint8_t *)value2, + .length = strlen(value2) }; /* foo and bar are the other order to in attrs */ @@ -326,9 +327,9 @@ static void test_filter_attrs_two_attr_matched_one_attr(void **state) assert_string_equal(filtered_msg->elements[0].name, "bar"); assert_int_equal(filtered_msg->elements[0].num_values, 1); assert_int_equal(filtered_msg->elements[0].values[0].length, - sizeof(value2)); + strlen(value2)); assert_memory_equal(filtered_msg->elements[0].values[0].data, - value2, sizeof(value2)); + value2, strlen(value2)); } /* @@ -345,15 +346,15 @@ static void test_filter_attrs_two_dup_attr_matched_one_attr(void **state) /* deliberatly the other order */ const char *attrs[] = {"bar", NULL}; - uint8_t value1[] = "The value.......end"; - uint8_t value2[] = "The value..MUST.end"; + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; struct ldb_val value_1 = { - .data = value1, - .length = (sizeof(value1)) + .data = (uint8_t *)value1, + .length = strlen(value1) }; struct ldb_val value_2 = { - .data = value2, - .length = (sizeof(value2)) + .data = (uint8_t *)value2, + .length = strlen(value2) }; /* foo and bar are the other order to in attrs */ @@ -400,15 +401,15 @@ static void test_filter_attrs_two_dup_attr_matched_dup(void **state) const char *attrs[] = {"bar", "bar", NULL}; - uint8_t value1[] = "The value.......end"; - uint8_t value2[] = "The value..MUST.end"; + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; struct ldb_val value_1 = { - .data = value1, - .length = (sizeof(value1)) + .data = (uint8_t *)value1, + .length = strlen(value1) }; struct ldb_val value_2 = { - .data = value2, - .length = (sizeof(value2)) + .data = (uint8_t *)value2, + .length = strlen(value2) }; /* foo and bar are the other order to in attrs */ @@ -445,15 +446,15 @@ static void test_filter_attrs_two_dup_attr_matched_dup(void **state) assert_string_equal(filtered_msg->elements[0].name, "bar"); assert_int_equal(filtered_msg->elements[0].num_values, 1); assert_int_equal(filtered_msg->elements[0].values[0].length, - sizeof(value1)); + strlen(value1)); assert_memory_equal(filtered_msg->elements[0].values[0].data, - value1, sizeof(value1)); + value1, strlen(value1)); assert_string_equal(filtered_msg->elements[1].name, "bar"); assert_int_equal(filtered_msg->elements[1].num_values, 1); assert_int_equal(filtered_msg->elements[1].values[0].length, - sizeof(value2)); + strlen(value2)); assert_memory_equal(filtered_msg->elements[1].values[0].data, - value2, sizeof(value2)); + value2, strlen(value2)); } /* @@ -469,15 +470,15 @@ static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) const char *attrs[] = {"bar", "foo", NULL}; - uint8_t value1[] = "The value.......end"; - uint8_t value2[] = "The value..MUST.end"; + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; struct ldb_val value_1 = { - .data = value1, - .length = (sizeof(value1)) + .data = (uint8_t *)value1, + .length = strlen(value1) }; struct ldb_val value_2 = { - .data = value2, - .length = (sizeof(value2)) + .data = (uint8_t *)value2, + .length = strlen(value2) }; /* foo and bar are the other order to in attrs */ @@ -514,15 +515,15 @@ static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) assert_string_equal(filtered_msg->elements[0].name, "bar"); assert_int_equal(filtered_msg->elements[0].num_values, 1); assert_int_equal(filtered_msg->elements[0].values[0].length, - sizeof(value1)); + strlen(value1)); assert_memory_equal(filtered_msg->elements[0].values[0].data, - value1, sizeof(value1)); + value1, strlen(value1)); assert_string_equal(filtered_msg->elements[1].name, "bar"); assert_int_equal(filtered_msg->elements[1].num_values, 1); assert_int_equal(filtered_msg->elements[1].values[0].length, - sizeof(value2)); + strlen(value2)); assert_memory_equal(filtered_msg->elements[1].values[0].data, - value2, sizeof(value2)); + value2, strlen(value2)); } /* @@ -538,15 +539,15 @@ static void test_filter_attrs_two_dup_attr_matched_star(void **state) const char *attrs[] = {"*", "foo", NULL}; - uint8_t value1[] = "The value.......end"; - uint8_t value2[] = "The value..MUST.end"; + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; struct ldb_val value_1 = { - .data = value1, - .length = (sizeof(value1)) + .data = (uint8_t *)value1, + .length = strlen(value1) }; struct ldb_val value_2 = { - .data = value2, - .length = (sizeof(value2)) + .data = (uint8_t *)value2, + .length = strlen(value2) }; /* foo and bar are the other order to in attrs */ @@ -586,15 +587,15 @@ static void test_filter_attrs_two_dup_attr_matched_star(void **state) assert_string_equal(filtered_msg->elements[0].name, "bar"); assert_int_equal(filtered_msg->elements[0].num_values, 1); assert_int_equal(filtered_msg->elements[0].values[0].length, - sizeof(value1)); + strlen(value1)); assert_memory_equal(filtered_msg->elements[0].values[0].data, - value1, sizeof(value1)); + value1, strlen(value1)); assert_string_equal(filtered_msg->elements[1].name, "bar"); assert_int_equal(filtered_msg->elements[1].num_values, 1); assert_int_equal(filtered_msg->elements[1].values[0].length, - sizeof(value2)); + strlen(value2)); assert_memory_equal(filtered_msg->elements[1].values[0].data, - value2, sizeof(value2)); + value2, strlen(value2)); /* * assert the ldb_filter_attrs does not modify filtered_msg.dn * in this case @@ -619,10 +620,10 @@ static void test_filter_attrs_one_attr_matched_star(void **state) const char *attrs[] = {"*", NULL}; - uint8_t value[] = "The value.......end"; + char value[] = "The value.......end"; struct ldb_val value_1 = { - .data = value, - .length = (sizeof(value)) + .data = (uint8_t *)value, + .length = strlen(value) }; struct ldb_message_element element_1 = { .name = "foo", @@ -676,15 +677,15 @@ static void test_filter_attrs_two_attr_matched_star(void **state) const char *attrs[] = {"*", NULL}; - uint8_t value1[] = "The value.......end"; - uint8_t value2[] = "The value..MUST.end"; + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; struct ldb_val value_1 = { - .data = value1, - .length = (sizeof(value1)) + .data = (uint8_t *)value1, + .length = strlen(value1) }; struct ldb_val value_2 = { - .data = value2, - .length = (sizeof(value2)) + .data = (uint8_t *)value2, + .length = strlen(value2) }; struct ldb_message_element elements[] = { { @@ -750,10 +751,10 @@ static void test_filter_attrs_one_attr_matched_star_no_dn(void **state) const char *attrs[] = {"*", NULL}; - uint8_t value[] = "The value.......end"; + char value[] = "The value.......end"; struct ldb_val value_1 = { - .data = value, - .length = (sizeof(value)) + .data = (uint8_t *)value, + .length = strlen(value) }; struct ldb_message_element element_1 = { .name = "foo", @@ -789,10 +790,10 @@ static void test_filter_attrs_one_attr_matched_star_dn(void **state) const char *attrs[] = {"*", "distinguishedName", NULL}; - uint8_t value[] = "The value.......end"; + char value[] = "The value.......end"; struct ldb_val value_1 = { - .data = value, - .length = (sizeof(value)) + .data = (uint8_t *)value, + .length = strlen(value) }; struct ldb_message_element element_1 = { .name = "foo", @@ -844,10 +845,10 @@ static void test_filter_attrs_one_attr_matched_dn(void **state) const char *attrs[] = {"distinguishedName", NULL}; - uint8_t value[] = "The value.......end"; + char value[] = "The value.......end"; struct ldb_val value_1 = { - .data = value, - .length = (sizeof(value)) + .data = (uint8_t *)value, + .length = strlen(value) }; struct ldb_message_element element_1 = { .name = "foo", @@ -894,10 +895,10 @@ static void test_filter_attrs_one_attr_empty_list(void **state) const char *attrs[] = {NULL}; - uint8_t value[] = "The value.......end"; + char value[] = "The value.......end"; struct ldb_val value_1 = { - .data = value, - .length = (sizeof(value)) + .data = (uint8_t *)value, + .length = strlen(value) }; struct ldb_message_element element_1 = { .name = "foo", -- 2.25.1 From b4f3aa03e2fdc89d50053e20723722b63c9ba7ec Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 15 Feb 2023 14:08:57 +1300 Subject: [PATCH 10/41] CVE-2023-0614 ldb:tests: Ensure all tests are accounted for Add ldb_filter_attrs_test to the list of tests so that it actually gets run. Remove a duplicate ldb_msg_test that was accidentally added in commit 5ca90e758ade97fb5e335029c7a1768094e70564. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/wscript | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ldb/wscript b/lib/ldb/wscript index 60bb7cf48b3..c862229822d 100644 --- a/lib/ldb/wscript +++ b/lib/ldb/wscript @@ -627,7 +627,6 @@ def test(ctx): 'ldb_msg_test', 'ldb_tdb_mod_op_test', 'ldb_tdb_guid_mod_op_test', - 'ldb_msg_test', 'ldb_tdb_kv_ops_test', 'ldb_tdb_test', 'ldb_match_test', @@ -637,7 +636,9 @@ def test(ctx): # on operations which the TDB backend does not currently # support # 'ldb_key_value_sub_txn_tdb_test' - 'ldb_parse_test'] + 'ldb_parse_test', + 'ldb_filter_attrs_test', + ] # if LIB_LDAP and LIB_LBER defined, then we can test ldb_ldap backend # behavior regression for bz#14413 -- 2.25.1 From 43746e79f67a57d63f824d1b3b0c19b4117af6cb Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:23:42 +1300 Subject: [PATCH 11/41] CVE-2023-0614 ldb: Add function to take ownership of an ldb message Many places in Samba depend upon various components of an ldb message being talloc allocated, and hence able to be used as talloc contexts. The elements and values of an unpacked ldb message point to unowned data inside the memory-mapped database, and this function ensures that such messages have talloc ownership of said elements and values. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/common/ldb_pack.c | 41 ++++++++++++++++++++++++++++++++++++ lib/ldb/include/ldb_module.h | 4 ++++ 2 files changed, 45 insertions(+) diff --git a/lib/ldb/common/ldb_pack.c b/lib/ldb/common/ldb_pack.c index e7dd364008a..028d96a619a 100644 --- a/lib/ldb/common/ldb_pack.c +++ b/lib/ldb/common/ldb_pack.c @@ -690,6 +690,7 @@ static int ldb_unpack_data_flags_v1(struct ldb_context *ldb, element->values = NULL; if ((flags & LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC) && element->num_values == 1) { element->values = &ldb_val_single_array[nelem]; + element->flags |= LDB_FLAG_INTERNAL_SHARED_VALUES; } else if (element->num_values != 0) { element->values = talloc_array(message->elements, struct ldb_val, @@ -932,6 +933,7 @@ static int ldb_unpack_data_flags_v2(struct ldb_context *ldb, if ((flags & LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC) && element->num_values == 1) { element->values = &ldb_val_single_array[nelem]; + element->flags |= LDB_FLAG_INTERNAL_SHARED_VALUES; } else if (element->num_values != 0) { element->values = talloc_array(message->elements, struct ldb_val, @@ -1259,3 +1261,42 @@ failed: TALLOC_FREE(filtered_msg->elements); return -1; } + +/* Have an unpacked ldb message take talloc ownership of its elements. */ +int ldb_msg_elements_take_ownership(struct ldb_message *msg) +{ + unsigned int i = 0; + + for (i = 0; i < msg->num_elements; i++) { + struct ldb_message_element *el = &msg->elements[i]; + const char *name; + unsigned int j; + + name = talloc_strdup(msg->elements, + el->name); + if (name == NULL) { + return -1; + } + el->name = name; + + if (el->flags & LDB_FLAG_INTERNAL_SHARED_VALUES) { + struct ldb_val *values = talloc_memdup(msg->elements, el->values, + sizeof(struct ldb_val) * el->num_values); + if (values == NULL) { + return -1; + } + el->values = values; + el->flags &= ~LDB_FLAG_INTERNAL_SHARED_VALUES; + } + + for (j = 0; j < el->num_values; j++) { + struct ldb_val val = ldb_val_dup(el->values, &el->values[j]); + if (val.data == NULL && el->values[j].length != 0) { + return -1; + } + el->values[j] = val; + } + } + + return LDB_SUCCESS; +} diff --git a/lib/ldb/include/ldb_module.h b/lib/ldb/include/ldb_module.h index 8481fd3991a..8c7f33496fb 100644 --- a/lib/ldb/include/ldb_module.h +++ b/lib/ldb/include/ldb_module.h @@ -542,6 +542,10 @@ int ldb_filter_attrs(struct ldb_context *ldb, const struct ldb_message *msg, const char *const *attrs, struct ldb_message *filtered_msg); + +/* Have an unpacked ldb message take talloc ownership of its elements. */ +int ldb_msg_elements_take_ownership(struct ldb_message *msg); + /* * Unpack a ldb message from a linear buffer in ldb_val * -- 2.25.1 From d97e92efafc7a9dd6c9143c74178be3aa549dd19 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:26:04 +1300 Subject: [PATCH 12/41] CVE-2023-0614 ldb: Add function to remove excess capacity from an ldb message BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org Adapted to conflict from lack of new ldb_ascii_toupper() in ldb_private.h] --- lib/ldb/common/ldb_msg.c | 16 ++++++++++++++++ lib/ldb/include/ldb_private.h | 3 +++ 2 files changed, 19 insertions(+) diff --git a/lib/ldb/common/ldb_msg.c b/lib/ldb/common/ldb_msg.c index cbc7e32b2ba..2ea2cce2e83 100644 --- a/lib/ldb/common/ldb_msg.c +++ b/lib/ldb/common/ldb_msg.c @@ -1497,6 +1497,22 @@ void ldb_msg_remove_attr(struct ldb_message *msg, const char *attr) } } +/* Reallocate elements to drop any excess capacity. */ +void ldb_msg_shrink_to_fit(struct ldb_message *msg) +{ + if (msg->num_elements > 0) { + struct ldb_message_element *elements = talloc_realloc(msg, + msg->elements, + struct ldb_message_element, + msg->num_elements); + if (elements != NULL) { + msg->elements = elements; + } + } else { + TALLOC_FREE(msg->elements); + } +} + /* return a LDAP formatted GeneralizedTime string */ diff --git a/lib/ldb/include/ldb_private.h b/lib/ldb/include/ldb_private.h index 4deb24691ca..338e71def6d 100644 --- a/lib/ldb/include/ldb_private.h +++ b/lib/ldb/include/ldb_private.h @@ -317,4 +317,7 @@ int ldb_match_message(struct ldb_context *ldb, const struct ldb_parse_tree *tree, enum ldb_scope scope, bool *matched); +/* Reallocate elements to drop any excess capacity. */ +void ldb_msg_shrink_to_fit(struct ldb_message *msg); + #endif -- 2.25.1 From ddf1ed69d8fd56b929e5d8d41fdbe513849c30f5 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:27:38 +1300 Subject: [PATCH 13/41] CVE-2023-0614 ldb: Add function to add distinguishedName to message BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org Adapted to conflict from lack of new ldb_ascii_toupper() in ldb_private.h] --- lib/ldb/common/ldb_pack.c | 6 +++--- lib/ldb/include/ldb_private.h | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/ldb/common/ldb_pack.c b/lib/ldb/common/ldb_pack.c index 028d96a619a..b0b0d64a5ba 100644 --- a/lib/ldb/common/ldb_pack.c +++ b/lib/ldb/common/ldb_pack.c @@ -1098,7 +1098,7 @@ int ldb_unpack_data(struct ldb_context *ldb, /* add the special distinguishedName element */ -static int msg_add_distinguished_name(struct ldb_message *msg) +int ldb_msg_add_distinguished_name(struct ldb_message *msg) { const char *dn_attr = "distinguishedName"; char *dn = NULL; @@ -1158,7 +1158,7 @@ int ldb_filter_attrs(struct ldb_context *ldb, /* Shortcuts for the simple cases */ } else if (add_dn && i == 1) { - if (msg_add_distinguished_name(filtered_msg) != 0) { + if (ldb_msg_add_distinguished_name(filtered_msg) != 0) { goto failed; } return 0; @@ -1238,7 +1238,7 @@ int ldb_filter_attrs(struct ldb_context *ldb, filtered_msg->num_elements = num_elements; if (add_dn) { - if (msg_add_distinguished_name(filtered_msg) != 0) { + if (ldb_msg_add_distinguished_name(filtered_msg) != 0) { goto failed; } } diff --git a/lib/ldb/include/ldb_private.h b/lib/ldb/include/ldb_private.h index 338e71def6d..ca43817d07a 100644 --- a/lib/ldb/include/ldb_private.h +++ b/lib/ldb/include/ldb_private.h @@ -320,4 +320,9 @@ int ldb_match_message(struct ldb_context *ldb, /* Reallocate elements to drop any excess capacity. */ void ldb_msg_shrink_to_fit(struct ldb_message *msg); +/* + add the special distinguishedName element +*/ +int ldb_msg_add_distinguished_name(struct ldb_message *msg); + #endif -- 2.25.1 From ec3737404e6aa9ee79fd27fd2eeba0d840fc624c Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:29:03 +1300 Subject: [PATCH 14/41] CVE-2023-0614 ldb: Add function to filter message in place At present this function is an exact duplicate of ldb_filter_attrs(), but in the next commit we shall modify it to work in place, without the need for the allocation of a second message. The test is a near duplicate of the existing test for ldb_filter_attrs(). BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/common/ldb_pack.c | 143 +++ lib/ldb/include/ldb_module.h | 10 + .../tests/ldb_filter_attrs_in_place_test.c | 989 ++++++++++++++++++ lib/ldb/wscript | 6 + 4 files changed, 1148 insertions(+) create mode 100644 lib/ldb/tests/ldb_filter_attrs_in_place_test.c diff --git a/lib/ldb/common/ldb_pack.c b/lib/ldb/common/ldb_pack.c index b0b0d64a5ba..f19ac73fa5e 100644 --- a/lib/ldb/common/ldb_pack.c +++ b/lib/ldb/common/ldb_pack.c @@ -1262,6 +1262,149 @@ failed: return -1; } +/* + * filter the specified list of attributes from msg, + * adding requested attributes, and perhaps all for *, + * but not the DN to filtered_msg. + */ +int ldb_filter_attrs_in_place(struct ldb_context *ldb, + const struct ldb_message *msg, + const char *const *attrs, + struct ldb_message *filtered_msg) +{ + unsigned int i; + bool keep_all = false; + bool add_dn = false; + uint32_t num_elements; + uint32_t elements_size; + + if (attrs) { + /* check for special attrs */ + for (i = 0; attrs[i]; i++) { + int cmp = strcmp(attrs[i], "*"); + if (cmp == 0) { + keep_all = true; + break; + } + cmp = ldb_attr_cmp(attrs[i], "distinguishedName"); + if (cmp == 0) { + add_dn = true; + } + } + } else { + keep_all = true; + } + + if (keep_all) { + add_dn = true; + elements_size = msg->num_elements + 1; + + /* Shortcuts for the simple cases */ + } else if (add_dn && i == 1) { + if (ldb_msg_add_distinguished_name(filtered_msg) != 0) { + goto failed; + } + return 0; + } else if (i == 0) { + return 0; + + /* + * Otherwise we are copying at most as many elements as we + * have attributes + */ + } else { + elements_size = i; + } + + filtered_msg->elements = talloc_array(filtered_msg, + struct ldb_message_element, + elements_size); + if (filtered_msg->elements == NULL) goto failed; + + num_elements = 0; + + for (i = 0; i < msg->num_elements; i++) { + struct ldb_message_element *el = &msg->elements[i]; + + /* + * el2 is assigned after the Pigeonhole principle + * check below for clarity + */ + struct ldb_message_element *el2 = NULL; + unsigned int j; + + if (keep_all == false) { + bool found = false; + for (j = 0; attrs[j]; j++) { + int cmp = ldb_attr_cmp(el->name, attrs[j]); + if (cmp == 0) { + found = true; + break; + } + } + if (found == false) { + continue; + } + } + + /* + * Pigeonhole principle: we can't have more elements + * than the number of attributes if they are unique in + * the DB. + */ + if (num_elements >= elements_size) { + goto failed; + } + + el2 = &filtered_msg->elements[num_elements]; + + *el2 = *el; + el2->name = talloc_strdup(filtered_msg->elements, + el->name); + if (el2->name == NULL) { + goto failed; + } + el2->values = talloc_array(filtered_msg->elements, + struct ldb_val, el->num_values); + if (el2->values == NULL) { + goto failed; + } + for (j=0;jnum_values;j++) { + el2->values[j] = ldb_val_dup(el2->values, &el->values[j]); + if (el2->values[j].data == NULL && el->values[j].length != 0) { + goto failed; + } + } + num_elements++; + } + + filtered_msg->num_elements = num_elements; + + if (add_dn) { + if (ldb_msg_add_distinguished_name(filtered_msg) != 0) { + goto failed; + } + } + + if (filtered_msg->num_elements > 0) { + filtered_msg->elements + = talloc_realloc(filtered_msg, + filtered_msg->elements, + struct ldb_message_element, + filtered_msg->num_elements); + if (filtered_msg->elements == NULL) { + goto failed; + } + } else { + TALLOC_FREE(filtered_msg->elements); + } + + return 0; +failed: + TALLOC_FREE(filtered_msg->elements); + return -1; +} + /* Have an unpacked ldb message take talloc ownership of its elements. */ int ldb_msg_elements_take_ownership(struct ldb_message *msg) { diff --git a/lib/ldb/include/ldb_module.h b/lib/ldb/include/ldb_module.h index 8c7f33496fb..105093cf38c 100644 --- a/lib/ldb/include/ldb_module.h +++ b/lib/ldb/include/ldb_module.h @@ -543,6 +543,16 @@ int ldb_filter_attrs(struct ldb_context *ldb, const char *const *attrs, struct ldb_message *filtered_msg); +/* + * filter the specified list of attributes from msg, + * adding requested attributes, and perhaps all for *, + * but not the DN to filtered_msg. + */ +int ldb_filter_attrs_in_place(struct ldb_context *ldb, + const struct ldb_message *msg, + const char *const *attrs, + struct ldb_message *filtered_msg); + /* Have an unpacked ldb message take talloc ownership of its elements. */ int ldb_msg_elements_take_ownership(struct ldb_message *msg); diff --git a/lib/ldb/tests/ldb_filter_attrs_in_place_test.c b/lib/ldb/tests/ldb_filter_attrs_in_place_test.c new file mode 100644 index 00000000000..bef961f8f9c --- /dev/null +++ b/lib/ldb/tests/ldb_filter_attrs_in_place_test.c @@ -0,0 +1,989 @@ +/* + * Tests exercising ldb_filter_attrs_in_place(). + * + * + * Copyright (C) Catalyst.NET Ltd 2017 + * Copyright (C) Andrew Bartlett 2019 + * + * 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 + +#include "../include/ldb.h" +#include "../include/ldb_module.h" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; +}; + +/* + * NOTE WELL: + * + * This test checks the current behaviour of the function, however + * this is not in a public ABI and many of the tested behaviours are + * not ideal. If the behaviour is deliberatly improved, this test + * should be updated without worry to the new better behaviour. + * + * In particular the test is particularly to ensure the current + * behaviour is memory-safe. + */ + +static int setup(void **state) +{ + struct ldbtest_ctx *test_ctx; + + test_ctx = talloc_zero(NULL, struct ldbtest_ctx); + assert_non_null(test_ctx); + + test_ctx->ev = tevent_context_init(test_ctx); + assert_non_null(test_ctx->ev); + + test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev); + assert_non_null(test_ctx->ldb); + + *state = test_ctx; + return 0; +} + +static int teardown(void **state) +{ + talloc_free(*state); + return 0; +} + + +/* + * Test against a record with only one attribute, matching the one in + * the list + */ +static void test_filter_attrs_one_attr_matched(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"foo", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + + /* + * assert the ldb_filter_attrs_in_place does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + assert_int_equal(filtered_msg->num_elements, 1); + assert_string_equal(filtered_msg->elements[0].name, "foo"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value, strlen(value)); +} + +/* + * Test against a record with only one attribute, matching the one of + * the multiple attributes in the list + */ +static void test_filter_attrs_one_attr_matched_of_many(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"foo", "bar", "baz", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + + /* + * assert the ldb_filter_attrs_in_place does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + assert_int_equal(filtered_msg->num_elements, 1); + assert_string_equal(filtered_msg->elements[0].name, "foo"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value, strlen(value)); +} + +/* + * Test against a record with only one attribute, matching both + * attributes in the list + */ +static void test_filter_attrs_two_attr_matched_attrs(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + /* deliberatly the other order */ + const char *attrs[] = {"bar", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 2); + + /* + * assert the ldb_filter_attrs_in_place does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "foo"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, only of which is in + * the list + */ +static void test_filter_attrs_two_attr_matched_one_attr(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + /* deliberatly the other order */ + const char *attrs[] = {"bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 1); + + /* + * assert the ldb_filter_attrs_in_place does not read or modify + * filtered_msg.dn in this case + */ + assert_null(filtered_msg->dn); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, both matching the one + * specified attribute in the list (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_one_attr(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + /* deliberatly the other order */ + const char *attrs[] = {"bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This should fail the pidgenhole test */ + assert_int_equal(ret, -1); + assert_null(filtered_msg->elements); +} + +/* + * Test against a record with two attributes, both matching the one + * specified attribute in the list (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_dup(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", "bar", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(filtered_msg->num_elements, 2); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes, both matching one of the + * specified attributes in the list (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"bar", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(filtered_msg->num_elements, 2); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); +} + +/* + * Test against a record with two attributes against * (but not the + * other named attribute) (a corrupt record) + */ +static void test_filter_attrs_two_dup_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", "foo", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + + /* foo and bar are the other order to in attrs */ + struct ldb_message_element elements[] = { + { + .name = "bar", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + /* Needed as * implies distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + + /* This does not fail the pidgenhole test */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(filtered_msg->num_elements, 3); + + /* Assert that DB order is preserved */ + assert_string_equal(filtered_msg->elements[0].name, "bar"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_int_equal(filtered_msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(filtered_msg->elements[0].values[0].data, + value1, strlen(value1)); + assert_string_equal(filtered_msg->elements[1].name, "bar"); + assert_int_equal(filtered_msg->elements[1].num_values, 1); + assert_int_equal(filtered_msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(filtered_msg->elements[1].values[0].data, + value2, strlen(value2)); + /* + * assert the ldb_filter_attrs_in_place does not modify filtered_msg.dn + * in this case + */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list + */ +static void test_filter_attrs_one_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + /* Needed as * implies distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 2); + + /* + * assert the ldb_filter_attrs_in_place does not modify filtered_msg.dn + * in this case + */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "foo", + NULL), + value); +} + +/* + * Test against a record with two attributes, matching the * in + * the list + */ +static void test_filter_attrs_two_attr_matched_star(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value1[] = "The value.......end"; + char value2[] = "The value..MUST.end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value1, + .length = strlen(value1) + }; + struct ldb_val value_2 = { + .data = (uint8_t *)value2, + .length = strlen(value2) + }; + struct ldb_message_element elements[] = { + { + .name = "foo", + .num_values = 1, + .values = &value_1 + }, + { + .name = "bar", + .num_values = 1, + .values = &value_2 + } + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 2, + .elements = elements, + }; + + assert_non_null(in.dn); + + /* Needed as * implies distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 3); + + /* + * assert the ldb_filter_attrs_in_place does not modify filtered_msg.dn + * in this case + */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "foo", + NULL), + value1); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "bar", + NULL), + value2); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list, but without the DN being pre-filled. Fails due to need + * to contstruct the distinguishedName + */ +static void test_filter_attrs_one_attr_matched_star_no_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, -1); + assert_null(filtered_msg->elements); +} + +/* + * Test against a record with only one attribute, matching the * in + * the list plus requsesting distinguishedName + */ +static void test_filter_attrs_one_attr_matched_star_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"*", "distinguishedName", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + /* Needed for distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 2); + + /* show that ldb_filter_attrs_in_place does not modify in.dn */ + assert_ptr_equal(filtered_msg->dn, in.dn); + + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "distinguishedName", + NULL), + ldb_dn_get_linearized(in.dn)); + assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + "foo", + NULL), + value); +} + +/* + * Test against a record with only one attribute, but returning + * distinguishedName from the list (only) + */ +static void test_filter_attrs_one_attr_matched_dn(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {"distinguishedName", NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + /* Needed for distinguishedName */ + filtered_msg->dn = in.dn; + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 1); + + /* show that ldb_filter_attrs_in_place does not modify in.dn */ + assert_ptr_equal(filtered_msg->dn, in.dn); + assert_string_equal(filtered_msg->elements[0].name, "distinguishedName"); + assert_int_equal(filtered_msg->elements[0].num_values, 1); + assert_string_equal(filtered_msg->elements[0].values[0].data, + ldb_dn_get_linearized(in.dn)); +} + +/* + * Test against a record with only one attribute, not matching the + * empty attribute list + */ +static void test_filter_attrs_one_attr_empty_list(void **state) +{ + struct ldbtest_ctx *ctx = *state; + int ret; + + struct ldb_message *filtered_msg = ldb_msg_new(ctx); + + const char *attrs[] = {NULL}; + + char value[] = "The value.......end"; + struct ldb_val value_1 = { + .data = (uint8_t *)value, + .length = strlen(value) + }; + struct ldb_message_element element_1 = { + .name = "foo", + .num_values = 1, + .values = &value_1 + }; + struct ldb_message in = { + .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), + .num_elements = 1, + .elements = &element_1, + }; + + assert_non_null(in.dn); + + ret = ldb_filter_attrs_in_place(ctx->ldb, + &in, + attrs, + filtered_msg); + assert_int_equal(ret, LDB_SUCCESS); + assert_non_null(filtered_msg); + assert_int_equal(filtered_msg->num_elements, 0); + assert_null(filtered_msg->dn); + assert_null(filtered_msg->elements); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_of_many, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_attr_matched_attrs, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_attr_matched_one_attr, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_one_attr, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_dup, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_one_of_two, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_dup_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_two_attr_matched_star, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_star_no_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_star_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_matched_dn, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_filter_attrs_one_attr_empty_list, + setup, + teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/ldb/wscript b/lib/ldb/wscript index c862229822d..7e02309c1d5 100644 --- a/lib/ldb/wscript +++ b/lib/ldb/wscript @@ -518,6 +518,11 @@ def build(bld): deps='cmocka ldb ldb_tdb_err_map', install=False) + bld.SAMBA_BINARY('ldb_filter_attrs_in_place_test', + source='tests/ldb_filter_attrs_in_place_test.c', + deps='cmocka ldb ldb_tdb_err_map', + install=False) + bld.SAMBA_BINARY('ldb_key_value_sub_txn_tdb_test', bld.SUBDIR('ldb_key_value', '''ldb_kv_search.c @@ -638,6 +643,7 @@ def test(ctx): # 'ldb_key_value_sub_txn_tdb_test' 'ldb_parse_test', 'ldb_filter_attrs_test', + 'ldb_filter_attrs_in_place_test', ] # if LIB_LDAP and LIB_LBER defined, then we can test ldb_ldap backend -- 2.25.1 From 4ed84d8fabee352fbe542849b01e83f486389a0a Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:30:19 +1300 Subject: [PATCH 15/41] CVE-2023-0614 ldb: Make ldb_filter_attrs_in_place() work in place ldb_filter_attrs() previously did too much. Now its replacement, ldb_filter_attrs_in_place(), only does the actual filtering, while taking ownership of each element's values is handled in a separate function, ldb_msg_elements_take_ownership(). Also, ldb_filter_attrs_in_place() no longer adds the distinguishedName to the message if it is missing. That is handled in another function, ldb_msg_add_distinguished_name(). As we're now modifying the original message rather than copying it into a new one, we no longer need the filtered_msg parameter. We adapt a test, based on ldb_filter_attrs_test, to exercise the new function. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/common/ldb_pack.c | 129 +--- lib/ldb/include/ldb_module.h | 11 +- .../tests/ldb_filter_attrs_in_place_test.c | 609 ++++++++---------- 3 files changed, 307 insertions(+), 442 deletions(-) diff --git a/lib/ldb/common/ldb_pack.c b/lib/ldb/common/ldb_pack.c index f19ac73fa5e..28b9a8dfe07 100644 --- a/lib/ldb/common/ldb_pack.c +++ b/lib/ldb/common/ldb_pack.c @@ -1264,19 +1264,16 @@ failed: /* * filter the specified list of attributes from msg, - * adding requested attributes, and perhaps all for *, - * but not the DN to filtered_msg. + * adding requested attributes, and perhaps all for *. + * Unlike ldb_filter_attrs(), the DN will not be added + * if it is missing. */ -int ldb_filter_attrs_in_place(struct ldb_context *ldb, - const struct ldb_message *msg, - const char *const *attrs, - struct ldb_message *filtered_msg) +int ldb_filter_attrs_in_place(struct ldb_message *msg, + const char *const *attrs) { - unsigned int i; + unsigned int i = 0; bool keep_all = false; - bool add_dn = false; - uint32_t num_elements; - uint32_t elements_size; + unsigned int num_del = 0; if (attrs) { /* check for special attrs */ @@ -1286,123 +1283,41 @@ int ldb_filter_attrs_in_place(struct ldb_context *ldb, keep_all = true; break; } - cmp = ldb_attr_cmp(attrs[i], "distinguishedName"); - if (cmp == 0) { - add_dn = true; - } } - } else { - keep_all = true; - } - - if (keep_all) { - add_dn = true; - elements_size = msg->num_elements + 1; - - /* Shortcuts for the simple cases */ - } else if (add_dn && i == 1) { - if (ldb_msg_add_distinguished_name(filtered_msg) != 0) { - goto failed; + if (!keep_all && i == 0) { + msg->num_elements = 0; + return LDB_SUCCESS; } - return 0; - } else if (i == 0) { - return 0; - - /* - * Otherwise we are copying at most as many elements as we - * have attributes - */ } else { - elements_size = i; + keep_all = true; } - filtered_msg->elements = talloc_array(filtered_msg, - struct ldb_message_element, - elements_size); - if (filtered_msg->elements == NULL) goto failed; - - num_elements = 0; - for (i = 0; i < msg->num_elements; i++) { - struct ldb_message_element *el = &msg->elements[i]; - - /* - * el2 is assigned after the Pigeonhole principle - * check below for clarity - */ - struct ldb_message_element *el2 = NULL; + bool found = false; unsigned int j; - if (keep_all == false) { - bool found = false; + if (keep_all) { + found = true; + } else { for (j = 0; attrs[j]; j++) { - int cmp = ldb_attr_cmp(el->name, attrs[j]); + int cmp = ldb_attr_cmp(msg->elements[i].name, attrs[j]); if (cmp == 0) { found = true; break; } } - if (found == false) { - continue; - } - } - - /* - * Pigeonhole principle: we can't have more elements - * than the number of attributes if they are unique in - * the DB. - */ - if (num_elements >= elements_size) { - goto failed; } - el2 = &filtered_msg->elements[num_elements]; - - *el2 = *el; - el2->name = talloc_strdup(filtered_msg->elements, - el->name); - if (el2->name == NULL) { - goto failed; - } - el2->values = talloc_array(filtered_msg->elements, - struct ldb_val, el->num_values); - if (el2->values == NULL) { - goto failed; + if (!found) { + ++num_del; + } else if (num_del != 0) { + msg->elements[i - num_del] = msg->elements[i]; } - for (j=0;jnum_values;j++) { - el2->values[j] = ldb_val_dup(el2->values, &el->values[j]); - if (el2->values[j].data == NULL && el->values[j].length != 0) { - goto failed; - } - } - num_elements++; } - filtered_msg->num_elements = num_elements; - - if (add_dn) { - if (ldb_msg_add_distinguished_name(filtered_msg) != 0) { - goto failed; - } - } + msg->num_elements -= num_del; - if (filtered_msg->num_elements > 0) { - filtered_msg->elements - = talloc_realloc(filtered_msg, - filtered_msg->elements, - struct ldb_message_element, - filtered_msg->num_elements); - if (filtered_msg->elements == NULL) { - goto failed; - } - } else { - TALLOC_FREE(filtered_msg->elements); - } - - return 0; -failed: - TALLOC_FREE(filtered_msg->elements); - return -1; + return LDB_SUCCESS; } /* Have an unpacked ldb message take talloc ownership of its elements. */ diff --git a/lib/ldb/include/ldb_module.h b/lib/ldb/include/ldb_module.h index 105093cf38c..4ae381ba5be 100644 --- a/lib/ldb/include/ldb_module.h +++ b/lib/ldb/include/ldb_module.h @@ -545,13 +545,12 @@ int ldb_filter_attrs(struct ldb_context *ldb, /* * filter the specified list of attributes from msg, - * adding requested attributes, and perhaps all for *, - * but not the DN to filtered_msg. + * adding requested attributes, and perhaps all for *. + * Unlike ldb_filter_attrs(), the DN will not be added + * if it is missing. */ -int ldb_filter_attrs_in_place(struct ldb_context *ldb, - const struct ldb_message *msg, - const char *const *attrs, - struct ldb_message *filtered_msg); +int ldb_filter_attrs_in_place(struct ldb_message *msg, + const char *const *attrs); /* Have an unpacked ldb message take talloc ownership of its elements. */ int ldb_msg_elements_take_ownership(struct ldb_message *msg); diff --git a/lib/ldb/tests/ldb_filter_attrs_in_place_test.c b/lib/ldb/tests/ldb_filter_attrs_in_place_test.c index bef961f8f9c..da333c73c99 100644 --- a/lib/ldb/tests/ldb_filter_attrs_in_place_test.c +++ b/lib/ldb/tests/ldb_filter_attrs_in_place_test.c @@ -83,17 +83,41 @@ static int teardown(void **state) return 0; } +static void msg_add_dn(struct ldb_message *msg) +{ + const char *dn_attr = "distinguishedName"; + char *dn = NULL; + int ret; + + assert_null(ldb_msg_find_element(msg, dn_attr)); + + assert_non_null(msg->dn); + dn = ldb_dn_alloc_linearized(msg, msg->dn); + assert_non_null(dn); + + /* + * The message's elements must be talloc allocated to call + * ldb_msg_add_steal_string(). + */ + msg->elements = talloc_memdup(msg, + msg->elements, + msg->num_elements * sizeof(msg->elements[0])); + assert_non_null(msg->elements); + + ret = ldb_msg_add_steal_string(msg, dn_attr, dn); + assert_int_equal(ret, LDB_SUCCESS); +} /* * Test against a record with only one attribute, matching the one in * the list */ -static void test_filter_attrs_one_attr_matched(void **state) +static void test_filter_attrs_in_place_one_attr_matched(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"foo", NULL}; @@ -107,32 +131,25 @@ static void test_filter_attrs_one_attr_matched(void **state) .num_values = 1, .values = &value_1 }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 1, - .elements = &element_1, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - /* - * assert the ldb_filter_attrs_in_place does not read or modify - * filtered_msg.dn in this case - */ - assert_null(filtered_msg->dn); - assert_int_equal(filtered_msg->num_elements, 1); - assert_string_equal(filtered_msg->elements[0].name, "foo"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_int_equal(filtered_msg->elements[0].values[0].length, + assert_non_null(msg->dn); + assert_int_equal(msg->num_elements, 1); + assert_string_equal(msg->elements[0].name, "foo"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, strlen(value)); - assert_memory_equal(filtered_msg->elements[0].values[0].data, + assert_memory_equal(msg->elements[0].values[0].data, value, strlen(value)); } @@ -140,12 +157,12 @@ static void test_filter_attrs_one_attr_matched(void **state) * Test against a record with only one attribute, matching the one of * the multiple attributes in the list */ -static void test_filter_attrs_one_attr_matched_of_many(void **state) +static void test_filter_attrs_in_place_one_attr_matched_of_many(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"foo", "bar", "baz", NULL}; @@ -159,32 +176,25 @@ static void test_filter_attrs_one_attr_matched_of_many(void **state) .num_values = 1, .values = &value_1 }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 1, - .elements = &element_1, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - /* - * assert the ldb_filter_attrs_in_place does not read or modify - * filtered_msg.dn in this case - */ - assert_null(filtered_msg->dn); - assert_int_equal(filtered_msg->num_elements, 1); - assert_string_equal(filtered_msg->elements[0].name, "foo"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_int_equal(filtered_msg->elements[0].values[0].length, + assert_non_null(msg->dn); + assert_int_equal(msg->num_elements, 1); + assert_string_equal(msg->elements[0].name, "foo"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, strlen(value)); - assert_memory_equal(filtered_msg->elements[0].values[0].data, + assert_memory_equal(msg->elements[0].values[0].data, value, strlen(value)); } @@ -192,12 +202,12 @@ static void test_filter_attrs_one_attr_matched_of_many(void **state) * Test against a record with only one attribute, matching both * attributes in the list */ -static void test_filter_attrs_two_attr_matched_attrs(void **state) +static void test_filter_attrs_in_place_two_attr_matched_attrs(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); /* deliberatly the other order */ const char *attrs[] = {"bar", "foo", NULL}; @@ -226,40 +236,33 @@ static void test_filter_attrs_two_attr_matched_attrs(void **state) .values = &value_2 } }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 2, - .elements = elements, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - assert_int_equal(filtered_msg->num_elements, 2); + assert_int_equal(msg->num_elements, 2); - /* - * assert the ldb_filter_attrs_in_place does not read or modify - * filtered_msg.dn in this case - */ - assert_null(filtered_msg->dn); + assert_non_null(msg->dn); /* Assert that DB order is preserved */ - assert_string_equal(filtered_msg->elements[0].name, "foo"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_int_equal(filtered_msg->elements[0].values[0].length, + assert_string_equal(msg->elements[0].name, "foo"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, strlen(value1)); - assert_memory_equal(filtered_msg->elements[0].values[0].data, + assert_memory_equal(msg->elements[0].values[0].data, value1, strlen(value1)); - assert_string_equal(filtered_msg->elements[1].name, "bar"); - assert_int_equal(filtered_msg->elements[1].num_values, 1); - assert_int_equal(filtered_msg->elements[1].values[0].length, + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, strlen(value2)); - assert_memory_equal(filtered_msg->elements[1].values[0].data, + assert_memory_equal(msg->elements[1].values[0].data, value2, strlen(value2)); } @@ -267,14 +270,13 @@ static void test_filter_attrs_two_attr_matched_attrs(void **state) * Test against a record with two attributes, only of which is in * the list */ -static void test_filter_attrs_two_attr_matched_one_attr(void **state) +static void test_filter_attrs_in_place_two_attr_matched_one_attr(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); - /* deliberatly the other order */ const char *attrs[] = {"bar", NULL}; char value1[] = "The value.......end"; @@ -288,7 +290,6 @@ static void test_filter_attrs_two_attr_matched_one_attr(void **state) .length = strlen(value2) }; - /* foo and bar are the other order to in attrs */ struct ldb_message_element elements[] = { { .name = "foo", @@ -301,34 +302,27 @@ static void test_filter_attrs_two_attr_matched_one_attr(void **state) .values = &value_2 } }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 2, - .elements = elements, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - assert_int_equal(filtered_msg->num_elements, 1); + assert_int_equal(msg->num_elements, 1); - /* - * assert the ldb_filter_attrs_in_place does not read or modify - * filtered_msg.dn in this case - */ - assert_null(filtered_msg->dn); + assert_non_null(msg->dn); /* Assert that DB order is preserved */ - assert_string_equal(filtered_msg->elements[0].name, "bar"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_int_equal(filtered_msg->elements[0].values[0].length, + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, strlen(value2)); - assert_memory_equal(filtered_msg->elements[0].values[0].data, + assert_memory_equal(msg->elements[0].values[0].data, value2, strlen(value2)); } @@ -336,14 +330,13 @@ static void test_filter_attrs_two_attr_matched_one_attr(void **state) * Test against a record with two attributes, both matching the one * specified attribute in the list (a corrupt record) */ -static void test_filter_attrs_two_dup_attr_matched_one_attr(void **state) +static void test_filter_attrs_in_place_two_dup_attr_matched_one_attr(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); - /* deliberatly the other order */ const char *attrs[] = {"bar", NULL}; char value1[] = "The value.......end"; @@ -357,7 +350,6 @@ static void test_filter_attrs_two_dup_attr_matched_one_attr(void **state) .length = strlen(value2) }; - /* foo and bar are the other order to in attrs */ struct ldb_message_element elements[] = { { .name = "bar", @@ -370,34 +362,49 @@ static void test_filter_attrs_two_dup_attr_matched_one_attr(void **state) .values = &value_2 } }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 2, - .elements = elements, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + /* Both elements match the filter */ + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 2); + + assert_non_null(msg->dn); - /* This should fail the pidgenhole test */ - assert_int_equal(ret, -1); - assert_null(filtered_msg->elements); + /* Assert that DB order is preserved */ + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, + strlen(value1)); + assert_memory_equal(msg->elements[0].values[0].data, + value1, strlen(value1)); + + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, + strlen(value2)); + assert_memory_equal(msg->elements[1].values[0].data, + value2, strlen(value2)); } /* * Test against a record with two attributes, both matching the one * specified attribute in the list (a corrupt record) */ -static void test_filter_attrs_two_dup_attr_matched_dup(void **state) +static void test_filter_attrs_in_place_two_dup_attr_matched_dup(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"bar", "bar", NULL}; @@ -412,7 +419,6 @@ static void test_filter_attrs_two_dup_attr_matched_dup(void **state) .length = strlen(value2) }; - /* foo and bar are the other order to in attrs */ struct ldb_message_element elements[] = { { .name = "bar", @@ -425,35 +431,33 @@ static void test_filter_attrs_two_dup_attr_matched_dup(void **state) .values = &value_2 } }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 2, - .elements = elements, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; + + assert_non_null(msg->dn); + msg_add_dn(msg); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + ret = ldb_filter_attrs_in_place(msg, attrs); /* This does not fail the pidgenhole test */ assert_int_equal(ret, LDB_SUCCESS); - assert_int_equal(filtered_msg->num_elements, 2); + assert_int_equal(msg->num_elements, 2); /* Assert that DB order is preserved */ - assert_string_equal(filtered_msg->elements[0].name, "bar"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_int_equal(filtered_msg->elements[0].values[0].length, + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, strlen(value1)); - assert_memory_equal(filtered_msg->elements[0].values[0].data, + assert_memory_equal(msg->elements[0].values[0].data, value1, strlen(value1)); - assert_string_equal(filtered_msg->elements[1].name, "bar"); - assert_int_equal(filtered_msg->elements[1].num_values, 1); - assert_int_equal(filtered_msg->elements[1].values[0].length, + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, strlen(value2)); - assert_memory_equal(filtered_msg->elements[1].values[0].data, + assert_memory_equal(msg->elements[1].values[0].data, value2, strlen(value2)); } @@ -461,12 +465,12 @@ static void test_filter_attrs_two_dup_attr_matched_dup(void **state) * Test against a record with two attributes, both matching one of the * specified attributes in the list (a corrupt record) */ -static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) +static void test_filter_attrs_in_place_two_dup_attr_matched_one_of_two(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"bar", "foo", NULL}; @@ -481,7 +485,6 @@ static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) .length = strlen(value2) }; - /* foo and bar are the other order to in attrs */ struct ldb_message_element elements[] = { { .name = "bar", @@ -494,35 +497,33 @@ static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) .values = &value_2 } }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 2, - .elements = elements, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + assert_non_null(msg->dn); + msg_add_dn(msg); + + ret = ldb_filter_attrs_in_place(msg, attrs); /* This does not fail the pidgenhole test */ assert_int_equal(ret, LDB_SUCCESS); - assert_int_equal(filtered_msg->num_elements, 2); + assert_int_equal(msg->num_elements, 2); /* Assert that DB order is preserved */ - assert_string_equal(filtered_msg->elements[0].name, "bar"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_int_equal(filtered_msg->elements[0].values[0].length, + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, strlen(value1)); - assert_memory_equal(filtered_msg->elements[0].values[0].data, + assert_memory_equal(msg->elements[0].values[0].data, value1, strlen(value1)); - assert_string_equal(filtered_msg->elements[1].name, "bar"); - assert_int_equal(filtered_msg->elements[1].num_values, 1); - assert_int_equal(filtered_msg->elements[1].values[0].length, + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, strlen(value2)); - assert_memory_equal(filtered_msg->elements[1].values[0].data, + assert_memory_equal(msg->elements[1].values[0].data, value2, strlen(value2)); } @@ -530,12 +531,12 @@ static void test_filter_attrs_two_dup_attr_matched_one_of_two(void **state) * Test against a record with two attributes against * (but not the * other named attribute) (a corrupt record) */ -static void test_filter_attrs_two_dup_attr_matched_star(void **state) +static void test_filter_attrs_in_place_two_dup_attr_matched_star(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"*", "foo", NULL}; @@ -550,7 +551,6 @@ static void test_filter_attrs_two_dup_attr_matched_star(void **state) .length = strlen(value2) }; - /* foo and bar are the other order to in attrs */ struct ldb_message_element elements[] = { { .name = "bar", @@ -563,60 +563,52 @@ static void test_filter_attrs_two_dup_attr_matched_star(void **state) .values = &value_2 } }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 2, - .elements = elements, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; - /* Needed as * implies distinguishedName */ - filtered_msg->dn = in.dn; + assert_non_null(msg->dn); + msg_add_dn(msg); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + ret = ldb_filter_attrs_in_place(msg, attrs); /* This does not fail the pidgenhole test */ assert_int_equal(ret, LDB_SUCCESS); - assert_int_equal(filtered_msg->num_elements, 3); + assert_int_equal(msg->num_elements, 3); /* Assert that DB order is preserved */ - assert_string_equal(filtered_msg->elements[0].name, "bar"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_int_equal(filtered_msg->elements[0].values[0].length, + assert_string_equal(msg->elements[0].name, "bar"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_int_equal(msg->elements[0].values[0].length, strlen(value1)); - assert_memory_equal(filtered_msg->elements[0].values[0].data, + assert_memory_equal(msg->elements[0].values[0].data, value1, strlen(value1)); - assert_string_equal(filtered_msg->elements[1].name, "bar"); - assert_int_equal(filtered_msg->elements[1].num_values, 1); - assert_int_equal(filtered_msg->elements[1].values[0].length, + assert_string_equal(msg->elements[1].name, "bar"); + assert_int_equal(msg->elements[1].num_values, 1); + assert_int_equal(msg->elements[1].values[0].length, strlen(value2)); - assert_memory_equal(filtered_msg->elements[1].values[0].data, + assert_memory_equal(msg->elements[1].values[0].data, value2, strlen(value2)); - /* - * assert the ldb_filter_attrs_in_place does not modify filtered_msg.dn - * in this case - */ - assert_ptr_equal(filtered_msg->dn, in.dn); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + + assert_non_null(msg->dn); + assert_string_equal(ldb_msg_find_attr_as_string(msg, "distinguishedName", NULL), - ldb_dn_get_linearized(in.dn)); + ldb_dn_get_linearized(msg->dn)); } /* * Test against a record with only one attribute, matching the * in * the list */ -static void test_filter_attrs_one_attr_matched_star(void **state) +static void test_filter_attrs_in_place_one_attr_matched_star(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"*", NULL}; @@ -630,35 +622,25 @@ static void test_filter_attrs_one_attr_matched_star(void **state) .num_values = 1, .values = &value_1 }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 1, - .elements = &element_1, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; - /* Needed as * implies distinguishedName */ - filtered_msg->dn = in.dn; + assert_non_null(msg->dn); + msg_add_dn(msg); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - assert_int_equal(filtered_msg->num_elements, 2); + assert_int_equal(msg->num_elements, 2); - /* - * assert the ldb_filter_attrs_in_place does not modify filtered_msg.dn - * in this case - */ - assert_ptr_equal(filtered_msg->dn, in.dn); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + assert_non_null(msg->dn); + assert_string_equal(ldb_msg_find_attr_as_string(msg, "distinguishedName", NULL), - ldb_dn_get_linearized(in.dn)); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + ldb_dn_get_linearized(msg->dn)); + assert_string_equal(ldb_msg_find_attr_as_string(msg, "foo", NULL), value); @@ -668,12 +650,12 @@ static void test_filter_attrs_one_attr_matched_star(void **state) * Test against a record with two attributes, matching the * in * the list */ -static void test_filter_attrs_two_attr_matched_star(void **state) +static void test_filter_attrs_in_place_two_attr_matched_star(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"*", NULL}; @@ -699,39 +681,29 @@ static void test_filter_attrs_two_attr_matched_star(void **state) .values = &value_2 } }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 2, - .elements = elements, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 2; + msg->elements = elements; - /* Needed as * implies distinguishedName */ - filtered_msg->dn = in.dn; + assert_non_null(msg->dn); + msg_add_dn(msg); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - assert_int_equal(filtered_msg->num_elements, 3); + assert_int_equal(msg->num_elements, 3); - /* - * assert the ldb_filter_attrs_in_place does not modify filtered_msg.dn - * in this case - */ - assert_ptr_equal(filtered_msg->dn, in.dn); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + assert_non_null(msg->dn); + assert_string_equal(ldb_msg_find_attr_as_string(msg, "distinguishedName", NULL), - ldb_dn_get_linearized(in.dn)); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + ldb_dn_get_linearized(msg->dn)); + assert_string_equal(ldb_msg_find_attr_as_string(msg, "foo", NULL), value1); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + assert_string_equal(ldb_msg_find_attr_as_string(msg, "bar", NULL), value2); @@ -739,15 +711,15 @@ static void test_filter_attrs_two_attr_matched_star(void **state) /* * Test against a record with only one attribute, matching the * in - * the list, but without the DN being pre-filled. Fails due to need - * to contstruct the distinguishedName + * the list, but without the DN being pre-filled. Succeeds, but the + * distinguishedName is not added. */ -static void test_filter_attrs_one_attr_matched_star_no_dn(void **state) +static void test_filter_attrs_in_place_one_attr_matched_star_no_dn(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"*", NULL}; @@ -761,32 +733,29 @@ static void test_filter_attrs_one_attr_matched_star_no_dn(void **state) .num_values = 1, .values = &value_1 }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 1, - .elements = &element_1, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = NULL; + msg->num_elements = 1; + msg->elements = &element_1; + + assert_null(msg->dn); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); - assert_int_equal(ret, -1); - assert_null(filtered_msg->elements); + ret = ldb_filter_attrs_in_place(msg, attrs); + assert_int_equal(ret, LDB_SUCCESS); + assert_int_equal(msg->num_elements, 1); } /* * Test against a record with only one attribute, matching the * in * the list plus requsesting distinguishedName */ -static void test_filter_attrs_one_attr_matched_star_dn(void **state) +static void test_filter_attrs_in_place_one_attr_matched_star_dn(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"*", "distinguishedName", NULL}; @@ -800,33 +769,26 @@ static void test_filter_attrs_one_attr_matched_star_dn(void **state) .num_values = 1, .values = &value_1 }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 1, - .elements = &element_1, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; - /* Needed for distinguishedName */ - filtered_msg->dn = in.dn; + assert_non_null(msg->dn); + msg_add_dn(msg); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - assert_int_equal(filtered_msg->num_elements, 2); + assert_int_equal(msg->num_elements, 2); - /* show that ldb_filter_attrs_in_place does not modify in.dn */ - assert_ptr_equal(filtered_msg->dn, in.dn); + assert_non_null(msg->dn); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + assert_string_equal(ldb_msg_find_attr_as_string(msg, "distinguishedName", NULL), - ldb_dn_get_linearized(in.dn)); - assert_string_equal(ldb_msg_find_attr_as_string(filtered_msg, + ldb_dn_get_linearized(msg->dn)); + assert_string_equal(ldb_msg_find_attr_as_string(msg, "foo", NULL), value); @@ -836,12 +798,12 @@ static void test_filter_attrs_one_attr_matched_star_dn(void **state) * Test against a record with only one attribute, but returning * distinguishedName from the list (only) */ -static void test_filter_attrs_one_attr_matched_dn(void **state) +static void test_filter_attrs_in_place_one_attr_matched_dn(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {"distinguishedName", NULL}; @@ -855,43 +817,36 @@ static void test_filter_attrs_one_attr_matched_dn(void **state) .num_values = 1, .values = &value_1 }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 1, - .elements = &element_1, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; - /* Needed for distinguishedName */ - filtered_msg->dn = in.dn; + assert_non_null(msg->dn); + msg_add_dn(msg); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - assert_int_equal(filtered_msg->num_elements, 1); - - /* show that ldb_filter_attrs_in_place does not modify in.dn */ - assert_ptr_equal(filtered_msg->dn, in.dn); - assert_string_equal(filtered_msg->elements[0].name, "distinguishedName"); - assert_int_equal(filtered_msg->elements[0].num_values, 1); - assert_string_equal(filtered_msg->elements[0].values[0].data, - ldb_dn_get_linearized(in.dn)); + assert_int_equal(msg->num_elements, 1); + + assert_non_null(msg->dn); + assert_string_equal(msg->elements[0].name, "distinguishedName"); + assert_int_equal(msg->elements[0].num_values, 1); + assert_string_equal(msg->elements[0].values[0].data, + ldb_dn_get_linearized(msg->dn)); } /* * Test against a record with only one attribute, not matching the * empty attribute list */ -static void test_filter_attrs_one_attr_empty_list(void **state) +static void test_filter_attrs_in_place_one_attr_empty_list(void **state) { struct ldbtest_ctx *ctx = *state; int ret; - struct ldb_message *filtered_msg = ldb_msg_new(ctx); + struct ldb_message *msg = ldb_msg_new(ctx); const char *attrs[] = {NULL}; @@ -905,82 +860,78 @@ static void test_filter_attrs_one_attr_empty_list(void **state) .num_values = 1, .values = &value_1 }; - struct ldb_message in = { - .dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"), - .num_elements = 1, - .elements = &element_1, - }; - assert_non_null(in.dn); + assert_non_null(msg); + msg->dn = ldb_dn_new(ctx, ctx->ldb, "dc=samba,dc=org"); + msg->num_elements = 1; + msg->elements = &element_1; + + assert_non_null(msg->dn); + msg_add_dn(msg); - ret = ldb_filter_attrs_in_place(ctx->ldb, - &in, - attrs, - filtered_msg); + ret = ldb_filter_attrs_in_place(msg, attrs); assert_int_equal(ret, LDB_SUCCESS); - assert_non_null(filtered_msg); - assert_int_equal(filtered_msg->num_elements, 0); - assert_null(filtered_msg->dn); - assert_null(filtered_msg->elements); + assert_int_equal(msg->num_elements, 0); + assert_non_null(msg->dn); } int main(int argc, const char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown( - test_filter_attrs_one_attr_matched, + test_filter_attrs_in_place_one_attr_matched, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_one_attr_matched_of_many, + test_filter_attrs_in_place_one_attr_matched_of_many, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_two_attr_matched_attrs, + test_filter_attrs_in_place_two_attr_matched_attrs, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_two_attr_matched_one_attr, + test_filter_attrs_in_place_two_attr_matched_one_attr, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_two_dup_attr_matched_one_attr, + test_filter_attrs_in_place_two_dup_attr_matched_one_attr, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_two_dup_attr_matched_dup, + test_filter_attrs_in_place_two_dup_attr_matched_dup, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_two_dup_attr_matched_one_of_two, + test_filter_attrs_in_place_two_dup_attr_matched_one_of_two, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_two_dup_attr_matched_star, + test_filter_attrs_in_place_two_dup_attr_matched_star, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_one_attr_matched_star, + test_filter_attrs_in_place_one_attr_matched_star, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_two_attr_matched_star, + test_filter_attrs_in_place_two_attr_matched_star, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_one_attr_matched_star_no_dn, + test_filter_attrs_in_place_one_attr_matched_star_no_dn, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_one_attr_matched_star_dn, + test_filter_attrs_in_place_one_attr_matched_star_dn, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_one_attr_matched_dn, + test_filter_attrs_in_place_one_attr_matched_dn, setup, teardown), cmocka_unit_test_setup_teardown( - test_filter_attrs_one_attr_empty_list, + test_filter_attrs_in_place_one_attr_empty_list, setup, teardown), }; -- 2.25.1 From 78a7f247dba26ddeffe7a388108cf6c9618d437e Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 27 Feb 2023 10:31:52 +1300 Subject: [PATCH 16/41] CVE-2023-0614 ldb: Make use of ldb_filter_attrs_in_place() Change all uses of ldb_kv_filter_attrs() to use ldb_filter_attrs_in_place() instead. This function does less work than its predecessor, and no longer requires the allocation of a second ldb message. Some of the work is able to be split out into separate functions that each accomplish a single task, with a purpose to make the code clearer. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/ldb_key_value/ldb_kv.h | 6 +- lib/ldb/ldb_key_value/ldb_kv_index.c | 27 +++++---- lib/ldb/ldb_key_value/ldb_kv_search.c | 86 ++++++++++++++------------- source4/torture/ldb/ldb.c | 12 ++-- 4 files changed, 66 insertions(+), 65 deletions(-) diff --git a/lib/ldb/ldb_key_value/ldb_kv.h b/lib/ldb/ldb_key_value/ldb_kv.h index ac474b04b4c..7d5a40e76e9 100644 --- a/lib/ldb/ldb_key_value/ldb_kv.h +++ b/lib/ldb/ldb_key_value/ldb_kv.h @@ -301,10 +301,8 @@ int ldb_kv_search_key(struct ldb_module *module, const struct ldb_val ldb_key, struct ldb_message *msg, unsigned int unpack_flags); -int ldb_kv_filter_attrs(struct ldb_context *ldb, - const struct ldb_message *msg, - const char *const *attrs, - struct ldb_message *filtered_msg); +int ldb_kv_filter_attrs_in_place(struct ldb_message *msg, + const char *const *attrs); int ldb_kv_search(struct ldb_kv_context *ctx); /* diff --git a/lib/ldb/ldb_key_value/ldb_kv_index.c b/lib/ldb/ldb_key_value/ldb_kv_index.c index d70e5f619ef..203266ea8c9 100644 --- a/lib/ldb/ldb_key_value/ldb_kv_index.c +++ b/lib/ldb/ldb_key_value/ldb_kv_index.c @@ -2264,7 +2264,6 @@ static int ldb_kv_index_filter(struct ldb_kv_private *ldb_kv, { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); struct ldb_message *msg; - struct ldb_message *filtered_msg; unsigned int i; unsigned int num_keys = 0; uint8_t previous_guid_key[LDB_KV_GUID_KEY_SIZE] = {0}; @@ -2456,27 +2455,31 @@ static int ldb_kv_index_filter(struct ldb_kv_private *ldb_kv, continue; } - filtered_msg = ldb_msg_new(ac); - if (filtered_msg == NULL) { - TALLOC_FREE(keys); - TALLOC_FREE(msg); + ret = ldb_msg_add_distinguished_name(msg); + if (ret == -1) { + talloc_free(msg); return LDB_ERR_OPERATIONS_ERROR; } - filtered_msg->dn = talloc_steal(filtered_msg, msg->dn); - /* filter the attributes that the user wants */ - ret = ldb_kv_filter_attrs(ldb, msg, ac->attrs, filtered_msg); + ret = ldb_kv_filter_attrs_in_place(msg, ac->attrs); + if (ret != LDB_SUCCESS) { + talloc_free(keys); + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } - talloc_free(msg); + ldb_msg_shrink_to_fit(msg); - if (ret == -1) { - TALLOC_FREE(filtered_msg); + /* Ensure the message elements are all talloc'd. */ + ret = ldb_msg_elements_take_ownership(msg); + if (ret != LDB_SUCCESS) { talloc_free(keys); + talloc_free(msg); return LDB_ERR_OPERATIONS_ERROR; } - ret = ldb_module_send_entry(ac->req, filtered_msg, NULL); + ret = ldb_module_send_entry(ac->req, msg, NULL); if (ret != LDB_SUCCESS) { /* Regardless of success or failure, the msg * is the callbacks responsiblity, and should diff --git a/lib/ldb/ldb_key_value/ldb_kv_search.c b/lib/ldb/ldb_key_value/ldb_kv_search.c index 46031b99c16..f3333510eab 100644 --- a/lib/ldb/ldb_key_value/ldb_kv_search.c +++ b/lib/ldb/ldb_key_value/ldb_kv_search.c @@ -292,15 +292,13 @@ int ldb_kv_search_dn1(struct ldb_module *module, /* * filter the specified list of attributes from msg, - * adding requested attributes, and perhaps all for *, - * but not the DN to filtered_msg. + * adding requested attributes, and perhaps all for *. + * The DN will not be added if it is missing. */ -int ldb_kv_filter_attrs(struct ldb_context *ldb, - const struct ldb_message *msg, - const char *const *attrs, - struct ldb_message *filtered_msg) +int ldb_kv_filter_attrs_in_place(struct ldb_message *msg, + const char *const *attrs) { - return ldb_filter_attrs(ldb, msg, attrs, filtered_msg); + return ldb_filter_attrs_in_place(msg, attrs); } /* @@ -313,7 +311,7 @@ static int search_func(_UNUSED_ struct ldb_kv_private *ldb_kv, { struct ldb_context *ldb; struct ldb_kv_context *ac; - struct ldb_message *msg, *filtered_msg; + struct ldb_message *msg; struct timeval now; int ret, timeval_cmp; bool matched; @@ -410,25 +408,31 @@ static int search_func(_UNUSED_ struct ldb_kv_private *ldb_kv, return 0; } - filtered_msg = ldb_msg_new(ac); - if (filtered_msg == NULL) { - TALLOC_FREE(msg); + ret = ldb_msg_add_distinguished_name(msg); + if (ret == -1) { + talloc_free(msg); return LDB_ERR_OPERATIONS_ERROR; } - filtered_msg->dn = talloc_steal(filtered_msg, msg->dn); - /* filter the attributes that the user wants */ - ret = ldb_kv_filter_attrs(ldb, msg, ac->attrs, filtered_msg); - talloc_free(msg); + ret = ldb_kv_filter_attrs_in_place(msg, ac->attrs); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + ac->error = LDB_ERR_OPERATIONS_ERROR; + return -1; + } - if (ret == -1) { - TALLOC_FREE(filtered_msg); + ldb_msg_shrink_to_fit(msg); + + /* Ensure the message elements are all talloc'd. */ + ret = ldb_msg_elements_take_ownership(msg); + if (ret != LDB_SUCCESS) { + talloc_free(msg); ac->error = LDB_ERR_OPERATIONS_ERROR; return -1; } - ret = ldb_module_send_entry(ac->req, filtered_msg, NULL); + ret = ldb_module_send_entry(ac->req, msg, NULL); if (ret != LDB_SUCCESS) { ac->request_terminated = true; /* the callback failed, abort the operation */ @@ -491,7 +495,7 @@ static int ldb_kv_search_full(struct ldb_kv_context *ctx) static int ldb_kv_search_and_return_base(struct ldb_kv_private *ldb_kv, struct ldb_kv_context *ctx) { - struct ldb_message *msg, *filtered_msg; + struct ldb_message *msg; struct ldb_context *ldb = ldb_module_get_ctx(ctx->module); const char *dn_linearized; const char *msg_dn_linearized; @@ -549,12 +553,6 @@ static int ldb_kv_search_and_return_base(struct ldb_kv_private *ldb_kv, dn_linearized = ldb_dn_get_linearized(ctx->base); msg_dn_linearized = ldb_dn_get_linearized(msg->dn); - filtered_msg = ldb_msg_new(ctx); - if (filtered_msg == NULL) { - talloc_free(msg); - return LDB_ERR_OPERATIONS_ERROR; - } - if (strcmp(dn_linearized, msg_dn_linearized) == 0) { /* * If the DN is exactly the same string, then @@ -562,36 +560,42 @@ static int ldb_kv_search_and_return_base(struct ldb_kv_private *ldb_kv, * returned result, as it has already been * casefolded */ - filtered_msg->dn = ldb_dn_copy(filtered_msg, ctx->base); + struct ldb_dn *dn = ldb_dn_copy(msg, ctx->base); + if (dn != NULL) { + msg->dn = dn; + } } - /* - * If the ldb_dn_copy() failed, or if we did not choose that - * optimisation (filtered_msg is zeroed at allocation), - * steal the one from the unpack - */ - if (filtered_msg->dn == NULL) { - filtered_msg->dn = talloc_steal(filtered_msg, msg->dn); + ret = ldb_msg_add_distinguished_name(msg); + if (ret == -1) { + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; } /* * filter the attributes that the user wants. */ - ret = ldb_kv_filter_attrs(ldb, msg, ctx->attrs, filtered_msg); - if (ret == -1) { + ret = ldb_kv_filter_attrs_in_place(msg, ctx->attrs); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return LDB_ERR_OPERATIONS_ERROR; + } + + ldb_msg_shrink_to_fit(msg); + + /* Ensure the message elements are all talloc'd. */ + ret = ldb_msg_elements_take_ownership(msg); + if (ret != LDB_SUCCESS) { talloc_free(msg); - filtered_msg = NULL; return LDB_ERR_OPERATIONS_ERROR; } /* - * Remove any extended components possibly copied in from - * msg->dn, we just want the casefold components + * Remove any extended components, we just want the casefold components */ - ldb_dn_remove_extended_components(filtered_msg->dn); - talloc_free(msg); + ldb_dn_remove_extended_components(msg->dn); - ret = ldb_module_send_entry(ctx->req, filtered_msg, NULL); + ret = ldb_module_send_entry(ctx->req, msg, NULL); if (ret != LDB_SUCCESS) { /* Regardless of success or failure, the msg * is the callbacks responsiblity, and should diff --git a/source4/torture/ldb/ldb.c b/source4/torture/ldb/ldb.c index bd0ae3a382a..c170416bec4 100644 --- a/source4/torture/ldb/ldb.c +++ b/source4/torture/ldb/ldb.c @@ -1634,7 +1634,6 @@ static bool torture_ldb_unpack_and_filter(struct torture_context *torture, TALLOC_CTX *mem_ctx = talloc_new(torture); struct ldb_context *ldb; struct ldb_val data = *discard_const_p(struct ldb_val, data_p); - struct ldb_message *unpack_msg = ldb_msg_new(mem_ctx); struct ldb_message *msg = ldb_msg_new(mem_ctx); const char *lookup_names[] = {"instanceType", "nonexistent", "whenChanged", "objectClass", @@ -1649,18 +1648,15 @@ static bool torture_ldb_unpack_and_filter(struct torture_context *torture, "Failed to init samba"); torture_assert_int_equal(torture, - ldb_unpack_data(ldb, &data, unpack_msg), + ldb_unpack_data(ldb, &data, msg), 0, "ldb_unpack_data failed"); - torture_assert_int_equal(torture, unpack_msg->num_elements, 13, + torture_assert_int_equal(torture, msg->num_elements, 13, "Got wrong count of elements"); - msg->dn = talloc_steal(msg, unpack_msg->dn); - torture_assert_int_equal(torture, - ldb_filter_attrs(ldb, unpack_msg, - lookup_names, msg), - 0, "ldb_kv_filter_attrs failed"); + ldb_filter_attrs_in_place(msg, lookup_names), + 0, "ldb_filter_attrs_in_place failed"); /* Compare data in binary form */ torture_assert_int_equal(torture, msg->num_elements, 6, -- 2.25.1 From 2ea5bbc269e3d7796247ccf428e082f383d51ec8 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 7 Feb 2023 09:35:24 +1300 Subject: [PATCH 17/41] CVE-2023-0614 s4:dsdb/extended_dn_in: Don't modify a search tree we don't own In extended_dn_fix_filter() we had: req->op.search.tree = ldb_parse_tree_copy_shallow(req, req->op.search.tree); which overwrote the parse tree on an existing ldb request with a fixed up tree. This became a problem if a module performed another search with that same request structure, as extended_dn_in would try to fix up the already-modified tree for a second time. The fixed-up tree element now having an extended DN, it would fall foul of the ldb_dn_match_allowed() check in extended_dn_filter_callback(), and be replaced with an ALWAYS_FALSE match rule. In practice this meant that searches would only work for one search in an ldb request, and fail for subsequent ones. Fix this by creating a new request with the modified tree, and leaving the original request unmodified. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- .../dsdb/samdb/ldb_modules/extended_dn_in.c | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_in.c b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c index 2d0513a738b..1dc1e1f2d42 100644 --- a/source4/dsdb/samdb/ldb_modules/extended_dn_in.c +++ b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c @@ -48,6 +48,7 @@ struct extended_search_context { struct ldb_module *module; struct ldb_request *req; + struct ldb_parse_tree *tree; struct ldb_dn *basedn; struct ldb_dn *dn; char *wellknown_object; @@ -200,7 +201,7 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are ldb_module_get_ctx(ac->module), ac->req, ac->basedn, ac->req->op.search.scope, - ac->req->op.search.tree, + ac->tree, ac->req->op.search.attrs, ac->req->controls, ac, extended_final_callback, @@ -515,11 +516,14 @@ static int extended_dn_filter_callback(struct ldb_parse_tree *tree, void *privat */ static int extended_dn_fix_filter(struct ldb_module *module, struct ldb_request *req, - uint32_t default_dsdb_flags) + uint32_t default_dsdb_flags, + struct ldb_parse_tree **down_tree) { struct extended_dn_filter_ctx *filter_ctx; int ret; + *down_tree = NULL; + filter_ctx = talloc_zero(req, struct extended_dn_filter_ctx); if (filter_ctx == NULL) { return ldb_module_oom(module); @@ -550,12 +554,12 @@ static int extended_dn_fix_filter(struct ldb_module *module, filter_ctx->test_only = false; filter_ctx->matched = false; - req->op.search.tree = ldb_parse_tree_copy_shallow(req, req->op.search.tree); - if (req->op.search.tree == NULL) { + *down_tree = ldb_parse_tree_copy_shallow(req, req->op.search.tree); + if (*down_tree == NULL) { return ldb_oom(ldb_module_get_ctx(module)); } - ret = ldb_parse_tree_walk(req->op.search.tree, extended_dn_filter_callback, filter_ctx); + ret = ldb_parse_tree_walk(*down_tree, extended_dn_filter_callback, filter_ctx); if (ret != LDB_SUCCESS) { talloc_free(filter_ctx); return ret; @@ -572,7 +576,8 @@ static int extended_dn_fix_filter(struct ldb_module *module, static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn) { struct extended_search_context *ac; - struct ldb_request *down_req; + struct ldb_request *down_req = NULL; + struct ldb_parse_tree *down_tree = NULL; int ret; struct ldb_dn *base_dn = NULL; enum ldb_scope base_dn_scope = LDB_SCOPE_BASE; @@ -595,7 +600,7 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req } if (req->operation == LDB_SEARCH) { - ret = extended_dn_fix_filter(module, req, dsdb_flags); + ret = extended_dn_fix_filter(module, req, dsdb_flags, &down_tree); if (ret != LDB_SUCCESS) { return ret; } @@ -603,7 +608,25 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req if (!ldb_dn_has_extended(dn)) { /* Move along there isn't anything to see here */ - return ldb_next_request(module, req); + if (down_tree == NULL) { + down_req = req; + } else { + ret = ldb_build_search_req_ex(&down_req, + ldb_module_get_ctx(module), req, + req->op.search.base, + req->op.search.scope, + down_tree, + req->op.search.attrs, + req->controls, + req, dsdb_next_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + LDB_REQ_SET_LOCATION(down_req); + } + + return ldb_next_request(module, down_req); } else { /* It looks like we need to map the DN */ const struct ldb_val *sid_val, *guid_val, *wkguid_val; @@ -690,6 +713,7 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req ac->module = module; ac->req = req; + ac->tree = (down_tree != NULL) ? down_tree : req->op.search.tree; ac->dn = dn; ac->basedn = NULL; /* Filled in if the search finds the DN by SID/GUID etc */ ac->wellknown_object = wellknown_object; -- 2.25.1 From d9a20068a3dd9905763c5f5991eb8e555da94605 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 7 Feb 2023 09:48:37 +1300 Subject: [PATCH 18/41] CVE-2023-0614 s4:dsdb:tests: Fix search in confidential attributes test The object returned by schema_format_value() is a bytes object. Therefore the search expression would resemble: (lastKnownParent=) which, due to the extra characters, would fail to match anything. Fix it to be: (lastKnownParent=) BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/tests/python/confidential_attr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source4/dsdb/tests/python/confidential_attr.py b/source4/dsdb/tests/python/confidential_attr.py index d5c7785485a..1c9c456917a 100755 --- a/source4/dsdb/tests/python/confidential_attr.py +++ b/source4/dsdb/tests/python/confidential_attr.py @@ -924,12 +924,12 @@ class ConfidentialAttrTestDirsync(ConfidentialAttrCommon): self.assert_negative_searches(has_rights_to="all", samdb=self.ldb_admin) - def get_guid(self, dn): + def get_guid_string(self, dn): """Returns an object's GUID (in string format)""" res = self.ldb_admin.search(base=dn, attrs=["objectGUID"], scope=SCOPE_BASE) guid = res[0]['objectGUID'][0] - return self.ldb_admin.schema_format_value("objectGUID", guid) + return self.ldb_admin.schema_format_value("objectGUID", guid).decode('utf-8') def make_attr_preserve_on_delete(self): """Marks the attribute under test as being preserve on delete""" @@ -978,7 +978,7 @@ class ConfidentialAttrTestDirsync(ConfidentialAttrCommon): # deleted objects, but only from this particular test run. We can do # this by matching lastKnownParent against this test case's OU, which # will match any deleted child objects. - ou_guid = self.get_guid(self.ou) + ou_guid = self.get_guid_string(self.ou) deleted_filter = "(lastKnownParent=)".format(ou_guid) # the extra-filter will get combined via AND with the search expression -- 2.25.1 From 65249df5259d5f17d040ca92a1ac2585621e7c29 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 25 Aug 2022 20:15:33 +1200 Subject: [PATCH 19/41] schema_samba4.ldif: Allocate previously added OIDs DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID was added to source4/dsdb/samdb/samdb.h in commit c2ab1f4696fa3f52918a126d0b37993a07f68bcb. DSDB_EXTENDED_SCHEMA_LOAD was added in commit 1fd4cdfafaa6a41c824d1b3d76635bf3e446de0f. Signed-off-by: Joseph Sutton Reviewed-by: Douglas Bagnall Reviewed-by: Stefan Metzmacher (cherry picked from commit 672ec6135f9ae3d7b5439523a4f456c19fb03a88) BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 [abartlet@samba.org This required as context for the above bug] --- source4/setup/schema_samba4.ldif | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif index a31b67750d4..54f48bde20e 100644 --- a/source4/setup/schema_samba4.ldif +++ b/source4/setup/schema_samba4.ldif @@ -231,6 +231,7 @@ #Allocated: DSDB_CONTROL_INVALID_NOT_IMPLEMENTED 1.3.6.1.4.1.7165.4.3.32 #Allocated: DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID 1.3.6.1.4.1.7165.4.3.33 #Allocated: DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID 1.3.6.1.4.1.7165.4.3.34 +#Allocated: DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID 1.3.6.1.4.1.7165.4.3.35 # Extended 1.3.6.1.4.1.7165.4.4.x @@ -243,6 +244,7 @@ #Allocated: DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID 1.3.6.1.4.1.7165.4.4.7 #Allocated: DSDB_EXTENDED_CREATE_OWN_RID_SET 1.3.6.1.4.1.7165.4.4.8 #Allocated: DSDB_EXTENDED_ALLOCATE_RID 1.3.6.1.4.1.7165.4.4.9 +#Allocated: DSDB_EXTENDED_SCHEMA_LOAD 1.3.6.1.4.1.7165.4.4.10 ############ -- 2.25.1 From a45fc44c39c8d956c03ef4acaaceee6c3523556a Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 7 Feb 2023 09:25:48 +1300 Subject: [PATCH 20/41] CVE-2023-0614 schema_samba4.ldif: Allocate previously added OID DSDB_CONTROL_CALCULATED_DEFAULT_SD_OID was added in commit 08187833fee57a8dba6c67546dfca516cd1f9d7a. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/setup/schema_samba4.ldif | 1 + 1 file changed, 1 insertion(+) diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif index 54f48bde20e..79800bfd6df 100644 --- a/source4/setup/schema_samba4.ldif +++ b/source4/setup/schema_samba4.ldif @@ -232,6 +232,7 @@ #Allocated: DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID 1.3.6.1.4.1.7165.4.3.33 #Allocated: DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID 1.3.6.1.4.1.7165.4.3.34 #Allocated: DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID 1.3.6.1.4.1.7165.4.3.35 +#Allocated: DSDB_CONTROL_CALCULATED_DEFAULT_SD_OID 1.3.6.1.4.1.7165.4.3.36 # Extended 1.3.6.1.4.1.7165.4.4.x -- 2.25.1 From efd1cfab96ff439a712897b945fe20ac8358f2c4 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 27 Jan 2023 08:32:41 +1300 Subject: [PATCH 21/41] CVE-2023-0614 tests/krb5: Add test for confidential attributes timing differences BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/confidential-attr-timing | 1 + .../dsdb/tests/python/confidential_attr.py | 162 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 selftest/knownfail.d/confidential-attr-timing diff --git a/selftest/knownfail.d/confidential-attr-timing b/selftest/knownfail.d/confidential-attr-timing new file mode 100644 index 00000000000..e213cdb16d3 --- /dev/null +++ b/selftest/knownfail.d/confidential-attr-timing @@ -0,0 +1 @@ +^samba4.ldap.confidential_attr.python\(ad_dc_slowtests\).__main__.ConfidentialAttrTestDirsync.test_timing_attack\(ad_dc_slowtests\) diff --git a/source4/dsdb/tests/python/confidential_attr.py b/source4/dsdb/tests/python/confidential_attr.py index 1c9c456917a..031c9690ba6 100755 --- a/source4/dsdb/tests/python/confidential_attr.py +++ b/source4/dsdb/tests/python/confidential_attr.py @@ -25,6 +25,9 @@ sys.path.insert(0, "bin/python") import samba import os +import random +import statistics +import time from samba.tests.subunitrun import SubunitOptions, TestProgram import samba.getopt as options from ldb import SCOPE_BASE, SCOPE_SUBTREE @@ -1022,4 +1025,163 @@ class ConfidentialAttrTestDirsync(ConfidentialAttrCommon): self.assert_conf_attr_searches(has_rights_to=0) self.assert_negative_searches(has_rights_to=0, dc_mode=dc_mode) + def test_timing_attack(self): + # Create the machine account. + mach_name = f'conf_timing_{random.randint(0, 0xffff)}' + mach_dn = Dn(self.ldb_admin, f'CN={mach_name},{self.ou}') + details = { + 'dn': mach_dn, + 'objectclass': 'computer', + 'sAMAccountName': f'{mach_name}$', + } + self.ldb_admin.add(details) + + # Get the machine account's GUID. + res = self.ldb_admin.search(mach_dn, + attrs=['objectGUID'], + scope=SCOPE_BASE) + mach_guid = res[0].get('objectGUID', idx=0) + + # Now we can create an msFVE-RecoveryInformation object that is a child + # of the machine account object. + recovery_dn = Dn(self.ldb_admin, str(mach_dn)) + recovery_dn.add_child('CN=recovery_info') + + secret_pw = 'Secret007' + not_secret_pw = 'Secret008' + + secret_pw_utf8 = secret_pw.encode('utf-8') + + # The crucial attribute, msFVE-RecoveryPassword, is a confidential + # attribute. + conf_attr = 'msFVE-RecoveryPassword' + + m = Message(recovery_dn) + m['objectClass'] = 'msFVE-RecoveryInformation' + m['msFVE-RecoveryGuid'] = mach_guid + m[conf_attr] = secret_pw + self.ldb_admin.add(m) + + attrs = [conf_attr] + + # Search for the confidential attribute as administrator, ensuring it + # is visible. + res = self.ldb_admin.search(recovery_dn, + attrs=attrs, + scope=SCOPE_BASE) + self.assertEqual(1, len(res)) + pw = res[0].get(conf_attr, idx=0) + self.assertEqual(secret_pw_utf8, pw) + + # Repeat the search with an expression matching on the confidential + # attribute. This should also work. + res = self.ldb_admin.search( + recovery_dn, + attrs=attrs, + expression=f'({conf_attr}={secret_pw})', + scope=SCOPE_BASE) + self.assertEqual(1, len(res)) + pw = res[0].get(conf_attr, idx=0) + self.assertEqual(secret_pw_utf8, pw) + + # Search for the attribute as an unprivileged user. It should not be + # visible. + user_res = self.ldb_user.search(recovery_dn, + attrs=attrs, + scope=SCOPE_BASE) + pw = user_res[0].get(conf_attr, idx=0) + # The attribute should be None. + self.assertIsNone(pw) + + # We use LDAP_MATCHING_RULE_TRANSITIVE_EVAL to create a search + # expression that takes a long time to execute, by setting off another + # search each time it is evaluated. It makes no difference that the + # object on which we're searching has no 'member' attribute. + dummy_dn = 'cn=user,cn=users,dc=samba,dc=example,dc=com' + slow_subexpr = f'(member:1.2.840.113556.1.4.1941:={dummy_dn})' + slow_expr = f'(|{slow_subexpr * 100})' + + # The full search expression. It comprises a match on the confidential + # attribute joined by an AND to our slow search expression, The AND + # operator is short-circuiting, so if our first subexpression fails to + # match, we'll bail out of the search early. Otherwise, we'll evaluate + # the slow part; as its subexpressions are joined by ORs, and will all + # fail to match, every one of them will need to be evaluated. By + # measuring how long the search takes, we'll be able to infer whether + # the confidential attribute matched or not. + + # This is bad if we are not an administrator, and are able to use this + # to determine the values of confidential attributes. Therefore we need + # to ensure we can't observe any difference in timing. + correct_expr = f'(&({conf_attr}={secret_pw}){slow_expr})' + wrong_expr = f'(&({conf_attr}={not_secret_pw}){slow_expr})' + + def standard_uncertainty_bounds(times): + mean = statistics.mean(times) + stdev = statistics.stdev(times, mean) + + return (mean - stdev, mean + stdev) + + # Perform a number of searches with both correct and incorrect + # expressions, and return the uncertainty bounds for each. + def time_searches(samdb): + warmup_samples = 3 + samples = 10 + matching_times = [] + non_matching_times = [] + + for _ in range(warmup_samples): + samdb.search(recovery_dn, + attrs=attrs, + expression=correct_expr, + scope=SCOPE_BASE) + + for _ in range(samples): + # Measure the time taken for a search, for both a matching and + # a non-matching search expression. + + prev = time.time() + samdb.search(recovery_dn, + attrs=attrs, + expression=correct_expr, + scope=SCOPE_BASE) + now = time.time() + matching_times.append(now - prev) + + prev = time.time() + samdb.search(recovery_dn, + attrs=attrs, + expression=wrong_expr, + scope=SCOPE_BASE) + now = time.time() + non_matching_times.append(now - prev) + + matching = standard_uncertainty_bounds(matching_times) + non_matching = standard_uncertainty_bounds(non_matching_times) + return matching, non_matching + + def assertRangesDistinct(a, b): + a0, a1 = a + b0, b1 = b + self.assertLess(min(a1, b1), max(a0, b0)) + + def assertRangesOverlap(a, b): + a0, a1 = a + b0, b1 = b + self.assertGreaterEqual(min(a1, b1), max(a0, b0)) + + # For an administrator, the uncertainty bounds for matching and + # non-matching searches should be distinct. This shows that the two + # cases are distinguishable, and therefore that confidential attributes + # are visible. + admin_matching, admin_non_matching = time_searches(self.ldb_admin) + assertRangesDistinct(admin_matching, admin_non_matching) + + # The user cannot view the confidential attribute, so the uncertainty + # bounds for matching and non-matching searches must overlap. The two + # cases must be indistinguishable. + user_matching, user_non_matching = time_searches(self.ldb_user) + assertRangesOverlap(user_matching, user_non_matching) + + TestProgram(module=__name__, opts=subunitopts) -- 2.25.1 From 64604c41c19e03b6d7f4240894cb4c9ffd9b9406 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:31:54 +1300 Subject: [PATCH 22/41] CVE-2023-0614 ldb: Add ldb_parse_tree_get_attr() BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/common/ldb_parse.c | 25 +++++++++++++++++++++++++ lib/ldb/include/ldb_module.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/lib/ldb/common/ldb_parse.c b/lib/ldb/common/ldb_parse.c index f0045ad2093..2d102ff750e 100644 --- a/lib/ldb/common/ldb_parse.c +++ b/lib/ldb/common/ldb_parse.c @@ -997,3 +997,28 @@ struct ldb_parse_tree *ldb_parse_tree_copy_shallow(TALLOC_CTX *mem_ctx, return nt; } + +/* Get the attribute (if any) associated with the top node of a parse tree. */ +const char *ldb_parse_tree_get_attr(const struct ldb_parse_tree *tree) +{ + switch (tree->operation) { + case LDB_OP_AND: + case LDB_OP_OR: + case LDB_OP_NOT: + return NULL; + case LDB_OP_EQUALITY: + return tree->u.equality.attr; + case LDB_OP_SUBSTRING: + return tree->u.substring.attr; + case LDB_OP_GREATER: + case LDB_OP_LESS: + case LDB_OP_APPROX: + return tree->u.comparison.attr; + case LDB_OP_PRESENT: + return tree->u.present.attr; + case LDB_OP_EXTENDED: + return tree->u.extended.attr; + } + + return NULL; +} diff --git a/lib/ldb/include/ldb_module.h b/lib/ldb/include/ldb_module.h index 4ae381ba5be..bd369ed9512 100644 --- a/lib/ldb/include/ldb_module.h +++ b/lib/ldb/include/ldb_module.h @@ -490,6 +490,9 @@ int ldb_init_module(const char *version); */ bool ldb_dn_replace_components(struct ldb_dn *dn, struct ldb_dn *new_dn); +/* Get the attribute (if any) associated with the top node of a parse tree. */ +const char *ldb_parse_tree_get_attr(const struct ldb_parse_tree *tree); + /* walk a parse tree, calling the provided callback on each node */ -- 2.25.1 From bfab55ebb69ba1d03c7caee627978513f2825202 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 27 Feb 2023 13:40:33 +1300 Subject: [PATCH 23/41] CVE-2023-0614 s4-acl: Split out logic to remove access checking attributes BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/acl_read.c | 58 ++++++++++++++--------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index 8814a816797..3980c44345e 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -545,6 +545,39 @@ static int check_search_ops_access(struct aclread_context *ac, return ret; } +/* + * Whether this attribute was added to perform access checks and must be + * removed. + */ +static bool should_remove_attr(const char *attr, const struct aclread_context *ac) +{ + if (ac->added_nTSecurityDescriptor && + ldb_attr_cmp("nTSecurityDescriptor", attr) == 0) + { + return true; + } + + if (ac->added_objectSid && + ldb_attr_cmp("objectSid", attr) == 0) + { + return true; + } + + if (ac->added_instanceType && + ldb_attr_cmp("instanceType", attr) == 0) + { + return true; + } + + if (ac->added_objectClass && + ldb_attr_cmp("objectClass", attr) == 0) + { + return true; + } + + return false; +} + static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) { struct ldb_context *ldb; @@ -619,7 +652,6 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) /* for every element in the message check RP */ for (i=0; i < msg->num_elements; i++) { const struct dsdb_attribute *attr; - bool is_sd, is_objectsid, is_instancetype, is_objectclass; uint32_t access_mask; attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, msg->elements[i].name); @@ -631,28 +663,8 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) ret = LDB_ERR_OPERATIONS_ERROR; goto fail; } - is_sd = ldb_attr_cmp("nTSecurityDescriptor", - msg->elements[i].name) == 0; - is_objectsid = ldb_attr_cmp("objectSid", - msg->elements[i].name) == 0; - is_instancetype = ldb_attr_cmp("instanceType", - msg->elements[i].name) == 0; - is_objectclass = ldb_attr_cmp("objectClass", - msg->elements[i].name) == 0; - /* these attributes were added to perform access checks and must be removed */ - if (is_objectsid && ac->added_objectSid) { - ldb_msg_element_mark_inaccessible(&msg->elements[i]); - continue; - } - if (is_instancetype && ac->added_instanceType) { - ldb_msg_element_mark_inaccessible(&msg->elements[i]); - continue; - } - if (is_objectclass && ac->added_objectClass) { - ldb_msg_element_mark_inaccessible(&msg->elements[i]); - continue; - } - if (is_sd && ac->added_nTSecurityDescriptor) { + /* Remove attributes added to perform access checks. */ + if (should_remove_attr(msg->elements[i].name, ac)) { ldb_msg_element_mark_inaccessible(&msg->elements[i]); continue; } -- 2.25.1 From 1ef0183057348be265c986f1d212f512d08c59f0 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 27 Feb 2023 12:19:08 +1300 Subject: [PATCH 24/41] CVE-2023-0614 s4-dsdb: Add samdb_result_dom_sid_buf() This function parses a SID from an ldb_message, similar to samdb_result_dom_sid(), but does it without allocating anything. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/common/util.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index a30ae662c1e..b556f06cb63 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -365,6 +365,26 @@ struct dom_sid *samdb_result_dom_sid(TALLOC_CTX *mem_ctx, const struct ldb_messa return sid; } +/* + pull a dom_sid structure from a objectSid in a result set. +*/ +int samdb_result_dom_sid_buf(const struct ldb_message *msg, + const char *attr, + struct dom_sid *sid) +{ + ssize_t ret; + const struct ldb_val *v = NULL; + v = ldb_msg_find_ldb_val(msg, attr); + if (v == NULL) { + return LDB_ERR_NO_SUCH_ATTRIBUTE; + } + ret = sid_parse(v->data, v->length, sid); + if (ret == -1) { + return LDB_ERR_OPERATIONS_ERROR; + } + return LDB_SUCCESS; +} + /* pull a guid structure from a objectGUID in a result set. */ -- 2.25.1 From 2e3ed6cfd24cb5f4d75d248cca1eb791c6c44250 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 27 Feb 2023 13:55:36 +1300 Subject: [PATCH 25/41] CVE-2023-0614 s4-acl: Split out function to set up access checking variables These variables are often used together, and it is useful to have the setup code in one place. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org adapted to the use of acl_check_access_on_attribute as acl_check_access_on_attribute_implicit_owner is only in Samba 4.18 and newer] --- source4/dsdb/samdb/ldb_modules/acl_read.c | 113 +++++++++++++++------- 1 file changed, 80 insertions(+), 33 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index 3980c44345e..6659c71c965 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -70,6 +70,13 @@ struct aclread_private { struct ldb_val sd_cached_blob; }; +struct access_check_context { + struct security_descriptor *sd; + struct dom_sid sid_buf; + const struct dom_sid *sid; + const struct dsdb_class *objectclass; +}; + /* * the object has a parent, so we have to check for visibility * @@ -254,7 +261,7 @@ static int aclread_check_object_visible(struct aclread_context *ac, */ static int aclread_get_sd_from_ldb_message(struct aclread_context *ac, - struct ldb_message *acl_res, + const struct ldb_message *acl_res, struct security_descriptor **sd) { struct ldb_message_element *sd_element; @@ -358,7 +365,7 @@ static uint32_t get_attr_access_mask(const struct dsdb_attribute *attr, struct parse_tree_aclread_ctx { struct aclread_context *ac; TALLOC_CTX *mem_ctx; - struct dom_sid *sid; + const struct dom_sid *sid; struct ldb_dn *dn; struct security_descriptor *sd; const struct dsdb_class *objectclass; @@ -372,7 +379,7 @@ static int check_attr_access_rights(TALLOC_CTX *mem_ctx, const char *attr_name, struct aclread_context *ac, struct security_descriptor *sd, const struct dsdb_class *objectclass, - struct dom_sid *sid, struct ldb_dn *dn) + const struct dom_sid *sid, struct ldb_dn *dn) { int ret; const struct dsdb_attribute *attr = NULL; @@ -448,6 +455,69 @@ static const char * parse_tree_get_attr(struct ldb_parse_tree *tree) return attr; } +static int setup_access_check_context(struct aclread_context *ac, + const struct ldb_message *msg, + struct access_check_context *ctx) +{ + int ret; + + /* + * Fetch the schema so we can check which attributes are + * considered confidential. + */ + if (ac->schema == NULL) { + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + + /* Cache the schema for later use. */ + ac->schema = dsdb_get_schema(ldb, ac); + + if (ac->schema == NULL) { + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "aclread_callback: Error obtaining schema."); + } + } + + /* Fetch the object's security descriptor. */ + ret = aclread_get_sd_from_ldb_message(ac, msg, &ctx->sd); + if (ret != LDB_SUCCESS) { + ldb_debug_set(ldb_module_get_ctx(ac->module), LDB_DEBUG_FATAL, + "acl_read: cannot get descriptor of %s: %s\n", + ldb_dn_get_linearized(msg->dn), ldb_strerror(ret)); + return LDB_ERR_OPERATIONS_ERROR; + } else if (ctx->sd == NULL) { + ldb_debug_set(ldb_module_get_ctx(ac->module), LDB_DEBUG_FATAL, + "acl_read: cannot get descriptor of %s (attribute not found)\n", + ldb_dn_get_linearized(msg->dn)); + return LDB_ERR_OPERATIONS_ERROR; + } + /* + * Get the most specific structural object class for the ACL check + */ + ctx->objectclass = dsdb_get_structural_oc_from_msg(ac->schema, msg); + if (ctx->objectclass == NULL) { + ldb_asprintf_errstring(ldb_module_get_ctx(ac->module), + "acl_read: Failed to find a structural class for %s", + ldb_dn_get_linearized(msg->dn)); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Fetch the object's SID. */ + ret = samdb_result_dom_sid_buf(msg, "objectSid", &ctx->sid_buf); + if (ret == LDB_SUCCESS) { + ctx->sid = &ctx->sid_buf; + } else if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) { + /* This is expected. */ + ctx->sid = NULL; + } else { + ldb_asprintf_errstring(ldb_module_get_ctx(ac->module), + "acl_read: Failed to parse objectSid as dom_sid for %s", + ldb_dn_get_linearized(msg->dn)); + return ret; + } + + return LDB_SUCCESS; +} + /* * Checks a single attribute in the search parse-tree to make sure the user has * sufficient rights to view it. @@ -522,7 +592,7 @@ static int check_search_ops_access(struct aclread_context *ac, TALLOC_CTX *mem_ctx, struct security_descriptor *sd, const struct dsdb_class *objectclass, - struct dom_sid *sid, struct ldb_dn *dn, + const struct dom_sid *sid, struct ldb_dn *dn, bool *suppress_result) { int ret; @@ -585,10 +655,8 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) struct ldb_message *msg; int ret; unsigned int i; - struct security_descriptor *sd = NULL; - struct dom_sid *sid = NULL; + struct access_check_context acl_ctx; TALLOC_CTX *tmp_ctx; - const struct dsdb_class *objectclass; bool suppress_result = false; ac = talloc_get_type_abort(req->context, struct aclread_context); @@ -604,32 +672,11 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) switch (ares->type) { case LDB_REPLY_ENTRY: msg = ares->message; - ret = aclread_get_sd_from_ldb_message(ac, msg, &sd); + ret = setup_access_check_context(ac, msg, &acl_ctx); if (ret != LDB_SUCCESS) { - ldb_debug_set(ldb, LDB_DEBUG_FATAL, - "acl_read: cannot get descriptor of %s: %s\n", - ldb_dn_get_linearized(msg->dn), ldb_strerror(ret)); - ret = LDB_ERR_OPERATIONS_ERROR; - goto fail; - } else if (sd == NULL) { - ldb_debug_set(ldb, LDB_DEBUG_FATAL, - "acl_read: cannot get descriptor of %s (attribute not found)\n", - ldb_dn_get_linearized(msg->dn)); - ret = LDB_ERR_OPERATIONS_ERROR; - goto fail; - } - /* - * Get the most specific structural object class for the ACL check - */ - objectclass = dsdb_get_structural_oc_from_msg(ac->schema, msg); - if (objectclass == NULL) { - ldb_asprintf_errstring(ldb, "acl_read: Failed to find a structural class for %s", - ldb_dn_get_linearized(msg->dn)); - ret = LDB_ERR_OPERATIONS_ERROR; - goto fail; + return ret; } - sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid"); if (!ldb_dn_is_null(msg->dn)) { /* * this is a real object, so we have @@ -678,8 +725,8 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) ret = acl_check_access_on_attribute(ac->module, tmp_ctx, - sd, - sid, + acl_ctx.sd, + acl_ctx.sid, access_mask, attr, objectclass); @@ -733,7 +780,7 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) * check access rights for the search attributes, as well as the * attribute values actually being returned */ - ret = check_search_ops_access(ac, tmp_ctx, sd, objectclass, sid, + ret = check_search_ops_access(ac, tmp_ctx, acl_ctx.sd, acl_ctx.objectclass, acl_ctx.sid, msg->dn, &suppress_result); if (ret != LDB_SUCCESS) { ldb_debug_set(ldb, LDB_DEBUG_FATAL, -- 2.25.1 From c1921f5ae0840c455ad18b2fa19839242bd8a3e8 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:34:29 +1300 Subject: [PATCH 26/41] CVE-2023-0614 ldb: Prevent disclosure of confidential attributes Add a hook, acl_redact_msg_for_filter(), in the aclread module, that marks inaccessible any message elements used by an LDAP search filter that the user has no right to access. Make the various ldb_match_*() functions check whether message elements are accessible, and refuse to match any that are not. Remaining message elements, not mentioned in the search filter, are checked in aclread_callback(), and any inaccessible elements are removed at this point. Certain attributes, namely objectClass, distinguishedName, name, and objectGUID, are always present, and hence the presence of said attributes is always allowed to be checked in a search filter. This corresponds with the behaviour of Windows. Further, we unconditionally allow the attributes isDeleted and isRecycled in a check for presence or equality. Windows is not known to make this special exception, but it seems mostly harmless, and should mitigate the performance impact on searches made by the show_deleted module. As a result of all these changes, our behaviour regarding confidential attributes happens to match Windows more closely. For the test in confidential_attr.py, we can now model our attribute handling with DC_MODE_RETURN_ALL, which corresponds to the behaviour exhibited by Windows. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Pair-Programmed-With: Andrew Bartlett Signed-off-by: Joseph Sutton Signed-off-by: Andrew Bartlett Reviewed-by: Andrew Bartlett [abartlet@samba.org adapted due to Samba 4.17 and lower not having the patches for CVE-2020-25720] --- lib/ldb-samba/ldb_matching_rules.c | 15 + lib/ldb/common/ldb_match.c | 37 + lib/ldb/include/ldb_module.h | 11 + lib/ldb/include/ldb_private.h | 5 + lib/ldb/ldb_key_value/ldb_kv_index.c | 8 + lib/ldb/ldb_key_value/ldb_kv_search.c | 15 + selftest/knownfail.d/confidential-attr-timing | 1 - source4/dsdb/samdb/ldb_modules/acl.c | 183 +--- source4/dsdb/samdb/ldb_modules/acl_read.c | 837 ++++++++++++------ source4/dsdb/samdb/samdb.h | 2 + .../dsdb/tests/python/confidential_attr.py | 12 +- source4/setup/schema_samba4.ldif | 1 + 12 files changed, 672 insertions(+), 455 deletions(-) delete mode 100644 selftest/knownfail.d/confidential-attr-timing diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c index 827f3920ae8..0c4c31e49f9 100644 --- a/lib/ldb-samba/ldb_matching_rules.c +++ b/lib/ldb-samba/ldb_matching_rules.c @@ -87,6 +87,11 @@ static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + /* * If the value to match is present in the attribute values of the * current entry being visited, set matched to true and return OK @@ -370,6 +375,11 @@ static int dsdb_match_for_dns_to_tombstone_time(struct ldb_context *ldb, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"), struct auth_session_info); if (session_info == NULL) { @@ -489,6 +499,11 @@ static int dsdb_match_for_expunge(struct ldb_context *ldb, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + session_info = talloc_get_type(ldb_get_opaque(ldb, DSDB_SESSION_INFO), struct auth_session_info); diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c index 51376871b4c..b0a33e939eb 100644 --- a/lib/ldb/common/ldb_match.c +++ b/lib/ldb/common/ldb_match.c @@ -99,6 +99,11 @@ static int ldb_match_present(struct ldb_context *ldb, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + a = ldb_schema_attribute_by_name(ldb, el->name); if (!a) { return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; @@ -140,6 +145,11 @@ static int ldb_match_comparison(struct ldb_context *ldb, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + a = ldb_schema_attribute_by_name(ldb, el->name); if (!a) { return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; @@ -210,6 +220,11 @@ static int ldb_match_equality(struct ldb_context *ldb, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + a = ldb_schema_attribute_by_name(ldb, el->name); if (a == NULL) { return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; @@ -410,6 +425,11 @@ static int ldb_match_substring(struct ldb_context *ldb, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + for (i = 0; i < el->num_values; i++) { int ret; ret = ldb_wildcard_compare(ldb, tree, el->values[i], matched); @@ -483,6 +503,11 @@ static int ldb_match_bitmask(struct ldb_context *ldb, return LDB_SUCCESS; } + if (ldb_msg_element_is_inaccessible(el)) { + *matched = false; + return LDB_SUCCESS; + } + for (i=0;inum_values;i++) { int ret; struct ldb_val *v = &el->values[i]; @@ -781,3 +806,15 @@ int ldb_register_extended_match_rule(struct ldb_context *ldb, return LDB_SUCCESS; } +int ldb_register_redact_callback(struct ldb_context *ldb, + ldb_redact_fn redact_fn, + struct ldb_module *module) +{ + if (ldb->redact.callback != NULL) { + return LDB_ERR_ENTRY_ALREADY_EXISTS; + } + + ldb->redact.callback = redact_fn; + ldb->redact.module = module; + return LDB_SUCCESS; +} diff --git a/lib/ldb/include/ldb_module.h b/lib/ldb/include/ldb_module.h index bd369ed9512..0f14b1ad346 100644 --- a/lib/ldb/include/ldb_module.h +++ b/lib/ldb/include/ldb_module.h @@ -102,6 +102,12 @@ struct ldb_module; */ #define LDB_FLAG_INTERNAL_SHARED_VALUES 0x200 +/* + * this attribute has been access checked. We know the user has the right to + * view it. Used internally in Samba aclread module. + */ +#define LDB_FLAG_INTERNAL_ACCESS_CHECKED 0x400 + /* an extended match rule that always fails to match */ #define SAMBA_LDAP_MATCH_ALWAYS_FALSE "1.3.6.1.4.1.7165.4.5.1" @@ -520,6 +526,11 @@ void ldb_msg_element_mark_inaccessible(struct ldb_message_element *el); bool ldb_msg_element_is_inaccessible(const struct ldb_message_element *el); void ldb_msg_remove_inaccessible(struct ldb_message *msg); +typedef int (*ldb_redact_fn)(struct ldb_module *, struct ldb_request *, struct ldb_message *); +int ldb_register_redact_callback(struct ldb_context *ldb, + ldb_redact_fn redact_fn, + struct ldb_module *module); + /* * these pack/unpack functions are exposed in the library for use by * ldb tools like ldbdump and for use in tests, diff --git a/lib/ldb/include/ldb_private.h b/lib/ldb/include/ldb_private.h index ca43817d07a..b0a42f6421c 100644 --- a/lib/ldb/include/ldb_private.h +++ b/lib/ldb/include/ldb_private.h @@ -119,6 +119,11 @@ struct ldb_context { struct ldb_extended_match_entry *prev, *next; } *extended_match_rules; + struct { + struct ldb_module *module; + ldb_redact_fn callback; + } redact; + /* custom utf8 functions */ struct ldb_utf8_fns utf8_fns; diff --git a/lib/ldb/ldb_key_value/ldb_kv_index.c b/lib/ldb/ldb_key_value/ldb_kv_index.c index 203266ea8c9..163052fecf7 100644 --- a/lib/ldb/ldb_key_value/ldb_kv_index.c +++ b/lib/ldb/ldb_key_value/ldb_kv_index.c @@ -2428,6 +2428,14 @@ static int ldb_kv_index_filter(struct ldb_kv_private *ldb_kv, return LDB_ERR_OPERATIONS_ERROR; } + if (ldb->redact.callback != NULL) { + ret = ldb->redact.callback(ldb->redact.module, ac->req, msg); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return ret; + } + } + /* * We trust the index for LDB_SCOPE_ONELEVEL * unless the index key has been truncated. diff --git a/lib/ldb/ldb_key_value/ldb_kv_search.c b/lib/ldb/ldb_key_value/ldb_kv_search.c index f3333510eab..d187ba223e1 100644 --- a/lib/ldb/ldb_key_value/ldb_kv_search.c +++ b/lib/ldb/ldb_key_value/ldb_kv_search.c @@ -395,6 +395,14 @@ static int search_func(_UNUSED_ struct ldb_kv_private *ldb_kv, } } + if (ldb->redact.callback != NULL) { + ret = ldb->redact.callback(ldb->redact.module, ac->req, msg); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return ret; + } + } + /* see if it matches the given expression */ ret = ldb_match_msg_error(ldb, msg, ac->tree, ac->base, ac->scope, &matched); @@ -530,6 +538,13 @@ static int ldb_kv_search_and_return_base(struct ldb_kv_private *ldb_kv, return ret; } + if (ldb->redact.callback != NULL) { + ret = ldb->redact.callback(ldb->redact.module, ctx->req, msg); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return ret; + } + } /* * We use this, not ldb_match_msg_error() as we know diff --git a/selftest/knownfail.d/confidential-attr-timing b/selftest/knownfail.d/confidential-attr-timing deleted file mode 100644 index e213cdb16d3..00000000000 --- a/selftest/knownfail.d/confidential-attr-timing +++ /dev/null @@ -1 +0,0 @@ -^samba4.ldap.confidential_attr.python\(ad_dc_slowtests\).__main__.ConfidentialAttrTestDirsync.test_timing_attack\(ad_dc_slowtests\) diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index 4098ae2d671..5c57dd25faa 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -46,11 +46,6 @@ #undef strcasecmp #undef strncasecmp -struct extended_access_check_attribute { - const char *oa_name; - const uint32_t requires_rights; -}; - struct acl_private { bool acl_search; const char **password_attrs; @@ -58,7 +53,6 @@ struct acl_private { uint64_t cached_schema_metadata_usn; uint64_t cached_schema_loaded_usn; const char **confidential_attrs; - bool userPassword_support; }; struct acl_context { @@ -66,15 +60,12 @@ struct acl_context { struct ldb_request *req; bool am_system; bool am_administrator; - bool modify_search; bool constructed_attrs; bool allowedAttributes; bool allowedAttributesEffective; bool allowedChildClasses; bool allowedChildClassesEffective; bool sDRightsEffective; - bool userPassword; - const char * const *attrs; struct dsdb_schema *schema; }; @@ -83,25 +74,9 @@ static int acl_module_init(struct ldb_module *module) struct ldb_context *ldb; struct acl_private *data; int ret; - unsigned int i, n, j; - TALLOC_CTX *mem_ctx; - static const char * const attrs[] = { "passwordAttribute", NULL }; - static const char * const secret_attrs[] = { - DSDB_SECRET_ATTRIBUTES - }; - struct ldb_result *res; - struct ldb_message *msg; - struct ldb_message_element *password_attributes; ldb = ldb_module_get_ctx(module); - ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID); - if (ret != LDB_SUCCESS) { - ldb_debug(ldb, LDB_DEBUG_ERROR, - "acl_module_init: Unable to register control with rootdse!\n"); - return ldb_operr(ldb); - } - data = talloc_zero(module, struct acl_private); if (data == NULL) { return ldb_oom(ldb); @@ -111,91 +86,14 @@ static int acl_module_init(struct ldb_module *module) NULL, "acl", "search", true); ldb_module_set_private(module, data); - mem_ctx = talloc_new(module); - if (!mem_ctx) { - return ldb_oom(ldb); - } - - ret = dsdb_module_search_dn(module, mem_ctx, &res, - ldb_dn_new(mem_ctx, ldb, "@KLUDGEACL"), - attrs, - DSDB_FLAG_NEXT_MODULE | - DSDB_FLAG_AS_SYSTEM, - NULL); - if (ret != LDB_SUCCESS) { - goto done; - } - if (res->count == 0) { - goto done; - } - - if (res->count > 1) { - talloc_free(mem_ctx); - return LDB_ERR_CONSTRAINT_VIOLATION; - } - - msg = res->msgs[0]; - - password_attributes = ldb_msg_find_element(msg, "passwordAttribute"); - if (!password_attributes) { - goto done; - } - data->password_attrs = talloc_array(data, const char *, - password_attributes->num_values + - ARRAY_SIZE(secret_attrs) + 1); - if (!data->password_attrs) { - talloc_free(mem_ctx); - return ldb_oom(ldb); - } - - n = 0; - for (i=0; i < password_attributes->num_values; i++) { - data->password_attrs[n] = (const char *)password_attributes->values[i].data; - talloc_steal(data->password_attrs, password_attributes->values[i].data); - n++; - } - - for (i=0; i < ARRAY_SIZE(secret_attrs); i++) { - bool found = false; - - for (j=0; j < n; j++) { - if (strcasecmp(data->password_attrs[j], secret_attrs[i]) == 0) { - found = true; - break; - } - } - - if (found) { - continue; - } - - data->password_attrs[n] = talloc_strdup(data->password_attrs, - secret_attrs[i]); - if (data->password_attrs[n] == NULL) { - talloc_free(mem_ctx); - return ldb_oom(ldb); - } - n++; - } - data->password_attrs[n] = NULL; - -done: - talloc_free(mem_ctx); - ret = ldb_next_init(module); - + ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID); if (ret != LDB_SUCCESS) { - return ret; + ldb_debug(ldb, LDB_DEBUG_ERROR, + "acl_module_init: Unable to register control with rootdse!\n"); + return ldb_operr(ldb); } - /* - * Check this after the modules have be initialised so we - * can actually read the backend DB. - */ - data->userPassword_support - = dsdb_user_password_support(module, - module, - NULL); - return ret; + return ldb_next_init(module); } static int acl_allowedAttributes(struct ldb_module *module, @@ -2522,29 +2420,11 @@ static int acl_search_callback(struct ldb_request *req, struct ldb_reply *ares) ares->controls); } - if (data->password_attrs != NULL) { - for (i = 0; data->password_attrs[i]; i++) { - if ((!ac->userPassword) && - (ldb_attr_cmp(data->password_attrs[i], - "userPassword") == 0)) - { - continue; - } - - ldb_msg_remove_attr(ares->message, data->password_attrs[i]); - } - } - if (ac->am_administrator) { return ldb_module_send_entry(ac->req, ares->message, ares->controls); } - ret = acl_search_update_confidential_attrs(ac, data); - if (ret != LDB_SUCCESS) { - return ret; - } - if (data->confidential_attrs != NULL) { for (i = 0; data->confidential_attrs[i]; i++) { ldb_msg_remove_attr(ares->message, @@ -2569,11 +2449,12 @@ static int acl_search(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct acl_context *ac; - struct ldb_parse_tree *down_tree; + struct ldb_parse_tree *down_tree = req->op.search.tree; struct ldb_request *down_req; struct acl_private *data; int ret; unsigned int i; + bool modify_search = true; if (ldb_dn_is_special(req->op.search.base)) { return ldb_next_request(module, req); @@ -2592,13 +2473,11 @@ static int acl_search(struct ldb_module *module, struct ldb_request *req) ac->am_system = dsdb_module_am_system(module); ac->am_administrator = dsdb_module_am_administrator(module); ac->constructed_attrs = false; - ac->modify_search = true; ac->allowedAttributes = ldb_attr_in_list(req->op.search.attrs, "allowedAttributes"); ac->allowedAttributesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedAttributesEffective"); ac->allowedChildClasses = ldb_attr_in_list(req->op.search.attrs, "allowedChildClasses"); ac->allowedChildClassesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedChildClassesEffective"); ac->sDRightsEffective = ldb_attr_in_list(req->op.search.attrs, "sDRightsEffective"); - ac->userPassword = true; ac->schema = dsdb_get_schema(ldb, ac); ac->constructed_attrs |= ac->allowedAttributes; @@ -2608,13 +2487,13 @@ static int acl_search(struct ldb_module *module, struct ldb_request *req) ac->constructed_attrs |= ac->sDRightsEffective; if (data == NULL) { - ac->modify_search = false; + modify_search = false; } if (ac->am_system) { - ac->modify_search = false; + modify_search = false; } - if (!ac->constructed_attrs && !ac->modify_search) { + if (!ac->constructed_attrs && !modify_search) { talloc_free(ac); return ldb_next_request(module, req); } @@ -2624,38 +2503,24 @@ static int acl_search(struct ldb_module *module, struct ldb_request *req) return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, "acl_private data is missing"); } - ac->userPassword = data->userPassword_support; - - ret = acl_search_update_confidential_attrs(ac, data); - if (ret != LDB_SUCCESS) { - return ret; - } - down_tree = ldb_parse_tree_copy_shallow(ac, req->op.search.tree); - if (down_tree == NULL) { - return ldb_oom(ldb); - } + if (!ac->am_system && !ac->am_administrator) { + ret = acl_search_update_confidential_attrs(ac, data); + if (ret != LDB_SUCCESS) { + return ret; + } - if (!ac->am_system && data->password_attrs) { - for (i = 0; data->password_attrs[i]; i++) { - if ((!ac->userPassword) && - (ldb_attr_cmp(data->password_attrs[i], - "userPassword") == 0)) - { - continue; + if (data->confidential_attrs != NULL) { + down_tree = ldb_parse_tree_copy_shallow(ac, req->op.search.tree); + if (down_tree == NULL) { + return ldb_oom(ldb); } - ldb_parse_tree_attr_replace(down_tree, - data->password_attrs[i], - "kludgeACLredactedattribute"); - } - } - - if (!ac->am_system && !ac->am_administrator && data->confidential_attrs) { - for (i = 0; data->confidential_attrs[i]; i++) { - ldb_parse_tree_attr_replace(down_tree, - data->confidential_attrs[i], - "kludgeACLredactedattribute"); + for (i = 0; data->confidential_attrs[i]; i++) { + ldb_parse_tree_attr_replace(down_tree, + data->confidential_attrs[i], + "kludgeACLredactedattribute"); + } } } diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index 6659c71c965..8ca8607b925 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -37,20 +37,25 @@ #include "librpc/gen_ndr/ndr_security.h" #include "param/param.h" #include "dsdb/samdb/ldb_modules/util.h" +#include "lib/util/binsearch.h" #undef strcasecmp +struct ldb_attr_vec { + const char** attrs; + size_t len; + size_t capacity; +}; + struct aclread_context { struct ldb_module *module; struct ldb_request *req; - const char * const *attrs; const struct dsdb_schema *schema; uint32_t sd_flags; bool added_nTSecurityDescriptor; bool added_instanceType; bool added_objectSid; bool added_objectClass; - bool indirsync; bool do_list_object_initialized; bool do_list_object; @@ -60,6 +65,11 @@ struct aclread_context { /* cache on the last parent we checked in this search */ struct ldb_dn *last_parent_dn; int last_parent_check_ret; + + bool am_administrator; + + bool got_tree_attrs; + struct ldb_attr_vec tree_attrs; }; struct aclread_private { @@ -68,6 +78,7 @@ struct aclread_private { /* cache of the last SD we read during any search */ struct security_descriptor *sd_cached; struct ldb_val sd_cached_blob; + const char **password_attrs; }; struct access_check_context { @@ -77,6 +88,183 @@ struct access_check_context { const struct dsdb_class *objectclass; }; +static void acl_element_mark_access_checked(struct ldb_message_element *el) +{ + el->flags |= LDB_FLAG_INTERNAL_ACCESS_CHECKED; +} + +static bool acl_element_is_access_checked(const struct ldb_message_element *el) +{ + return (el->flags & LDB_FLAG_INTERNAL_ACCESS_CHECKED) != 0; +} + +static bool attr_in_vec(const struct ldb_attr_vec *vec, const char *attr) +{ + const char **found = NULL; + + if (vec == NULL) { + return false; + } + + BINARY_ARRAY_SEARCH_V(vec->attrs, + vec->len, + attr, + ldb_attr_cmp, + found); + return found != NULL; +} + +static int acl_attr_cmp_fn(const char *a, const char **b) +{ + return ldb_attr_cmp(a, *b); +} + +static int attr_vec_add_unique(TALLOC_CTX *mem_ctx, + struct ldb_attr_vec *vec, + const char *attr) +{ + const char **exact = NULL; + const char **next = NULL; + size_t next_idx = 0; + + BINARY_ARRAY_SEARCH_GTE(vec->attrs, + vec->len, + attr, + acl_attr_cmp_fn, + exact, + next); + if (exact != NULL) { + return LDB_SUCCESS; + } + + if (vec->len == SIZE_MAX) { + return LDB_ERR_OPERATIONS_ERROR; + } + + if (next != NULL) { + next_idx = next - vec->attrs; + } + + if (vec->len >= vec->capacity) { + const char **attrs = NULL; + + if (vec->capacity == 0) { + vec->capacity = 4; + } else { + if (vec->capacity > SIZE_MAX / 2) { + return LDB_ERR_OPERATIONS_ERROR; + } + vec->capacity *= 2; + } + + attrs = talloc_realloc(mem_ctx, vec->attrs, const char *, vec->capacity); + if (attrs == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + vec->attrs = attrs; + } + SMB_ASSERT(vec->len < vec->capacity); + + if (next == NULL) { + vec->attrs[vec->len++] = attr; + } else { + size_t count = (vec->len - next_idx) * sizeof (vec->attrs[0]); + memmove(&vec->attrs[next_idx + 1], + &vec->attrs[next_idx], + count); + + vec->attrs[next_idx] = attr; + ++vec->len; + } + + return LDB_SUCCESS; +} + +static bool ldb_attr_always_present(const char *attr) +{ + static const char * const attrs_always_present[] = { + "objectClass", + "distinguishedName", + "name", + "objectGUID", + NULL + }; + + return ldb_attr_in_list(attrs_always_present, attr); +} + +static bool ldb_attr_always_visible(const char *attr) +{ + static const char * const attrs_always_visible[] = { + "isDeleted", + "isRecycled", + NULL + }; + + return ldb_attr_in_list(attrs_always_visible, attr); +} + +/* Collect a list of attributes required to match a given parse tree. */ +static int ldb_parse_tree_collect_acl_attrs(struct ldb_module *module, + TALLOC_CTX *mem_ctx, + struct ldb_attr_vec *attrs, + const struct ldb_parse_tree *tree) +{ + const char *attr = NULL; + unsigned int i; + int ret; + + if (tree == NULL) { + return 0; + } + + switch (tree->operation) { + case LDB_OP_OR: + case LDB_OP_AND: /* attributes stored in list of subtrees */ + for (i = 0; i < tree->u.list.num_elements; i++) { + ret = ldb_parse_tree_collect_acl_attrs(module, mem_ctx, + attrs, tree->u.list.elements[i]); + if (ret) { + return ret; + } + } + return 0; + + case LDB_OP_NOT: /* attributes stored in single subtree */ + return ldb_parse_tree_collect_acl_attrs(module, mem_ctx, attrs, tree->u.isnot.child); + + case LDB_OP_PRESENT: + /* + * If the search filter is checking for an attribute's presence, + * and the attribute is always present, we can skip access + * rights checks. Every object has these attributes, and so + * there's no security reason to hide their presence. + * Note: the acl.py tests (e.g. test_search1()) rely on this + * exception. I.e. even if we lack Read Property (RP) rights + * for a child object, it should still appear as a visible + * object in 'objectClass=*' searches, so long as we have List + * Contents (LC) rights for the object. + */ + if (ldb_attr_always_present(tree->u.present.attr)) { + /* No need to check this attribute. */ + return 0; + } + + FALL_THROUGH; + case LDB_OP_EQUALITY: + if (ldb_attr_always_visible(tree->u.present.attr)) { + /* No need to check this attribute. */ + return 0; + } + + FALL_THROUGH; + default: /* single attribute in tree */ + attr = ldb_parse_tree_get_attr(tree); + return attr_vec_add_unique(mem_ctx, attrs, attr); + } +} + /* * the object has a parent, so we have to check for visibility * @@ -308,16 +496,11 @@ static int aclread_get_sd_from_ldb_message(struct aclread_context *ac, } talloc_unlink(private_data, private_data->sd_cached_blob.data); - if (ac->added_nTSecurityDescriptor) { - private_data->sd_cached_blob = sd_element->values[0]; - talloc_steal(private_data, sd_element->values[0].data); - } else { - private_data->sd_cached_blob = ldb_val_dup(private_data, - &sd_element->values[0]); - if (private_data->sd_cached_blob.data == NULL) { - TALLOC_FREE(*sd); - return ldb_operr(ldb); - } + private_data->sd_cached_blob = ldb_val_dup(private_data, + &sd_element->values[0]); + if (private_data->sd_cached_blob.data == NULL) { + TALLOC_FREE(*sd); + return ldb_operr(ldb); } talloc_unlink(private_data, private_data->sd_cached); @@ -326,6 +509,27 @@ static int aclread_get_sd_from_ldb_message(struct aclread_context *ac, return LDB_SUCCESS; } +/* Check whether the attribute is a password attribute. */ +static bool attr_is_secret(const char *attr, const struct aclread_private *private_data) +{ + unsigned i; + + if (private_data->password_attrs == NULL) { + return false; + } + + for (i = 0; private_data->password_attrs[i] != NULL; ++i) { + const char *password_attr = private_data->password_attrs[i]; + if (ldb_attr_cmp(attr, password_attr) != 0) { + continue; + } + + return true; + } + + return false; +} + /* * Returns the access mask required to read a given attribute */ @@ -361,61 +565,59 @@ static uint32_t get_attr_access_mask(const struct dsdb_attribute *attr, return access_mask; } -/* helper struct for traversing the attributes in the search-tree */ -struct parse_tree_aclread_ctx { - struct aclread_context *ac; - TALLOC_CTX *mem_ctx; - const struct dom_sid *sid; - struct ldb_dn *dn; - struct security_descriptor *sd; - const struct dsdb_class *objectclass; - bool suppress_result; -}; - /* - * Checks that the user has sufficient access rights to view an attribute + * Checks that the user has sufficient access rights to view an attribute, else + * marks it as inaccessible. */ -static int check_attr_access_rights(TALLOC_CTX *mem_ctx, const char *attr_name, - struct aclread_context *ac, - struct security_descriptor *sd, - const struct dsdb_class *objectclass, - const struct dom_sid *sid, struct ldb_dn *dn) +static int acl_redact_attr(TALLOC_CTX *mem_ctx, + struct ldb_message_element *el, + struct aclread_context *ac, + const struct aclread_private *private_data, + const struct ldb_message *msg, + const struct dsdb_schema *schema, + const struct security_descriptor *sd, + const struct dom_sid *sid, + const struct dsdb_class *objectclass) { int ret; const struct dsdb_attribute *attr = NULL; uint32_t access_mask; struct ldb_context *ldb = ldb_module_get_ctx(ac->module); - attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, attr_name); + if (attr_is_secret(el->name, private_data)) { + ldb_msg_element_mark_inaccessible(el); + return LDB_SUCCESS; + } + + /* Look up the attribute in the schema. */ + attr = dsdb_attribute_by_lDAPDisplayName(schema, el->name); if (!attr) { ldb_debug_set(ldb, - LDB_DEBUG_TRACE, - "acl_read: %s cannot find attr[%s] in schema," - "ignoring\n", - ldb_dn_get_linearized(dn), attr_name); - return LDB_SUCCESS; + LDB_DEBUG_FATAL, + "acl_read: %s cannot find attr[%s] in schema\n", + ldb_dn_get_linearized(msg->dn), el->name); + return LDB_ERR_OPERATIONS_ERROR; } access_mask = get_attr_access_mask(attr, ac->sd_flags); - - /* the access-mask should be non-zero. Skip attribute otherwise */ if (access_mask == 0) { DBG_ERR("Could not determine access mask for attribute %s\n", - attr_name); + el->name); + ldb_msg_element_mark_inaccessible(el); return LDB_SUCCESS; } + /* We must check whether the user has rights to view the attribute. */ + ret = acl_check_access_on_attribute(ac->module, mem_ctx, sd, sid, access_mask, attr, objectclass); if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { - return ret; - } - - if (ret != LDB_SUCCESS) { + ldb_msg_element_mark_inaccessible(el); + } else if (ret != LDB_SUCCESS) { ldb_debug_set(ldb, LDB_DEBUG_FATAL, "acl_read: %s check attr[%s] gives %s - %s\n", - ldb_dn_get_linearized(dn), attr_name, + ldb_dn_get_linearized(msg->dn), el->name, ldb_strerror(ret), ldb_errstring(ldb)); return ret; } @@ -423,38 +625,6 @@ static int check_attr_access_rights(TALLOC_CTX *mem_ctx, const char *attr_name, return LDB_SUCCESS; } -/* - * Returns the attribute name for this particular level of a search operation - * parse-tree. - */ -static const char * parse_tree_get_attr(struct ldb_parse_tree *tree) -{ - const char *attr = NULL; - - switch (tree->operation) { - case LDB_OP_EQUALITY: - case LDB_OP_GREATER: - case LDB_OP_LESS: - case LDB_OP_APPROX: - attr = tree->u.equality.attr; - break; - case LDB_OP_SUBSTRING: - attr = tree->u.substring.attr; - break; - case LDB_OP_PRESENT: - attr = tree->u.present.attr; - break; - case LDB_OP_EXTENDED: - attr = tree->u.extended.attr; - break; - - /* we'll check LDB_OP_AND/_OR/_NOT children later on in the walk */ - default: - break; - } - return attr; -} - static int setup_access_check_context(struct aclread_context *ac, const struct ldb_message *msg, struct access_check_context *ctx) @@ -518,103 +688,6 @@ static int setup_access_check_context(struct aclread_context *ac, return LDB_SUCCESS; } -/* - * Checks a single attribute in the search parse-tree to make sure the user has - * sufficient rights to view it. - */ -static int parse_tree_check_attr_access(struct ldb_parse_tree *tree, - void *private_context) -{ - struct parse_tree_aclread_ctx *ctx = NULL; - const char *attr_name = NULL; - int ret; - static const char * const attrs_always_present[] = { - "objectClass", - "distinguishedName", - "name", - "objectGUID", - NULL - }; - - ctx = (struct parse_tree_aclread_ctx *)private_context; - - /* - * we can skip any further checking if we already know that this object - * shouldn't be visible in this user's search - */ - if (ctx->suppress_result) { - return LDB_SUCCESS; - } - - /* skip this level of the search-tree if it has no attribute to check */ - attr_name = parse_tree_get_attr(tree); - if (attr_name == NULL) { - return LDB_SUCCESS; - } - - /* - * If the search filter is checking for an attribute's presence, and the - * attribute is always present, we can skip access rights checks. Every - * object has these attributes, and so there's no security reason to - * hide their presence. - * Note: the acl.py tests (e.g. test_search1()) rely on this exception. - * I.e. even if we lack Read Property (RP) rights for a child object, it - * should still appear as a visible object in 'objectClass=*' searches, - * so long as we have List Contents (LC) rights for the object. - */ - if (tree->operation == LDB_OP_PRESENT && - is_attr_in_list(attrs_always_present, attr_name)) { - return LDB_SUCCESS; - } - - ret = check_attr_access_rights(ctx->mem_ctx, attr_name, ctx->ac, - ctx->sd, ctx->objectclass, ctx->sid, - ctx->dn); - - /* - * if the user does not have the rights to view this attribute, then we - * should not return the object as a search result, i.e. act as if the - * object doesn't exist (for this particular user, at least) - */ - if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { - ctx->suppress_result = true; - return LDB_SUCCESS; - } - - return ret; -} - -/* - * Traverse the search-tree to check that the user has sufficient access rights - * to view all the attributes. - */ -static int check_search_ops_access(struct aclread_context *ac, - TALLOC_CTX *mem_ctx, - struct security_descriptor *sd, - const struct dsdb_class *objectclass, - const struct dom_sid *sid, struct ldb_dn *dn, - bool *suppress_result) -{ - int ret; - struct parse_tree_aclread_ctx ctx = { 0 }; - struct ldb_parse_tree *tree = ac->req->op.search.tree; - - ctx.ac = ac; - ctx.mem_ctx = mem_ctx; - ctx.suppress_result = false; - ctx.sid = sid; - ctx.dn = dn; - ctx.sd = sd; - ctx.objectclass = objectclass; - - /* walk the search tree, checking each attribute as we go */ - ret = ldb_parse_tree_walk(tree, parse_tree_check_attr_access, &ctx); - - /* return whether this search result should be hidden to this user */ - *suppress_result = ctx.suppress_result; - return ret; -} - /* * Whether this attribute was added to perform access checks and must be * removed. @@ -650,17 +723,14 @@ static bool should_remove_attr(const char *attr, const struct aclread_context *a static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) { - struct ldb_context *ldb; struct aclread_context *ac; + struct aclread_private *private_data = NULL; struct ldb_message *msg; int ret; unsigned int i; struct access_check_context acl_ctx; - TALLOC_CTX *tmp_ctx; - bool suppress_result = false; ac = talloc_get_type_abort(req->context, struct aclread_context); - ldb = ldb_module_get_ctx(ac->module); if (!ares) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR ); } @@ -668,14 +738,9 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) return ldb_module_done(ac->req, ares->controls, ares->response, ares->error); } - tmp_ctx = talloc_new(ac); switch (ares->type) { case LDB_REPLY_ENTRY: msg = ares->message; - ret = setup_access_check_context(ac, msg, &acl_ctx); - if (ret != LDB_SUCCESS) { - return ret; - } if (!ldb_dn_is_null(msg->dn)) { /* @@ -684,132 +749,88 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) */ ret = aclread_check_object_visible(ac, msg, req); if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { - talloc_free(tmp_ctx); return LDB_SUCCESS; } else if (ret != LDB_SUCCESS) { + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); ldb_debug_set(ldb, LDB_DEBUG_FATAL, "acl_read: %s check parent %s - %s\n", ldb_dn_get_linearized(msg->dn), ldb_strerror(ret), ldb_errstring(ldb)); - goto fail; + return ldb_module_done(ac->req, NULL, NULL, ret); } } /* for every element in the message check RP */ - for (i=0; i < msg->num_elements; i++) { - const struct dsdb_attribute *attr; - uint32_t access_mask; - attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, - msg->elements[i].name); - if (!attr) { - ldb_debug_set(ldb, LDB_DEBUG_FATAL, - "acl_read: %s cannot find attr[%s] in of schema\n", - ldb_dn_get_linearized(msg->dn), - msg->elements[i].name); - ret = LDB_ERR_OPERATIONS_ERROR; - goto fail; - } + for (i = 0; i < msg->num_elements; ++i) { + struct ldb_message_element *el = &msg->elements[i]; + /* Remove attributes added to perform access checks. */ - if (should_remove_attr(msg->elements[i].name, ac)) { - ldb_msg_element_mark_inaccessible(&msg->elements[i]); + if (should_remove_attr(el->name, ac)) { + ldb_msg_element_mark_inaccessible(el); continue; } - access_mask = get_attr_access_mask(attr, ac->sd_flags); - - if (access_mask == 0) { - ldb_msg_element_mark_inaccessible(&msg->elements[i]); + if (acl_element_is_access_checked(el)) { + /* We will have already checked this attribute. */ continue; } - ret = acl_check_access_on_attribute(ac->module, - tmp_ctx, - acl_ctx.sd, - acl_ctx.sid, - access_mask, - attr, - objectclass); - /* - * Dirsync control needs the replpropertymetadata attribute - * so return it as it will be removed by the control - * in anycase. + * We need to fetch the security descriptor to check + * this attribute. */ - if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { - bool in_search_filter; - - /* check if attr is part of the search filter */ - in_search_filter = dsdb_attr_in_parse_tree(ac->req->op.search.tree, - msg->elements[i].name); - - if (in_search_filter) { - - /* - * We are doing dirysnc answers - * and the object shouldn't be returned (normally) - * but we will return it without replPropertyMetaData - * so that the dirysync module will do what is needed - * (remove the object if it is not deleted, or return - * just the objectGUID if it's deleted). - */ - if (ac->indirsync) { - ldb_msg_remove_attr(msg, "replPropertyMetaData"); - break; - } else { - - /* do not return this entry */ - talloc_free(tmp_ctx); - return LDB_SUCCESS; - } - } else { - ldb_msg_element_mark_inaccessible(&msg->elements[i]); - } - } else if (ret != LDB_SUCCESS) { - ldb_debug_set(ldb, LDB_DEBUG_FATAL, - "acl_read: %s check attr[%s] gives %s - %s\n", - ldb_dn_get_linearized(msg->dn), - msg->elements[i].name, - ldb_strerror(ret), - ldb_errstring(ldb)); - goto fail; - } + break; } - /* - * check access rights for the search attributes, as well as the - * attribute values actually being returned - */ - ret = check_search_ops_access(ac, tmp_ctx, acl_ctx.sd, acl_ctx.objectclass, acl_ctx.sid, - msg->dn, &suppress_result); + if (i == msg->num_elements) { + /* All elements have been checked. */ + goto reply_entry_done; + } + + ret = setup_access_check_context(ac, msg, &acl_ctx); if (ret != LDB_SUCCESS) { - ldb_debug_set(ldb, LDB_DEBUG_FATAL, - "acl_read: %s check search ops %s - %s\n", - ldb_dn_get_linearized(msg->dn), - ldb_strerror(ret), ldb_errstring(ldb)); - goto fail; + return ret; } - if (suppress_result) { + private_data = talloc_get_type_abort(ldb_module_get_private(ac->module), + struct aclread_private); + + for (/* begin where we left off */; i < msg->num_elements; ++i) { + struct ldb_message_element *el = &msg->elements[i]; + + /* Remove attributes added to perform access checks. */ + if (should_remove_attr(el->name, ac)) { + ldb_msg_element_mark_inaccessible(el); + continue; + } + + if (acl_element_is_access_checked(el)) { + /* We will have already checked this attribute. */ + continue; + } /* - * As per the above logic, we strip replPropertyMetaData - * out of the msg so that the dirysync module will do - * what is needed (return just the objectGUID if it's, - * deleted, or remove the object if it is not). + * We need to check whether the attribute is secret, + * confidential, or access-controlled. */ - if (ac->indirsync) { - ldb_msg_remove_attr(msg, "replPropertyMetaData"); - } else { - talloc_free(tmp_ctx); - return LDB_SUCCESS; + ret = acl_redact_attr(ac, + el, + ac, + private_data, + msg, + ac->schema, + acl_ctx.sd, + acl_ctx.sid, + acl_ctx.objectclass); + if (ret != LDB_SUCCESS) { + return ldb_module_done(ac->req, NULL, NULL, ret); } } + reply_entry_done: ldb_msg_remove_inaccessible(msg); - talloc_free(tmp_ctx); - ac->num_entries++; return ldb_module_send_entry(ac->req, msg, ares->controls); case LDB_REPLY_REFERRAL: @@ -830,9 +851,6 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) } return LDB_SUCCESS; -fail: - talloc_free(tmp_ctx); - return ldb_module_done(ac->req, NULL, NULL, ret); } @@ -843,7 +861,6 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) struct aclread_context *ac; struct ldb_request *down_req; struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); - uint32_t flags = ldb_req_get_custom_flags(req); struct ldb_result *res; struct aclread_private *p; bool need_sd = false; @@ -878,15 +895,6 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) } ac->module = module; ac->req = req; - ac->schema = dsdb_get_schema(ldb, req); - if (flags & DSDB_ACL_CHECKS_DIRSYNC_FLAG) { - ac->indirsync = true; - } else { - ac->indirsync = false; - } - if (!ac->schema) { - return ldb_operr(ldb); - } attrs = req->op.search.attrs; if (attrs == NULL) { @@ -943,7 +951,7 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) ac->added_nTSecurityDescriptor = true; } - ac->attrs = req->op.search.attrs; + ac->am_administrator = dsdb_module_am_administrator(module); /* check accessibility of base */ if (!ldb_dn_is_null(req->op.search.base)) { @@ -987,19 +995,270 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) return LDB_ERR_OPERATIONS_ERROR; } + /* + * We provide 'ac' as the control value, which is then used by the + * callback to avoid double-work. + */ + ret = ldb_request_add_control(down_req, DSDB_CONTROL_ACL_READ_OID, false, ac); + if (ret != LDB_SUCCESS) { + return ldb_error(ldb, ret, + "acl_read: Error adding acl_read control."); + } + return ldb_next_request(module, down_req); } +/* + * Here we mark inaccessible attributes known to be looked for in the + * filter. This only redacts attributes found in the search expression. If any + * extended attribute match rules examine different attributes without their own + * access control checks, a security bypass is possible. + */ +static int acl_redact_msg_for_filter(struct ldb_module *module, struct ldb_request *req, struct ldb_message *msg) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + const struct aclread_private *private_data = NULL; + struct ldb_control *control = NULL; + struct aclread_context *ac = NULL; + struct access_check_context acl_ctx; + int ret; + unsigned i; + + /* + * The private data contains a list of attributes which are to be + * considered secret. + */ + private_data = talloc_get_type(ldb_module_get_private(module), struct aclread_private); + if (private_data == NULL) { + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "aclread_private data is missing"); + } + if (!private_data->enabled) { + return LDB_SUCCESS; + } + + control = ldb_request_get_control(req, DSDB_CONTROL_ACL_READ_OID); + if (control == NULL) { + /* + * We've bypassed the acl_read module for this request, and + * should skip redaction in this case. + */ + return LDB_SUCCESS; + } + + ac = talloc_get_type_abort(control->data, struct aclread_context); + + if (!ac->got_tree_attrs) { + ret = ldb_parse_tree_collect_acl_attrs(module, ac, &ac->tree_attrs, req->op.search.tree); + if (ret != LDB_SUCCESS) { + return ret; + } + ac->got_tree_attrs = true; + } + + for (i = 0; i < msg->num_elements; ++i) { + struct ldb_message_element *el = &msg->elements[i]; + + /* Is the attribute mentioned in the search expression? */ + if (attr_in_vec(&ac->tree_attrs, el->name)) { + /* + * We need to fetch the security descriptor to check + * this element. + */ + break; + } + + /* + * This attribute is not in the search filter, so we can leave + * handling it till aclread_callback(), by which time we know + * this object is a match. This saves work checking ACLs if the + * search is unindexed and most objects don't match the filter. + */ + } + + if (i == msg->num_elements) { + /* All elements have been checked. */ + return LDB_SUCCESS; + } + + ret = setup_access_check_context(ac, msg, &acl_ctx); + if (ret != LDB_SUCCESS) { + return ret; + } + + /* For every element in the message and the parse tree, check RP. */ + + for (/* begin where we left off */; i < msg->num_elements; ++i) { + struct ldb_message_element *el = &msg->elements[i]; + + /* Is the attribute mentioned in the search expression? */ + if (!attr_in_vec(&ac->tree_attrs, el->name)) { + /* + * If not, leave it for later and check the next + * attribute. + */ + continue; + } + + /* + * We need to check whether the attribute is secret, + * confidential, or access-controlled. + */ + ret = acl_redact_attr(ac, + el, + ac, + private_data, + msg, + ac->schema, + acl_ctx.sd, + acl_ctx.sid, + acl_ctx.objectclass); + if (ret != LDB_SUCCESS) { + return ret; + } + + acl_element_mark_access_checked(el); + } + + return LDB_SUCCESS; +} + static int aclread_init(struct ldb_module *module) { struct ldb_context *ldb = ldb_module_get_ctx(module); + unsigned int i, n, j; + TALLOC_CTX *mem_ctx = NULL; + int ret; + bool userPassword_support; + static const char * const attrs[] = { "passwordAttribute", NULL }; + static const char * const secret_attrs[] = { + DSDB_SECRET_ATTRIBUTES + }; + struct ldb_result *res; + struct ldb_message *msg; + struct ldb_message_element *password_attributes; struct aclread_private *p = talloc_zero(module, struct aclread_private); if (p == NULL) { return ldb_module_oom(module); } p->enabled = lpcfg_parm_bool(ldb_get_opaque(ldb, "loadparm"), NULL, "acl", "search", true); + + ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(ldb, LDB_DEBUG_ERROR, + "acl_module_init: Unable to register sd_flags control with rootdse!\n"); + return ldb_operr(ldb); + } + ldb_module_set_private(module, p); - return ldb_next_init(module); + + mem_ctx = talloc_new(module); + if (!mem_ctx) { + return ldb_oom(ldb); + } + + ret = dsdb_module_search_dn(module, mem_ctx, &res, + ldb_dn_new(mem_ctx, ldb, "@KLUDGEACL"), + attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM, + NULL); + if (ret != LDB_SUCCESS) { + goto done; + } + if (res->count == 0) { + goto done; + } + + if (res->count > 1) { + talloc_free(mem_ctx); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + msg = res->msgs[0]; + + password_attributes = ldb_msg_find_element(msg, "passwordAttribute"); + if (!password_attributes) { + goto done; + } + p->password_attrs = talloc_array(p, const char *, + password_attributes->num_values + + ARRAY_SIZE(secret_attrs) + 1); + if (!p->password_attrs) { + talloc_free(mem_ctx); + return ldb_oom(ldb); + } + + n = 0; + for (i=0; i < password_attributes->num_values; i++) { + p->password_attrs[n] = (const char *)password_attributes->values[i].data; + talloc_steal(p->password_attrs, password_attributes->values[i].data); + n++; + } + + for (i=0; i < ARRAY_SIZE(secret_attrs); i++) { + bool found = false; + + for (j=0; j < n; j++) { + if (strcasecmp(p->password_attrs[j], secret_attrs[i]) == 0) { + found = true; + break; + } + } + + if (found) { + continue; + } + + p->password_attrs[n] = talloc_strdup(p->password_attrs, + secret_attrs[i]); + if (p->password_attrs[n] == NULL) { + talloc_free(mem_ctx); + return ldb_oom(ldb); + } + n++; + } + p->password_attrs[n] = NULL; + + ret = ldb_register_redact_callback(ldb, acl_redact_msg_for_filter, module); + if (ret != LDB_SUCCESS) { + return ret; + } + +done: + talloc_free(mem_ctx); + ret = ldb_next_init(module); + + if (ret != LDB_SUCCESS) { + return ret; + } + + if (p->password_attrs != NULL) { + /* + * Check this after the modules have be initialised so we can + * actually read the backend DB. + */ + userPassword_support = dsdb_user_password_support(module, + module, + NULL); + if (!userPassword_support) { + /* + * Remove the userPassword attribute, as it is not + * considered secret. + */ + for (i = 0; p->password_attrs[i] != NULL; ++i) { + if (ldb_attr_cmp(p->password_attrs[i], "userPassword") == 0) { + break; + } + } + + /* Shift following elements backwards by one. */ + for (; p->password_attrs[i] != NULL; ++i) { + p->password_attrs[i] = p->password_attrs[i + 1]; + } + } + } + return ret; } static const struct ldb_module_ops ldb_aclread_module_ops = { diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h index 3db7704307f..f22b4d99972 100644 --- a/source4/dsdb/samdb/samdb.h +++ b/source4/dsdb/samdb/samdb.h @@ -232,6 +232,8 @@ struct dsdb_control_transaction_identifier { */ #define DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID "1.3.6.1.4.1.7165.4.3.35" +#define DSDB_CONTROL_ACL_READ_OID "1.3.6.1.4.1.7165.4.3.37" + #define DSDB_EXTENDED_REPLICATED_OBJECTS_OID "1.3.6.1.4.1.7165.4.4.1" struct dsdb_extended_replicated_object { struct ldb_message *msg; diff --git a/source4/dsdb/tests/python/confidential_attr.py b/source4/dsdb/tests/python/confidential_attr.py index 031c9690ba6..6889d5a5560 100755 --- a/source4/dsdb/tests/python/confidential_attr.py +++ b/source4/dsdb/tests/python/confidential_attr.py @@ -490,7 +490,7 @@ class ConfidentialAttrTest(ConfidentialAttrCommon): self.make_attr_confidential() self.assert_conf_attr_searches(has_rights_to=0) - dc_mode = self.guess_dc_mode() + dc_mode = DC_MODE_RETURN_ALL self.assert_negative_searches(has_rights_to=0, dc_mode=dc_mode) self.assert_attr_visible(expect_attr=False) @@ -503,7 +503,7 @@ class ConfidentialAttrTest(ConfidentialAttrCommon): self.make_attr_confidential() self.assert_conf_attr_searches(has_rights_to=0) - dc_mode = self.guess_dc_mode() + dc_mode = DC_MODE_RETURN_ALL self.assert_negative_searches(has_rights_to=0, dc_mode=dc_mode) self.assert_attr_visible(expect_attr=False) @@ -566,7 +566,7 @@ class ConfidentialAttrTest(ConfidentialAttrCommon): self.make_attr_confidential() self.assert_conf_attr_searches(has_rights_to=0) - dc_mode = self.guess_dc_mode() + dc_mode = DC_MODE_RETURN_ALL self.assert_negative_searches(has_rights_to=0, dc_mode=dc_mode) self.assert_attr_visible(expect_attr=False) @@ -741,7 +741,7 @@ class ConfidentialAttrTestDenyAcl(ConfidentialAttrCommon): # the user shouldn't be able to see the attribute anymore self.assert_conf_attr_searches(has_rights_to="deny-one") - dc_mode = self.guess_dc_mode() + dc_mode = DC_MODE_RETURN_ALL self.assert_negative_searches(has_rights_to="deny-one", dc_mode=dc_mode) self.assert_attr_visible(expect_attr=False) @@ -917,7 +917,7 @@ class ConfidentialAttrTestDirsync(ConfidentialAttrCommon): self.assert_conf_attr_searches(has_rights_to=0) self.assert_attr_visible(expect_attr=False) - dc_mode = self.guess_dc_mode() + dc_mode = DC_MODE_RETURN_ALL self.assert_negative_searches(has_rights_to=0, dc_mode=dc_mode) # as a final sanity-check, make sure the admin can still see the attr @@ -1012,7 +1012,7 @@ class ConfidentialAttrTestDirsync(ConfidentialAttrCommon): # check we can't see the objects now, even with using dirsync controls self.assert_conf_attr_searches(has_rights_to=0) self.assert_attr_visible(expect_attr=False) - dc_mode = self.guess_dc_mode() + dc_mode = DC_MODE_RETURN_ALL self.assert_negative_searches(has_rights_to=0, dc_mode=dc_mode) # now delete the users (except for the user whose LDB connection diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif index 79800bfd6df..27c7e9dbb47 100644 --- a/source4/setup/schema_samba4.ldif +++ b/source4/setup/schema_samba4.ldif @@ -233,6 +233,7 @@ #Allocated: DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID 1.3.6.1.4.1.7165.4.3.34 #Allocated: DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID 1.3.6.1.4.1.7165.4.3.35 #Allocated: DSDB_CONTROL_CALCULATED_DEFAULT_SD_OID 1.3.6.1.4.1.7165.4.3.36 +#Allocated: DSDB_CONTROL_ACL_READ_OID 1.3.6.1.4.1.7165.4.3.37 # Extended 1.3.6.1.4.1.7165.4.4.x -- 2.25.1 From 8811e67cb2e9046f0654f5be53e95fa0a4d1af73 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 27 Feb 2023 13:31:44 +1300 Subject: [PATCH 27/41] CVE-2023-0614 s4-acl: Avoid calling dsdb_module_am_system() if we can help it If the AS_SYSTEM control is present, we know we have system privileges, and have no need to call dsdb_module_am_system(). BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/acl_read.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index 8ca8607b925..6dcc3c9b36e 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -860,7 +860,7 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) int ret; struct aclread_context *ac; struct ldb_request *down_req; - struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); + bool am_system; struct ldb_result *res; struct aclread_private *p; bool need_sd = false; @@ -877,11 +877,16 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) ldb = ldb_module_get_ctx(module); p = talloc_get_type(ldb_module_get_private(module), struct aclread_private); + am_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID) != NULL; + if (!am_system) { + am_system = dsdb_module_am_system(module); + } + /* skip access checks if we are system or system control is supplied * or this is not LDAP server request */ if (!p || !p->enabled || - dsdb_module_am_system(module) - || as_system || !is_untrusted) { + am_system || + !is_untrusted) { return ldb_next_request(module, req); } /* no checks on special dn */ -- 2.25.1 From bd69d5e962674f4921887ac551e28c9b4c71feae Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 16 Feb 2023 12:35:34 +1300 Subject: [PATCH 28/41] CVE-2023-0614 ldb: Use binary search to check whether attribute is secret BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/acl_read.c | 56 ++++++++++++++--------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index 6dcc3c9b36e..21f72fbe720 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -79,6 +79,7 @@ struct aclread_private { struct security_descriptor *sd_cached; struct ldb_val sd_cached_blob; const char **password_attrs; + size_t num_password_attrs; }; struct access_check_context { @@ -512,22 +513,18 @@ static int aclread_get_sd_from_ldb_message(struct aclread_context *ac, /* Check whether the attribute is a password attribute. */ static bool attr_is_secret(const char *attr, const struct aclread_private *private_data) { - unsigned i; + const char **found = NULL; if (private_data->password_attrs == NULL) { return false; } - for (i = 0; private_data->password_attrs[i] != NULL; ++i) { - const char *password_attr = private_data->password_attrs[i]; - if (ldb_attr_cmp(attr, password_attr) != 0) { - continue; - } - - return true; - } - - return false; + BINARY_ARRAY_SEARCH_V(private_data->password_attrs, + private_data->num_password_attrs, + attr, + ldb_attr_cmp, + found); + return found != NULL; } /* @@ -1128,6 +1125,14 @@ static int acl_redact_msg_for_filter(struct ldb_module *module, struct ldb_reque return LDB_SUCCESS; } +static int ldb_attr_cmp_fn(const void *_a, const void *_b) +{ + const char * const *a = _a; + const char * const *b = _b; + + return ldb_attr_cmp(*a, *b); +} + static int aclread_init(struct ldb_module *module) { struct ldb_context *ldb = ldb_module_get_ctx(module); @@ -1188,7 +1193,7 @@ static int aclread_init(struct ldb_module *module) } p->password_attrs = talloc_array(p, const char *, password_attributes->num_values + - ARRAY_SIZE(secret_attrs) + 1); + ARRAY_SIZE(secret_attrs)); if (!p->password_attrs) { talloc_free(mem_ctx); return ldb_oom(ldb); @@ -1223,7 +1228,10 @@ static int aclread_init(struct ldb_module *module) } n++; } - p->password_attrs[n] = NULL; + p->num_password_attrs = n; + + /* Sort the password attributes so we can use binary search. */ + TYPESAFE_QSORT(p->password_attrs, p->num_password_attrs, ldb_attr_cmp_fn); ret = ldb_register_redact_callback(ldb, acl_redact_msg_for_filter, module); if (ret != LDB_SUCCESS) { @@ -1247,19 +1255,25 @@ done: module, NULL); if (!userPassword_support) { + const char **found = NULL; + /* * Remove the userPassword attribute, as it is not * considered secret. */ - for (i = 0; p->password_attrs[i] != NULL; ++i) { - if (ldb_attr_cmp(p->password_attrs[i], "userPassword") == 0) { - break; + BINARY_ARRAY_SEARCH_V(p->password_attrs, + p->num_password_attrs, + "userPassword", + ldb_attr_cmp, + found); + if (found != NULL) { + size_t found_idx = found - p->password_attrs; + + /* Shift following elements backwards by one. */ + for (i = found_idx; i < p->num_password_attrs - 1; ++i) { + p->password_attrs[i] = p->password_attrs[i + 1]; } - } - - /* Shift following elements backwards by one. */ - for (; p->password_attrs[i] != NULL; ++i) { - p->password_attrs[i] = p->password_attrs[i + 1]; + --p->num_password_attrs; } } } -- 2.25.1 From b98f8c1af7770b49a447fdcc67ea64d98454955f Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 14 Feb 2023 13:17:24 +1300 Subject: [PATCH 29/41] CVE-2023-0614 ldb: Centralise checking for inaccessible matches This makes it less likely that we forget to handle a case. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb-samba/ldb_matching_rules.c | 5 --- lib/ldb/common/ldb_match.c | 56 +++++++++++++++++------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c index 0c4c31e49f9..b86594c1823 100644 --- a/lib/ldb-samba/ldb_matching_rules.c +++ b/lib/ldb-samba/ldb_matching_rules.c @@ -87,11 +87,6 @@ static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx, return LDB_SUCCESS; } - if (ldb_msg_element_is_inaccessible(el)) { - *matched = false; - return LDB_SUCCESS; - } - /* * If the value to match is present in the attribute values of the * current entry being visited, set matched to true and return OK diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c index b0a33e939eb..267498560e6 100644 --- a/lib/ldb/common/ldb_match.c +++ b/lib/ldb/common/ldb_match.c @@ -99,11 +99,6 @@ static int ldb_match_present(struct ldb_context *ldb, return LDB_SUCCESS; } - if (ldb_msg_element_is_inaccessible(el)) { - *matched = false; - return LDB_SUCCESS; - } - a = ldb_schema_attribute_by_name(ldb, el->name); if (!a) { return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; @@ -145,11 +140,6 @@ static int ldb_match_comparison(struct ldb_context *ldb, return LDB_SUCCESS; } - if (ldb_msg_element_is_inaccessible(el)) { - *matched = false; - return LDB_SUCCESS; - } - a = ldb_schema_attribute_by_name(ldb, el->name); if (!a) { return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; @@ -220,11 +210,6 @@ static int ldb_match_equality(struct ldb_context *ldb, return LDB_SUCCESS; } - if (ldb_msg_element_is_inaccessible(el)) { - *matched = false; - return LDB_SUCCESS; - } - a = ldb_schema_attribute_by_name(ldb, el->name); if (a == NULL) { return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; @@ -425,11 +410,6 @@ static int ldb_match_substring(struct ldb_context *ldb, return LDB_SUCCESS; } - if (ldb_msg_element_is_inaccessible(el)) { - *matched = false; - return LDB_SUCCESS; - } - for (i = 0; i < el->num_values; i++) { int ret; ret = ldb_wildcard_compare(ldb, tree, el->values[i], matched); @@ -503,11 +483,6 @@ static int ldb_match_bitmask(struct ldb_context *ldb, return LDB_SUCCESS; } - if (ldb_msg_element_is_inaccessible(el)) { - *matched = false; - return LDB_SUCCESS; - } - for (i=0;inum_values;i++) { int ret; struct ldb_val *v = &el->values[i]; @@ -596,6 +571,26 @@ static int ldb_match_extended(struct ldb_context *ldb, &tree->u.extended.value, matched); } +static bool ldb_must_suppress_match(const struct ldb_message *msg, + const struct ldb_parse_tree *tree) +{ + const char *attr = NULL; + struct ldb_message_element *el = NULL; + + attr = ldb_parse_tree_get_attr(tree); + if (attr == NULL) { + return false; + } + + /* find the message element */ + el = ldb_msg_find_element(msg, attr); + if (el == NULL) { + return false; + } + + return ldb_msg_element_is_inaccessible(el); +} + /* Check if a particular message will match the given filter @@ -620,6 +615,17 @@ int ldb_match_message(struct ldb_context *ldb, return LDB_SUCCESS; } + /* + * Suppress matches on confidential attributes (handled + * manually in extended matches as these can do custom things + * like read other parts of the DB or other attributes). + */ + if (tree->operation != LDB_OP_EXTENDED) { + if (ldb_must_suppress_match(msg, tree)) { + return LDB_SUCCESS; + } + } + switch (tree->operation) { case LDB_OP_AND: for (i=0;iu.list.num_elements;i++) { -- 2.25.1 From e08188bb9847b4c34d62f7c812d58b07105f5756 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 3 Mar 2023 17:35:55 +1300 Subject: [PATCH 30/41] CVE-2023-0614 ldb: Filter on search base before redacting message Redaction may be expensive if we end up needing to fetch a security descriptor to verify rights to an attribute. Checking the search scope is probably cheaper, so do that first. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- lib/ldb/common/ldb_match.c | 8 +++--- lib/ldb/include/ldb_private.h | 8 ++++++ lib/ldb/ldb_key_value/ldb_kv_index.c | 40 +++++++++++++++------------ lib/ldb/ldb_key_value/ldb_kv_search.c | 14 ++++++++-- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c index 267498560e6..463a24ce3bc 100644 --- a/lib/ldb/common/ldb_match.c +++ b/lib/ldb/common/ldb_match.c @@ -39,10 +39,10 @@ /* check if the scope matches in a search result */ -static int ldb_match_scope(struct ldb_context *ldb, - struct ldb_dn *base, - struct ldb_dn *dn, - enum ldb_scope scope) +int ldb_match_scope(struct ldb_context *ldb, + struct ldb_dn *base, + struct ldb_dn *dn, + enum ldb_scope scope) { int ret = 0; diff --git a/lib/ldb/include/ldb_private.h b/lib/ldb/include/ldb_private.h index b0a42f6421c..5e29de34f79 100644 --- a/lib/ldb/include/ldb_private.h +++ b/lib/ldb/include/ldb_private.h @@ -322,6 +322,14 @@ int ldb_match_message(struct ldb_context *ldb, const struct ldb_parse_tree *tree, enum ldb_scope scope, bool *matched); +/* + check if the scope matches in a search result +*/ +int ldb_match_scope(struct ldb_context *ldb, + struct ldb_dn *base, + struct ldb_dn *dn, + enum ldb_scope scope); + /* Reallocate elements to drop any excess capacity. */ void ldb_msg_shrink_to_fit(struct ldb_message *msg); diff --git a/lib/ldb/ldb_key_value/ldb_kv_index.c b/lib/ldb/ldb_key_value/ldb_kv_index.c index 163052fecf7..aac0913f431 100644 --- a/lib/ldb/ldb_key_value/ldb_kv_index.c +++ b/lib/ldb/ldb_key_value/ldb_kv_index.c @@ -2428,31 +2428,37 @@ static int ldb_kv_index_filter(struct ldb_kv_private *ldb_kv, return LDB_ERR_OPERATIONS_ERROR; } - if (ldb->redact.callback != NULL) { - ret = ldb->redact.callback(ldb->redact.module, ac->req, msg); - if (ret != LDB_SUCCESS) { - talloc_free(msg); - return ret; - } - } - /* * We trust the index for LDB_SCOPE_ONELEVEL * unless the index key has been truncated. * * LDB_SCOPE_BASE is not passed in by our only caller. */ - if (ac->scope == LDB_SCOPE_ONELEVEL && - ldb_kv->cache->one_level_indexes && - scope_one_truncation == KEY_NOT_TRUNCATED) { - ret = ldb_match_message(ldb, msg, ac->tree, - ac->scope, &matched); - } else { - ret = ldb_match_msg_error(ldb, msg, - ac->tree, ac->base, - ac->scope, &matched); + if (ac->scope != LDB_SCOPE_ONELEVEL || + !ldb_kv->cache->one_level_indexes || + scope_one_truncation != KEY_NOT_TRUNCATED) + { + /* + * The redaction callback may be expensive to call if it + * fetches a security descriptor. Check the DN early and + * bail out if it doesn't match the base. + */ + if (!ldb_match_scope(ldb, ac->base, msg->dn, ac->scope)) { + talloc_free(msg); + continue; + } + } + + if (ldb->redact.callback != NULL) { + ret = ldb->redact.callback(ldb->redact.module, ac->req, msg); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return ret; + } } + ret = ldb_match_message(ldb, msg, ac->tree, + ac->scope, &matched); if (ret != LDB_SUCCESS) { talloc_free(keys); talloc_free(msg); diff --git a/lib/ldb/ldb_key_value/ldb_kv_search.c b/lib/ldb/ldb_key_value/ldb_kv_search.c index d187ba223e1..27f68caef01 100644 --- a/lib/ldb/ldb_key_value/ldb_kv_search.c +++ b/lib/ldb/ldb_key_value/ldb_kv_search.c @@ -395,6 +395,16 @@ static int search_func(_UNUSED_ struct ldb_kv_private *ldb_kv, } } + /* + * The redaction callback may be expensive to call if it fetches a + * security descriptor. Check the DN early and bail out if it doesn't + * match the base. + */ + if (!ldb_match_scope(ldb, ac->base, msg->dn, ac->scope)) { + talloc_free(msg); + return 0; + } + if (ldb->redact.callback != NULL) { ret = ldb->redact.callback(ldb->redact.module, ac->req, msg); if (ret != LDB_SUCCESS) { @@ -404,8 +414,8 @@ static int search_func(_UNUSED_ struct ldb_kv_private *ldb_kv, } /* see if it matches the given expression */ - ret = ldb_match_msg_error(ldb, msg, - ac->tree, ac->base, ac->scope, &matched); + ret = ldb_match_message(ldb, msg, + ac->tree, ac->scope, &matched); if (ret != LDB_SUCCESS) { talloc_free(msg); ac->error = LDB_ERR_OPERATIONS_ERROR; -- 2.25.1 From d148a7dd88d4bcc596225be2795fc969284dfd08 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 24 Feb 2023 10:03:25 +1300 Subject: [PATCH 31/41] CVE-2023-0614 s4-dsdb: Treat confidential attributes as unindexed In the unlikely case that someone adds a confidential indexed attribute to the schema, LDAP search expressions on that attribute could disclose information via timing differences. Let's not use the index for searches on confidential attributes. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/samdb/ldb_modules/extended_dn_in.c | 10 +++++++++- source4/dsdb/schema/schema_description.c | 7 +++++++ source4/dsdb/schema/schema_init.c | 11 +++++++++-- source4/dsdb/schema/schema_set.c | 9 ++++++++- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_in.c b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c index 1dc1e1f2d42..248bb66f039 100644 --- a/source4/dsdb/samdb/ldb_modules/extended_dn_in.c +++ b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c @@ -423,7 +423,15 @@ static int extended_dn_filter_callback(struct ldb_parse_tree *tree, void *privat guid_val = ldb_dn_get_extended_component(dn, "GUID"); sid_val = ldb_dn_get_extended_component(dn, "SID"); - if (!guid_val && !sid_val && (attribute->searchFlags & SEARCH_FLAG_ATTINDEX)) { + /* + * Is the attribute indexed? By treating confidential attributes + * as unindexed, we force searches to go through the unindexed + * search path, avoiding observable timing differences. + */ + if (!guid_val && !sid_val && + (attribute->searchFlags & SEARCH_FLAG_ATTINDEX) && + !(attribute->searchFlags & SEARCH_FLAG_CONFIDENTIAL)) + { /* if it is indexed, then fixing the string DN will do no good here, as we will not find the attribute in the index. So for now fall through to a standard DN diff --git a/source4/dsdb/schema/schema_description.c b/source4/dsdb/schema/schema_description.c index 243a02a15f3..5fc70154bf8 100644 --- a/source4/dsdb/schema/schema_description.c +++ b/source4/dsdb/schema/schema_description.c @@ -160,6 +160,13 @@ char *schema_attribute_to_extendedInfo(TALLOC_CTX *mem_ctx, const struct dsdb_at attribute->rangeUpper, GUID_hexstring(tmp_ctx, &attribute->schemaIDGUID), GUID_hexstring(tmp_ctx, &attribute->attributeSecurityGUID), + /* + * We actually ignore the indexed + * flag for confidential + * attributes, but we'll include + * it for the purposes of + * description. + */ (attribute->searchFlags & SEARCH_FLAG_ATTINDEX), attribute->systemOnly); talloc_free(tmp_ctx); diff --git a/source4/dsdb/schema/schema_init.c b/source4/dsdb/schema/schema_init.c index a3b00497b6b..c8197b86306 100644 --- a/source4/dsdb/schema/schema_init.c +++ b/source4/dsdb/schema/schema_init.c @@ -514,8 +514,15 @@ static int dsdb_schema_setup_ldb_schema_attribute(struct ldb_context *ldb, if (attr->isSingleValued) { a->flags |= LDB_ATTR_FLAG_SINGLE_VALUE; } - - if (attr->searchFlags & SEARCH_FLAG_ATTINDEX) { + + /* + * Is the attribute indexed? By treating confidential attributes as + * unindexed, we force searches to go through the unindexed search path, + * avoiding observable timing differences. + */ + if (attr->searchFlags & SEARCH_FLAG_ATTINDEX && + !(attr->searchFlags & SEARCH_FLAG_CONFIDENTIAL)) + { a->flags |= LDB_ATTR_FLAG_INDEXED; } diff --git a/source4/dsdb/schema/schema_set.c b/source4/dsdb/schema/schema_set.c index 45faa0912ec..03cf2405595 100644 --- a/source4/dsdb/schema/schema_set.c +++ b/source4/dsdb/schema/schema_set.c @@ -221,7 +221,14 @@ int dsdb_schema_set_indices_and_attributes(struct ldb_context *ldb, break; } - if (attr->searchFlags & SEARCH_FLAG_ATTINDEX) { + /* + * Is the attribute indexed? By treating confidential attributes + * as unindexed, we force searches to go through the unindexed + * search path, avoiding observable timing differences. + */ + if (attr->searchFlags & SEARCH_FLAG_ATTINDEX && + !(attr->searchFlags & SEARCH_FLAG_CONFIDENTIAL)) + { /* * When preparing to downgrade Samba, we need to write * out an LDB without the new key word ORDERED_INTEGER. -- 2.25.1 From 07fffb3e90621c47050929e3ca2232f5a222954e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 2 Mar 2023 16:31:17 +1300 Subject: [PATCH 32/41] CVE-2023-0614 dsdb: Add DSDB_MARK_REQ_UNTRUSTED This will allow our dsdb helper search functions to mark the new request as untrusted, forcing read ACL evaluation (per current behaviour). BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- source4/dsdb/common/util.c | 4 ++++ source4/dsdb/common/util.h | 1 + 2 files changed, 5 insertions(+) diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index b556f06cb63..39b29cd2a0c 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -4878,6 +4878,10 @@ int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags) } } + if (dsdb_flags & DSDB_MARK_REQ_UNTRUSTED) { + ldb_req_mark_untrusted(req); + } + return LDB_SUCCESS; } diff --git a/source4/dsdb/common/util.h b/source4/dsdb/common/util.h index e1854644d53..5bb96d60b3c 100644 --- a/source4/dsdb/common/util.h +++ b/source4/dsdb/common/util.h @@ -43,6 +43,7 @@ #define DSDB_MODIFY_PARTIAL_REPLICA 0x04000 #define DSDB_PASSWORD_BYPASS_LAST_SET 0x08000 #define DSDB_REPLMD_VANISH_LINKS 0x10000 +#define DSDB_MARK_REQ_UNTRUSTED 0x20000 bool is_attr_in_list(const char * const * attrs, const char *attr); -- 2.25.1 From eaeb3dc461fe2913c3e7ff3db802d37dd7c699c8 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 3 Mar 2023 16:49:00 +1300 Subject: [PATCH 33/41] CVE-2023-0614 dsdb: Add pre-cleanup and self.addCleanup() of OU created in match_rules tests BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- lib/ldb-samba/tests/match_rules.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/ldb-samba/tests/match_rules.py b/lib/ldb-samba/tests/match_rules.py index abf485c9eab..2af1dd6a070 100755 --- a/lib/ldb-samba/tests/match_rules.py +++ b/lib/ldb-samba/tests/match_rules.py @@ -31,11 +31,19 @@ class MatchRulesTests(samba.tests.TestCase): self.ou_groups = "OU=groups,%s" % self.ou self.ou_computers = "OU=computers,%s" % self.ou + try: + self.ldb.delete(self.ou, ["tree_delete:1"]) + except LdbError as e: + pass + # Add a organizational unit to create objects self.ldb.add({ "dn": self.ou, "objectclass": "organizationalUnit"}) + self.addCleanup(self.ldb.delete, self.ou, controls=['tree_delete:0']) + + # Add the following OU hierarchy and set otherWellKnownObjects, # which has BinaryDN syntax: # -- 2.25.1 From f17179189c6364c2b0e202e8b839c7879a2b747a Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 2 Mar 2023 16:51:25 +1300 Subject: [PATCH 34/41] CVE-2023-0614 lib/ldb-samba: Add test for SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL / LDAP_MATCHING_RULE_IN_CHAIN with and ACL hidden attributes The chain for transitive evaluation does consider ACLs, avoiding the disclosure of confidential information. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- lib/ldb-samba/tests/match_rules.py | 127 ++++++++++++---------- lib/ldb-samba/tests/match_rules_remote.py | 104 ++++++++++++++++++ source4/selftest/tests.py | 1 + 3 files changed, 175 insertions(+), 57 deletions(-) create mode 100755 lib/ldb-samba/tests/match_rules_remote.py diff --git a/lib/ldb-samba/tests/match_rules.py b/lib/ldb-samba/tests/match_rules.py index 2af1dd6a070..2fe6c3e2264 100755 --- a/lib/ldb-samba/tests/match_rules.py +++ b/lib/ldb-samba/tests/match_rules.py @@ -20,13 +20,18 @@ from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL # Windows appear to preserve casing of the RDN and uppercase the other keys. -class MatchRulesTests(samba.tests.TestCase): +class MatchRulesTestsBase(samba.tests.TestCase): def setUp(self): - super(MatchRulesTests, self).setUp() - self.lp = lp - self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) + super().setUp() + self.lp = self.sambaopts.get_loadparm() + self.creds = self.credopts.get_credentials(self.lp) + + self.ldb = SamDB(self.host, credentials=self.creds, + session_info=system_session(self.lp), + lp=self.lp) self.base_dn = self.ldb.domain_dn() - self.ou = "OU=matchrulestest,%s" % self.base_dn + self.ou_rdn = "OU=matchrulestest" + self.ou = self.ou_rdn + "," + self.base_dn self.ou_users = "OU=users,%s" % self.ou self.ou_groups = "OU=groups,%s" % self.ou self.ou_computers = "OU=computers,%s" % self.ou @@ -212,6 +217,39 @@ class MatchRulesTests(samba.tests.TestCase): FLAG_MOD_ADD, "member") self.ldb.modify(m) + # Add a couple of ms-Exch-Configuration-Container to test forward-link + # attributes without backward link (addressBookRoots2) + # e1 + # |--> e2 + # | |--> c1 + self.ldb.add({ + "dn": "cn=e1,%s" % self.ou, + "objectclass": "msExchConfigurationContainer"}) + self.ldb.add({ + "dn": "cn=e2,%s" % self.ou, + "objectclass": "msExchConfigurationContainer"}) + + m = Message() + m.dn = Dn(self.ldb, "cn=e2,%s" % self.ou) + m["e1"] = MessageElement("cn=c1,%s" % self.ou_computers, + FLAG_MOD_ADD, "addressBookRoots2") + self.ldb.modify(m) + + m = Message() + m.dn = Dn(self.ldb, "cn=e1,%s" % self.ou) + m["e1"] = MessageElement("cn=e2,%s" % self.ou, + FLAG_MOD_ADD, "addressBookRoots2") + self.ldb.modify(m) + + + +class MatchRulesTests(MatchRulesTestsBase): + def setUp(self): + self.sambaopts = sambaopts + self.credopts = credopts + self.host = host + super().setUp() + # The msDS-RevealedUsers is owned by system and cannot be modified # directly. Set the schemaUpgradeInProgress flag as workaround # and create this hierarchy: @@ -251,33 +289,6 @@ class MatchRulesTests(samba.tests.TestCase): m["e1"] = MessageElement("0", FLAG_MOD_REPLACE, "schemaUpgradeInProgress") self.ldb.modify(m) - # Add a couple of ms-Exch-Configuration-Container to test forward-link - # attributes without backward link (addressBookRoots2) - # e1 - # |--> e2 - # | |--> c1 - self.ldb.add({ - "dn": "cn=e1,%s" % self.ou, - "objectclass": "msExchConfigurationContainer"}) - self.ldb.add({ - "dn": "cn=e2,%s" % self.ou, - "objectclass": "msExchConfigurationContainer"}) - - m = Message() - m.dn = Dn(self.ldb, "cn=e2,%s" % self.ou) - m["e1"] = MessageElement("cn=c1,%s" % self.ou_computers, - FLAG_MOD_ADD, "addressBookRoots2") - self.ldb.modify(m) - - m = Message() - m.dn = Dn(self.ldb, "cn=e1,%s" % self.ou) - m["e1"] = MessageElement("cn=e2,%s" % self.ou, - FLAG_MOD_ADD, "addressBookRoots2") - self.ldb.modify(m) - - def tearDown(self): - super(MatchRulesTests, self).tearDown() - self.ldb.delete(self.ou, controls=['tree_delete:0']) def test_u1_member_of_g4(self): # Search without transitive match must return 0 results @@ -953,8 +964,12 @@ class MatchRulesTests(samba.tests.TestCase): class MatchRuleConditionTests(samba.tests.TestCase): def setUp(self): super(MatchRuleConditionTests, self).setUp() - self.lp = lp - self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) + self.lp = sambaopts.get_loadparm() + self.creds = credopts.get_credentials(self.lp) + + self.ldb = SamDB(host, credentials=self.creds, + session_info=system_session(self.lp), + lp=self.lp) self.base_dn = self.ldb.domain_dn() self.ou = "OU=matchruleconditiontests,%s" % self.base_dn self.ou_users = "OU=users,%s" % self.ou @@ -1753,32 +1768,30 @@ class MatchRuleConditionTests(samba.tests.TestCase): self.ou_groups, self.ou_computers)) self.assertEqual(len(res1), 0) +if __name__ == "__main__": -parser = optparse.OptionParser("match_rules.py [options] ") -sambaopts = options.SambaOptions(parser) -parser.add_option_group(sambaopts) -parser.add_option_group(options.VersionOptions(parser)) - -# use command line creds if available -credopts = options.CredentialsOptions(parser) -parser.add_option_group(credopts) -opts, args = parser.parse_args() -subunitopts = SubunitOptions(parser) -parser.add_option_group(subunitopts) + parser = optparse.OptionParser("match_rules.py [options] ") + sambaopts = options.SambaOptions(parser) + parser.add_option_group(sambaopts) + parser.add_option_group(options.VersionOptions(parser)) -if len(args) < 1: - parser.print_usage() - sys.exit(1) + # use command line creds if available + credopts = options.CredentialsOptions(parser) + parser.add_option_group(credopts) + opts, args = parser.parse_args() + subunitopts = SubunitOptions(parser) + parser.add_option_group(subunitopts) -host = args[0] + if len(args) < 1: + parser.print_usage() + sys.exit(1) -lp = sambaopts.get_loadparm() -creds = credopts.get_credentials(lp) + host = args[0] -if "://" not in host: - if os.path.isfile(host): - host = "tdb://%s" % host - else: - host = "ldap://%s" % host + if "://" not in host: + if os.path.isfile(host): + host = "tdb://%s" % host + else: + host = "ldap://%s" % host -TestProgram(module=__name__, opts=subunitopts) + TestProgram(module=__name__, opts=subunitopts) diff --git a/lib/ldb-samba/tests/match_rules_remote.py b/lib/ldb-samba/tests/match_rules_remote.py new file mode 100755 index 00000000000..122231f2a60 --- /dev/null +++ b/lib/ldb-samba/tests/match_rules_remote.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import optparse +import sys +import os +import samba +import samba.getopt as options + +from samba.tests.subunitrun import SubunitOptions, TestProgram + +from samba.samdb import SamDB +from samba.auth import system_session +from samba import sd_utils +from samba.ndr import ndr_unpack +from ldb import Message, MessageElement, Dn, LdbError +from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE +from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL + +from match_rules import MatchRulesTestsBase + + +class MatchRulesTestsUser(MatchRulesTestsBase): + def setUp(self): + self.sambaopts = sambaopts + self.credopts = credopts + self.host = host + super().setUp() + self.sd_utils = sd_utils.SDUtils(self.ldb) + + self.user_pass = "samba123@" + self.match_test_user = "matchtestuser" + self.ldb.newuser(self.match_test_user, + self.user_pass, + userou=self.ou_rdn) + user_creds = self.insta_creds(template=self.creds, + username=self.match_test_user, + userpass=self.user_pass) + self.user_ldb = SamDB(host, credentials=user_creds, lp=self.lp) + token_res = self.user_ldb.search(scope=SCOPE_BASE, + base="", + attrs=["tokenGroups"]) + self.user_sid = ndr_unpack(samba.dcerpc.security.dom_sid, + token_res[0]["tokenGroups"][0]) + + self.member_attr_guid = "bf9679c0-0de6-11d0-a285-00aa003049e2" + + def test_with_denied_link(self): + + # add an ACE that denies the user Read Property (RP) access to + # the member attr (which is similar to making the attribute + # confidential) + ace = "(OD;;RP;{0};;{1})".format(self.member_attr_guid, + self.user_sid) + g2_dn = Dn(self.ldb, "CN=g2,%s" % self.ou_groups) + + # add the ACE that denies access to the attr under test + self.sd_utils.dacl_add_ace(g2_dn, ace) + + # Search without transitive match must return 0 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + # Search with transitive match must return 1 results + res1 = self.ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 1) + self.assertEqual(str(res1[0].dn).lower(), ("CN=g4,%s" % self.ou_groups).lower()) + + # Search as a user match must return 0 results as the intermediate link can't be seen + res1 = self.user_ldb.search("cn=g4,%s" % self.ou_groups, + scope=SCOPE_BASE, + expression="member:1.2.840.113556.1.4.1941:=cn=u1,%s" % self.ou_users) + self.assertEqual(len(res1), 0) + + + +parser = optparse.OptionParser("match_rules_remote.py [options] ") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) + +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +opts, args = parser.parse_args() +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] + +if "://" not in host: + if os.path.isfile(host): + host = "tdb://%s" % host + else: + host = "ldap://%s" % host + +TestProgram(module=__name__, opts=subunitopts) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index c6bf668aa9c..a09945f40da 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1322,6 +1322,7 @@ for env in ['ad_dc_default:local', 'schema_dc:local']: plantestsuite_loadlist("samba4.urgent_replication.python(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [python, os.path.join(DSDB_PYTEST_DIR, "urgent_replication.py"), '$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.ldap.dirsync.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(DSDB_PYTEST_DIR, "dirsync.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.ldap.match_rules.python", "ad_dc_ntvfs", [python, os.path.join(srcdir(), "lib/ldb-samba/tests/match_rules.py"), '$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) +plantestsuite_loadlist("samba4.ldap.match_rules.python", "ad_dc_ntvfs", [python, os.path.join(srcdir(), "lib/ldb-samba/tests/match_rules_remote.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite("samba4.ldap.index.python", "none", [python, os.path.join(srcdir(), "lib/ldb-samba/tests/index.py")]) plantestsuite_loadlist("samba4.ldap.notification.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(DSDB_PYTEST_DIR, "notification.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.ldap.sites.python(ad_dc_default)", "ad_dc_default", [python, os.path.join(DSDB_PYTEST_DIR, "sites.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) -- 2.25.1 From 0313aa744f12b70f7446ca3d104a8b5f5052bade Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 2 Mar 2023 17:24:15 +1300 Subject: [PATCH 35/41] CVE-2023-0614 lib/ldb-samba Ensure ACLs are evaluated on SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL / LDAP_MATCHING_RULE_IN_CHAIN Setting the LDB_HANDLE_FLAG_UNTRUSTED tells the acl_read module to operate on this request. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- lib/ldb-samba/ldb_matching_rules.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c index b86594c1823..59d1385f4e3 100644 --- a/lib/ldb-samba/ldb_matching_rules.c +++ b/lib/ldb-samba/ldb_matching_rules.c @@ -67,7 +67,12 @@ static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx, * Note also that we don't have the original request * here, so we can not apply controls or timeouts here. */ - ret = dsdb_search_dn(ldb, tmp_ctx, &res, to_visit->dn, attrs, 0); + ret = dsdb_search_dn(ldb, + tmp_ctx, + &res, + to_visit->dn, + attrs, + DSDB_MARK_REQ_UNTRUSTED); if (ret != LDB_SUCCESS) { talloc_free(tmp_ctx); return ret; -- 2.25.1 From 6b92716e7f89e22cedbf196b97a0203c54608e7a Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Fri, 3 Mar 2023 17:52:13 +1300 Subject: [PATCH 36/41] CVE-2023-0614 ldb: Release LDB 2.6.2 * CVE-2023-0614 Not-secret but access controlled LDAP attributes can be discovered (bug 15270) BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270 Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton [abartlet@samba.org Adapted to LDB 2.6 series in Samba 4.17] --- lib/ldb/ABI/ldb-2.6.2.sigs | 301 ++++++++++++++++++++++++++++++ lib/ldb/ABI/pyldb-util-2.6.2.sigs | 3 + lib/ldb/wscript | 2 +- 3 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 lib/ldb/ABI/ldb-2.6.2.sigs create mode 100644 lib/ldb/ABI/pyldb-util-2.6.2.sigs diff --git a/lib/ldb/ABI/ldb-2.6.2.sigs b/lib/ldb/ABI/ldb-2.6.2.sigs new file mode 100644 index 00000000000..b4c5e20e8c7 --- /dev/null +++ b/lib/ldb/ABI/ldb-2.6.2.sigs @@ -0,0 +1,301 @@ +ldb_add: int (struct ldb_context *, const struct ldb_message *) +ldb_any_comparison: int (struct ldb_context *, void *, ldb_attr_handler_t, const struct ldb_val *, const struct ldb_val *) +ldb_asprintf_errstring: void (struct ldb_context *, const char *, ...) +ldb_attr_casefold: char *(TALLOC_CTX *, const char *) +ldb_attr_dn: int (const char *) +ldb_attr_in_list: int (const char * const *, const char *) +ldb_attr_list_copy: const char **(TALLOC_CTX *, const char * const *) +ldb_attr_list_copy_add: const char **(TALLOC_CTX *, const char * const *, const char *) +ldb_base64_decode: int (char *) +ldb_base64_encode: char *(TALLOC_CTX *, const char *, int) +ldb_binary_decode: struct ldb_val (TALLOC_CTX *, const char *) +ldb_binary_encode: char *(TALLOC_CTX *, struct ldb_val) +ldb_binary_encode_string: char *(TALLOC_CTX *, const char *) +ldb_build_add_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_del_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_extended_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const char *, void *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_mod_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_rename_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_search_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, const char *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_build_search_req_ex: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, struct ldb_parse_tree *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *) +ldb_casefold: char *(struct ldb_context *, TALLOC_CTX *, const char *, size_t) +ldb_casefold_default: char *(void *, TALLOC_CTX *, const char *, size_t) +ldb_check_critical_controls: int (struct ldb_control **) +ldb_comparison_binary: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *) +ldb_comparison_fold: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *) +ldb_connect: int (struct ldb_context *, const char *, unsigned int, const char **) +ldb_control_to_string: char *(TALLOC_CTX *, const struct ldb_control *) +ldb_controls_except_specified: struct ldb_control **(struct ldb_control **, TALLOC_CTX *, struct ldb_control *) +ldb_debug: void (struct ldb_context *, enum ldb_debug_level, const char *, ...) +ldb_debug_add: void (struct ldb_context *, const char *, ...) +ldb_debug_end: void (struct ldb_context *, enum ldb_debug_level) +ldb_debug_set: void (struct ldb_context *, enum ldb_debug_level, const char *, ...) +ldb_delete: int (struct ldb_context *, struct ldb_dn *) +ldb_dn_add_base: bool (struct ldb_dn *, struct ldb_dn *) +ldb_dn_add_base_fmt: bool (struct ldb_dn *, const char *, ...) +ldb_dn_add_child: bool (struct ldb_dn *, struct ldb_dn *) +ldb_dn_add_child_fmt: bool (struct ldb_dn *, const char *, ...) +ldb_dn_add_child_val: bool (struct ldb_dn *, const char *, struct ldb_val) +ldb_dn_alloc_casefold: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_alloc_linearized: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_canonical_ex_string: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_canonical_string: char *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_check_local: bool (struct ldb_module *, struct ldb_dn *) +ldb_dn_check_special: bool (struct ldb_dn *, const char *) +ldb_dn_compare: int (struct ldb_dn *, struct ldb_dn *) +ldb_dn_compare_base: int (struct ldb_dn *, struct ldb_dn *) +ldb_dn_copy: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_escape_value: char *(TALLOC_CTX *, struct ldb_val) +ldb_dn_extended_add_syntax: int (struct ldb_context *, unsigned int, const struct ldb_dn_extended_syntax *) +ldb_dn_extended_filter: void (struct ldb_dn *, const char * const *) +ldb_dn_extended_syntax_by_name: const struct ldb_dn_extended_syntax *(struct ldb_context *, const char *) +ldb_dn_from_ldb_val: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const struct ldb_val *) +ldb_dn_get_casefold: const char *(struct ldb_dn *) +ldb_dn_get_comp_num: int (struct ldb_dn *) +ldb_dn_get_component_name: const char *(struct ldb_dn *, unsigned int) +ldb_dn_get_component_val: const struct ldb_val *(struct ldb_dn *, unsigned int) +ldb_dn_get_extended_comp_num: int (struct ldb_dn *) +ldb_dn_get_extended_component: const struct ldb_val *(struct ldb_dn *, const char *) +ldb_dn_get_extended_linearized: char *(TALLOC_CTX *, struct ldb_dn *, int) +ldb_dn_get_ldb_context: struct ldb_context *(struct ldb_dn *) +ldb_dn_get_linearized: const char *(struct ldb_dn *) +ldb_dn_get_parent: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *) +ldb_dn_get_rdn_name: const char *(struct ldb_dn *) +ldb_dn_get_rdn_val: const struct ldb_val *(struct ldb_dn *) +ldb_dn_has_extended: bool (struct ldb_dn *) +ldb_dn_is_null: bool (struct ldb_dn *) +ldb_dn_is_special: bool (struct ldb_dn *) +ldb_dn_is_valid: bool (struct ldb_dn *) +ldb_dn_map_local: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) +ldb_dn_map_rebase_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) +ldb_dn_map_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *) +ldb_dn_minimise: bool (struct ldb_dn *) +ldb_dn_new: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *) +ldb_dn_new_fmt: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *, ...) +ldb_dn_remove_base_components: bool (struct ldb_dn *, unsigned int) +ldb_dn_remove_child_components: bool (struct ldb_dn *, unsigned int) +ldb_dn_remove_extended_components: void (struct ldb_dn *) +ldb_dn_replace_components: bool (struct ldb_dn *, struct ldb_dn *) +ldb_dn_set_component: int (struct ldb_dn *, int, const char *, const struct ldb_val) +ldb_dn_set_extended_component: int (struct ldb_dn *, const char *, const struct ldb_val *) +ldb_dn_update_components: int (struct ldb_dn *, const struct ldb_dn *) +ldb_dn_validate: bool (struct ldb_dn *) +ldb_dump_results: void (struct ldb_context *, struct ldb_result *, FILE *) +ldb_error_at: int (struct ldb_context *, int, const char *, const char *, int) +ldb_errstring: const char *(struct ldb_context *) +ldb_extended: int (struct ldb_context *, const char *, void *, struct ldb_result **) +ldb_extended_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_filter_attrs: int (struct ldb_context *, const struct ldb_message *, const char * const *, struct ldb_message *) +ldb_filter_attrs_in_place: int (struct ldb_message *, const char * const *) +ldb_filter_from_tree: char *(TALLOC_CTX *, const struct ldb_parse_tree *) +ldb_get_config_basedn: struct ldb_dn *(struct ldb_context *) +ldb_get_create_perms: unsigned int (struct ldb_context *) +ldb_get_default_basedn: struct ldb_dn *(struct ldb_context *) +ldb_get_event_context: struct tevent_context *(struct ldb_context *) +ldb_get_flags: unsigned int (struct ldb_context *) +ldb_get_opaque: void *(struct ldb_context *, const char *) +ldb_get_root_basedn: struct ldb_dn *(struct ldb_context *) +ldb_get_schema_basedn: struct ldb_dn *(struct ldb_context *) +ldb_global_init: int (void) +ldb_handle_get_event_context: struct tevent_context *(struct ldb_handle *) +ldb_handle_new: struct ldb_handle *(TALLOC_CTX *, struct ldb_context *) +ldb_handle_use_global_event_context: void (struct ldb_handle *) +ldb_handler_copy: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *) +ldb_handler_fold: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *) +ldb_init: struct ldb_context *(TALLOC_CTX *, struct tevent_context *) +ldb_ldif_message_redacted_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *) +ldb_ldif_message_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *) +ldb_ldif_parse_modrdn: int (struct ldb_context *, const struct ldb_ldif *, TALLOC_CTX *, struct ldb_dn **, struct ldb_dn **, bool *, struct ldb_dn **, struct ldb_dn **) +ldb_ldif_read: struct ldb_ldif *(struct ldb_context *, int (*)(void *), void *) +ldb_ldif_read_file: struct ldb_ldif *(struct ldb_context *, FILE *) +ldb_ldif_read_file_state: struct ldb_ldif *(struct ldb_context *, struct ldif_read_file_state *) +ldb_ldif_read_free: void (struct ldb_context *, struct ldb_ldif *) +ldb_ldif_read_string: struct ldb_ldif *(struct ldb_context *, const char **) +ldb_ldif_write: int (struct ldb_context *, int (*)(void *, const char *, ...), void *, const struct ldb_ldif *) +ldb_ldif_write_file: int (struct ldb_context *, FILE *, const struct ldb_ldif *) +ldb_ldif_write_redacted_trace_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *) +ldb_ldif_write_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *) +ldb_load_modules: int (struct ldb_context *, const char **) +ldb_map_add: int (struct ldb_module *, struct ldb_request *) +ldb_map_delete: int (struct ldb_module *, struct ldb_request *) +ldb_map_init: int (struct ldb_module *, const struct ldb_map_attribute *, const struct ldb_map_objectclass *, const char * const *, const char *, const char *) +ldb_map_modify: int (struct ldb_module *, struct ldb_request *) +ldb_map_rename: int (struct ldb_module *, struct ldb_request *) +ldb_map_search: int (struct ldb_module *, struct ldb_request *) +ldb_match_message: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, enum ldb_scope, bool *) +ldb_match_msg: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope) +ldb_match_msg_error: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope, bool *) +ldb_match_msg_objectclass: int (const struct ldb_message *, const char *) +ldb_match_scope: int (struct ldb_context *, struct ldb_dn *, struct ldb_dn *, enum ldb_scope) +ldb_mod_register_control: int (struct ldb_module *, const char *) +ldb_modify: int (struct ldb_context *, const struct ldb_message *) +ldb_modify_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_module_call_chain: char *(struct ldb_request *, TALLOC_CTX *) +ldb_module_connect_backend: int (struct ldb_context *, const char *, const char **, struct ldb_module **) +ldb_module_done: int (struct ldb_request *, struct ldb_control **, struct ldb_extended *, int) +ldb_module_flags: uint32_t (struct ldb_context *) +ldb_module_get_ctx: struct ldb_context *(struct ldb_module *) +ldb_module_get_name: const char *(struct ldb_module *) +ldb_module_get_ops: const struct ldb_module_ops *(struct ldb_module *) +ldb_module_get_private: void *(struct ldb_module *) +ldb_module_init_chain: int (struct ldb_context *, struct ldb_module *) +ldb_module_load_list: int (struct ldb_context *, const char **, struct ldb_module *, struct ldb_module **) +ldb_module_new: struct ldb_module *(TALLOC_CTX *, struct ldb_context *, const char *, const struct ldb_module_ops *) +ldb_module_next: struct ldb_module *(struct ldb_module *) +ldb_module_popt_options: struct poptOption **(struct ldb_context *) +ldb_module_send_entry: int (struct ldb_request *, struct ldb_message *, struct ldb_control **) +ldb_module_send_referral: int (struct ldb_request *, char *) +ldb_module_set_next: void (struct ldb_module *, struct ldb_module *) +ldb_module_set_private: void (struct ldb_module *, void *) +ldb_modules_hook: int (struct ldb_context *, enum ldb_module_hook_type) +ldb_modules_list_from_string: const char **(struct ldb_context *, TALLOC_CTX *, const char *) +ldb_modules_load: int (const char *, const char *) +ldb_msg_add: int (struct ldb_message *, const struct ldb_message_element *, int) +ldb_msg_add_distinguished_name: int (struct ldb_message *) +ldb_msg_add_empty: int (struct ldb_message *, const char *, int, struct ldb_message_element **) +ldb_msg_add_fmt: int (struct ldb_message *, const char *, const char *, ...) +ldb_msg_add_linearized_dn: int (struct ldb_message *, const char *, struct ldb_dn *) +ldb_msg_add_steal_string: int (struct ldb_message *, const char *, char *) +ldb_msg_add_steal_value: int (struct ldb_message *, const char *, struct ldb_val *) +ldb_msg_add_string: int (struct ldb_message *, const char *, const char *) +ldb_msg_add_string_flags: int (struct ldb_message *, const char *, const char *, int) +ldb_msg_add_value: int (struct ldb_message *, const char *, const struct ldb_val *, struct ldb_message_element **) +ldb_msg_append_fmt: int (struct ldb_message *, int, const char *, const char *, ...) +ldb_msg_append_linearized_dn: int (struct ldb_message *, const char *, struct ldb_dn *, int) +ldb_msg_append_steal_string: int (struct ldb_message *, const char *, char *, int) +ldb_msg_append_steal_value: int (struct ldb_message *, const char *, struct ldb_val *, int) +ldb_msg_append_string: int (struct ldb_message *, const char *, const char *, int) +ldb_msg_append_value: int (struct ldb_message *, const char *, const struct ldb_val *, int) +ldb_msg_canonicalize: struct ldb_message *(struct ldb_context *, const struct ldb_message *) +ldb_msg_check_string_attribute: int (const struct ldb_message *, const char *, const char *) +ldb_msg_copy: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *) +ldb_msg_copy_attr: int (struct ldb_message *, const char *, const char *) +ldb_msg_copy_shallow: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *) +ldb_msg_diff: struct ldb_message *(struct ldb_context *, struct ldb_message *, struct ldb_message *) +ldb_msg_difference: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message *, struct ldb_message *, struct ldb_message **) +ldb_msg_element_add_value: int (TALLOC_CTX *, struct ldb_message_element *, const struct ldb_val *) +ldb_msg_element_compare: int (struct ldb_message_element *, struct ldb_message_element *) +ldb_msg_element_compare_name: int (struct ldb_message_element *, struct ldb_message_element *) +ldb_msg_element_equal_ordered: bool (const struct ldb_message_element *, const struct ldb_message_element *) +ldb_msg_element_is_inaccessible: bool (const struct ldb_message_element *) +ldb_msg_element_mark_inaccessible: void (struct ldb_message_element *) +ldb_msg_elements_take_ownership: int (struct ldb_message *) +ldb_msg_find_attr_as_bool: int (const struct ldb_message *, const char *, int) +ldb_msg_find_attr_as_dn: struct ldb_dn *(struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, const char *) +ldb_msg_find_attr_as_double: double (const struct ldb_message *, const char *, double) +ldb_msg_find_attr_as_int: int (const struct ldb_message *, const char *, int) +ldb_msg_find_attr_as_int64: int64_t (const struct ldb_message *, const char *, int64_t) +ldb_msg_find_attr_as_string: const char *(const struct ldb_message *, const char *, const char *) +ldb_msg_find_attr_as_uint: unsigned int (const struct ldb_message *, const char *, unsigned int) +ldb_msg_find_attr_as_uint64: uint64_t (const struct ldb_message *, const char *, uint64_t) +ldb_msg_find_common_values: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message_element *, struct ldb_message_element *, uint32_t) +ldb_msg_find_duplicate_val: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message_element *, struct ldb_val **, uint32_t) +ldb_msg_find_element: struct ldb_message_element *(const struct ldb_message *, const char *) +ldb_msg_find_ldb_val: const struct ldb_val *(const struct ldb_message *, const char *) +ldb_msg_find_val: struct ldb_val *(const struct ldb_message_element *, struct ldb_val *) +ldb_msg_new: struct ldb_message *(TALLOC_CTX *) +ldb_msg_normalize: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_message **) +ldb_msg_remove_attr: void (struct ldb_message *, const char *) +ldb_msg_remove_element: void (struct ldb_message *, struct ldb_message_element *) +ldb_msg_remove_inaccessible: void (struct ldb_message *) +ldb_msg_rename_attr: int (struct ldb_message *, const char *, const char *) +ldb_msg_sanity_check: int (struct ldb_context *, const struct ldb_message *) +ldb_msg_shrink_to_fit: void (struct ldb_message *) +ldb_msg_sort_elements: void (struct ldb_message *) +ldb_next_del_trans: int (struct ldb_module *) +ldb_next_end_trans: int (struct ldb_module *) +ldb_next_init: int (struct ldb_module *) +ldb_next_prepare_commit: int (struct ldb_module *) +ldb_next_read_lock: int (struct ldb_module *) +ldb_next_read_unlock: int (struct ldb_module *) +ldb_next_remote_request: int (struct ldb_module *, struct ldb_request *) +ldb_next_request: int (struct ldb_module *, struct ldb_request *) +ldb_next_start_trans: int (struct ldb_module *) +ldb_op_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_options_copy: const char **(TALLOC_CTX *, const char **) +ldb_options_find: const char *(struct ldb_context *, const char **, const char *) +ldb_options_get: const char **(struct ldb_context *) +ldb_pack_data: int (struct ldb_context *, const struct ldb_message *, struct ldb_val *, uint32_t) +ldb_parse_control_from_string: struct ldb_control *(struct ldb_context *, TALLOC_CTX *, const char *) +ldb_parse_control_strings: struct ldb_control **(struct ldb_context *, TALLOC_CTX *, const char **) +ldb_parse_tree: struct ldb_parse_tree *(TALLOC_CTX *, const char *) +ldb_parse_tree_attr_replace: void (struct ldb_parse_tree *, const char *, const char *) +ldb_parse_tree_copy_shallow: struct ldb_parse_tree *(TALLOC_CTX *, const struct ldb_parse_tree *) +ldb_parse_tree_get_attr: const char *(const struct ldb_parse_tree *) +ldb_parse_tree_walk: int (struct ldb_parse_tree *, int (*)(struct ldb_parse_tree *, void *), void *) +ldb_qsort: void (void * const, size_t, size_t, void *, ldb_qsort_cmp_fn_t) +ldb_register_backend: int (const char *, ldb_connect_fn, bool) +ldb_register_extended_match_rule: int (struct ldb_context *, const struct ldb_extended_match_rule *) +ldb_register_hook: int (ldb_hook_fn) +ldb_register_module: int (const struct ldb_module_ops *) +ldb_register_redact_callback: int (struct ldb_context *, ldb_redact_fn, struct ldb_module *) +ldb_rename: int (struct ldb_context *, struct ldb_dn *, struct ldb_dn *) +ldb_reply_add_control: int (struct ldb_reply *, const char *, bool, void *) +ldb_reply_get_control: struct ldb_control *(struct ldb_reply *, const char *) +ldb_req_get_custom_flags: uint32_t (struct ldb_request *) +ldb_req_is_untrusted: bool (struct ldb_request *) +ldb_req_location: const char *(struct ldb_request *) +ldb_req_mark_trusted: void (struct ldb_request *) +ldb_req_mark_untrusted: void (struct ldb_request *) +ldb_req_set_custom_flags: void (struct ldb_request *, uint32_t) +ldb_req_set_location: void (struct ldb_request *, const char *) +ldb_request: int (struct ldb_context *, struct ldb_request *) +ldb_request_add_control: int (struct ldb_request *, const char *, bool, void *) +ldb_request_done: int (struct ldb_request *, int) +ldb_request_get_control: struct ldb_control *(struct ldb_request *, const char *) +ldb_request_get_status: int (struct ldb_request *) +ldb_request_replace_control: int (struct ldb_request *, const char *, bool, void *) +ldb_request_set_state: void (struct ldb_request *, int) +ldb_reset_err_string: void (struct ldb_context *) +ldb_save_controls: int (struct ldb_control *, struct ldb_request *, struct ldb_control ***) +ldb_schema_attribute_add: int (struct ldb_context *, const char *, unsigned int, const char *) +ldb_schema_attribute_add_with_syntax: int (struct ldb_context *, const char *, unsigned int, const struct ldb_schema_syntax *) +ldb_schema_attribute_by_name: const struct ldb_schema_attribute *(struct ldb_context *, const char *) +ldb_schema_attribute_fill_with_syntax: int (struct ldb_context *, TALLOC_CTX *, const char *, unsigned int, const struct ldb_schema_syntax *, struct ldb_schema_attribute *) +ldb_schema_attribute_remove: void (struct ldb_context *, const char *) +ldb_schema_attribute_remove_flagged: void (struct ldb_context *, unsigned int) +ldb_schema_attribute_set_override_handler: void (struct ldb_context *, ldb_attribute_handler_override_fn_t, void *) +ldb_schema_set_override_GUID_index: void (struct ldb_context *, const char *, const char *) +ldb_schema_set_override_indexlist: void (struct ldb_context *, bool) +ldb_search: int (struct ldb_context *, TALLOC_CTX *, struct ldb_result **, struct ldb_dn *, enum ldb_scope, const char * const *, const char *, ...) +ldb_search_default_callback: int (struct ldb_request *, struct ldb_reply *) +ldb_sequence_number: int (struct ldb_context *, enum ldb_sequence_type, uint64_t *) +ldb_set_create_perms: void (struct ldb_context *, unsigned int) +ldb_set_debug: int (struct ldb_context *, void (*)(void *, enum ldb_debug_level, const char *, va_list), void *) +ldb_set_debug_stderr: int (struct ldb_context *) +ldb_set_default_dns: void (struct ldb_context *) +ldb_set_errstring: void (struct ldb_context *, const char *) +ldb_set_event_context: void (struct ldb_context *, struct tevent_context *) +ldb_set_flags: void (struct ldb_context *, unsigned int) +ldb_set_modules_dir: void (struct ldb_context *, const char *) +ldb_set_opaque: int (struct ldb_context *, const char *, void *) +ldb_set_require_private_event_context: void (struct ldb_context *) +ldb_set_timeout: int (struct ldb_context *, struct ldb_request *, int) +ldb_set_timeout_from_prev_req: int (struct ldb_context *, struct ldb_request *, struct ldb_request *) +ldb_set_utf8_default: void (struct ldb_context *) +ldb_set_utf8_fns: void (struct ldb_context *, void *, char *(*)(void *, void *, const char *, size_t)) +ldb_setup_wellknown_attributes: int (struct ldb_context *) +ldb_should_b64_encode: int (struct ldb_context *, const struct ldb_val *) +ldb_standard_syntax_by_name: const struct ldb_schema_syntax *(struct ldb_context *, const char *) +ldb_strerror: const char *(int) +ldb_string_to_time: time_t (const char *) +ldb_string_utc_to_time: time_t (const char *) +ldb_timestring: char *(TALLOC_CTX *, time_t) +ldb_timestring_utc: char *(TALLOC_CTX *, time_t) +ldb_transaction_cancel: int (struct ldb_context *) +ldb_transaction_cancel_noerr: int (struct ldb_context *) +ldb_transaction_commit: int (struct ldb_context *) +ldb_transaction_prepare_commit: int (struct ldb_context *) +ldb_transaction_start: int (struct ldb_context *) +ldb_unpack_data: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *) +ldb_unpack_data_flags: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *, unsigned int) +ldb_unpack_get_format: int (const struct ldb_val *, uint32_t *) +ldb_val_dup: struct ldb_val (TALLOC_CTX *, const struct ldb_val *) +ldb_val_equal_exact: int (const struct ldb_val *, const struct ldb_val *) +ldb_val_map_local: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *) +ldb_val_map_remote: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *) +ldb_val_string_cmp: int (const struct ldb_val *, const char *) +ldb_val_to_time: int (const struct ldb_val *, time_t *) +ldb_valid_attr_name: int (const char *) +ldb_vdebug: void (struct ldb_context *, enum ldb_debug_level, const char *, va_list) +ldb_wait: int (struct ldb_handle *, enum ldb_wait_type) diff --git a/lib/ldb/ABI/pyldb-util-2.6.2.sigs b/lib/ldb/ABI/pyldb-util-2.6.2.sigs new file mode 100644 index 00000000000..164a806b2ff --- /dev/null +++ b/lib/ldb/ABI/pyldb-util-2.6.2.sigs @@ -0,0 +1,3 @@ +pyldb_Dn_FromDn: PyObject *(struct ldb_dn *) +pyldb_Object_AsDn: bool (TALLOC_CTX *, PyObject *, struct ldb_context *, struct ldb_dn **) +pyldb_check_type: bool (PyObject *, const char *) diff --git a/lib/ldb/wscript b/lib/ldb/wscript index 7e02309c1d5..bc14410618b 100644 --- a/lib/ldb/wscript +++ b/lib/ldb/wscript @@ -2,7 +2,7 @@ APPNAME = 'ldb' # For Samba 4.17.x -VERSION = '2.6.1' +VERSION = '2.6.2' import sys, os -- 2.25.1 From b7af8aa2552e0690aac58fb98e3134b71f678ece Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Thu, 28 Apr 2022 20:34:36 +1200 Subject: [PATCH 37/41] CVE-2023-0225 CVE-2020-25720 s4/dsdb/util: Add functions for dsHeuristics 28, 29 These are the newly-added AttributeAuthorizationOnLDAPAdd and BlockOwnerImplicitRights. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14810 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15276 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett (cherry picked from commit 0af5706b559e89c77123ed174b41fd3d01705aa5) [abartlet@samba.org This patch is needed for a clean backport of CVE-2023-0225 as these constants are used in the acl_modify test even when this behaviour is not itself used.] --- libds/common/flags.h | 2 ++ source4/dsdb/samdb/ldb_modules/util.c | 40 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/libds/common/flags.h b/libds/common/flags.h index 75e04b0c488..bee1016b294 100644 --- a/libds/common/flags.h +++ b/libds/common/flags.h @@ -258,6 +258,8 @@ #define DS_HR_KVNOEMUW2K 0x00000011 #define DS_HR_TWENTIETH_CHAR 0x00000014 +#define DS_HR_ATTR_AUTHZ_ON_LDAP_ADD 0x0000001C +#define DS_HR_BLOCK_OWNER_IMPLICIT_RIGHTS 0x0000001D #define DS_HR_THIRTIETH_CHAR 0x0000001E #define DS_HR_FOURTIETH_CHAR 0x00000028 #define DS_HR_FIFTIETH_CHAR 0x00000032 diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c index 9e00aedd09e..c2949f0734d 100644 --- a/source4/dsdb/samdb/ldb_modules/util.c +++ b/source4/dsdb/samdb/ldb_modules/util.c @@ -1433,6 +1433,46 @@ bool dsdb_do_list_object(struct ldb_module *module, return result; } +bool dsdb_attribute_authz_on_ldap_add(struct ldb_module *module, + TALLOC_CTX *mem_ctx, + struct ldb_request *parent) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + bool result = false; + const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module, + tmp_ctx, + parent); + if (hr_val != NULL && hr_val->length >= DS_HR_ATTR_AUTHZ_ON_LDAP_ADD) { + uint8_t val = hr_val->data[DS_HR_ATTR_AUTHZ_ON_LDAP_ADD - 1]; + if (val != '0' && val != '2') { + result = true; + } + } + + talloc_free(tmp_ctx); + return result; +} + +bool dsdb_block_owner_implicit_rights(struct ldb_module *module, + TALLOC_CTX *mem_ctx, + struct ldb_request *parent) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + bool result = false; + const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module, + tmp_ctx, + parent); + if (hr_val != NULL && hr_val->length >= DS_HR_BLOCK_OWNER_IMPLICIT_RIGHTS) { + uint8_t val = hr_val->data[DS_HR_BLOCK_OWNER_IMPLICIT_RIGHTS - 1]; + if (val != '0' && val != '2') { + result = true; + } + } + + talloc_free(tmp_ctx); + return result; +} + /* show the chain of requests, useful for debugging async requests */ -- 2.25.1 From 307b2e65d51903f6805460a2633ebe809d4052ab Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Tue, 6 Sep 2022 19:23:13 +1200 Subject: [PATCH 38/41] CVE-2023-0225 CVE-2020-25720 pydsdb: Add dsHeuristics constant definitions We want to be able to use these values in Python tests. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14810 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15276 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett (cherry picked from commit cc709077822a39227174b91ed2345c2bd603f61f) [abartlet@samba.org This patch is needed for a clean backport of CVE-2023-0225 as these constants are used in the acl_modify test even when this behaviour is not itself used.] --- source4/dsdb/pydsdb.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index bcfc7e95478..626d849a561 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -1665,6 +1665,36 @@ MODULE_INIT_FUNC(dsdb) ADD_DSDB_FLAG(DS_NTDSDSA_OPT_DISABLE_NTDSCONN_XLATE); ADD_DSDB_FLAG(DS_NTDSDSA_OPT_DISABLE_SPN_REGISTRATION); + /* dsHeuristics character indexes (see MS-ADTS 7.1.1.2.4.1.2) */ + ADD_DSDB_FLAG(DS_HR_SUPFIRSTLASTANR); + ADD_DSDB_FLAG(DS_HR_SUPLASTFIRSTANR); + ADD_DSDB_FLAG(DS_HR_DOLISTOBJECT); + ADD_DSDB_FLAG(DS_HR_DONICKRES); + ADD_DSDB_FLAG(DS_HR_LDAP_USEPERMMOD); + ADD_DSDB_FLAG(DS_HR_HIDEDSID); + ADD_DSDB_FLAG(DS_HR_BLOCK_ANONYMOUS_OPS); + ADD_DSDB_FLAG(DS_HR_ALLOW_ANON_NSPI); + ADD_DSDB_FLAG(DS_HR_USER_PASSWORD_SUPPORT); + ADD_DSDB_FLAG(DS_HR_TENTH_CHAR); + ADD_DSDB_FLAG(DS_HR_SPECIFY_GUID_ON_ADD); + ADD_DSDB_FLAG(DS_HR_NO_STANDARD_SD); + ADD_DSDB_FLAG(DS_HR_ALLOW_NONSECURE_PWD_OPS); + ADD_DSDB_FLAG(DS_HR_NO_PROPAGATE_ON_NOCHANGE); + ADD_DSDB_FLAG(DS_HR_COMPUTE_ANR_STATS); + ADD_DSDB_FLAG(DS_HR_ADMINSDEXMASK); + ADD_DSDB_FLAG(DS_HR_KVNOEMUW2K); + + ADD_DSDB_FLAG(DS_HR_TWENTIETH_CHAR); + ADD_DSDB_FLAG(DS_HR_ATTR_AUTHZ_ON_LDAP_ADD); + ADD_DSDB_FLAG(DS_HR_BLOCK_OWNER_IMPLICIT_RIGHTS); + ADD_DSDB_FLAG(DS_HR_THIRTIETH_CHAR); + ADD_DSDB_FLAG(DS_HR_FOURTIETH_CHAR); + ADD_DSDB_FLAG(DS_HR_FIFTIETH_CHAR); + ADD_DSDB_FLAG(DS_HR_SIXTIETH_CHAR); + ADD_DSDB_FLAG(DS_HR_SEVENTIETH_CHAR); + ADD_DSDB_FLAG(DS_HR_EIGHTIETH_CHAR); + ADD_DSDB_FLAG(DS_HR_NINETIETH_CHAR); + ADD_DSDB_FLAG(NTDSCONN_KCC_GC_TOPOLOGY); ADD_DSDB_FLAG(NTDSCONN_KCC_RING_TOPOLOGY); ADD_DSDB_FLAG(NTDSCONN_KCC_MINIMIZE_HOPS_TOPOLOGY); -- 2.25.1 From 54691236fc80a932f2069eef0aa21d6818445503 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 4 Jan 2023 21:37:49 +1300 Subject: [PATCH 39/41] CVE-2023-0225 pytest/acl: test deleting dNSHostName as unprivileged user BUG: https://bugzilla.samba.org/show_bug.cgi?id=15276 Signed-off-by: Douglas Bagnall Reviewed-by: Joseph Sutton Reviewed-by: Andrew Bartlett [abartlet@samba.org The self.set_heuristic(samba.dsdb.DS_HR_ATTR_AUTHZ_ON_LDAP_ADD, b'11') in the test setUp() is unused in this test but is included as a clean backport, so the fact that the server does not implement this is unimportant] --- selftest/knownfail.d/dns-host-name-deletion | 2 + source4/dsdb/tests/python/acl_modify.py | 236 ++++++++++++++++++++ source4/selftest/tests.py | 1 + 3 files changed, 239 insertions(+) create mode 100644 selftest/knownfail.d/dns-host-name-deletion create mode 100755 source4/dsdb/tests/python/acl_modify.py diff --git a/selftest/knownfail.d/dns-host-name-deletion b/selftest/knownfail.d/dns-host-name-deletion new file mode 100644 index 00000000000..ac11619ffc3 --- /dev/null +++ b/selftest/knownfail.d/dns-host-name-deletion @@ -0,0 +1,2 @@ +^samba4.ldap.acl_modify.python\(.*\).__main__.AclModifyTests.test_modify_delete_dns_host_name_ldif_unspecified\(.*\) +^samba4.ldap.acl_modify.python\(.*\).__main__.AclModifyTests.test_modify_delete_dns_host_name_unspecified\(.*\) diff --git a/source4/dsdb/tests/python/acl_modify.py b/source4/dsdb/tests/python/acl_modify.py new file mode 100755 index 00000000000..c85748a764f --- /dev/null +++ b/source4/dsdb/tests/python/acl_modify.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +import optparse +import sys +sys.path.insert(0, "bin/python") +import samba + +from samba.tests.subunitrun import SubunitOptions, TestProgram + +import samba.getopt as options + +from ldb import ERR_INSUFFICIENT_ACCESS_RIGHTS +from ldb import Message, MessageElement, Dn +from ldb import FLAG_MOD_REPLACE, FLAG_MOD_DELETE +from samba.dcerpc import security + +from samba.auth import system_session +from samba import gensec, sd_utils +from samba.samdb import SamDB +from samba.credentials import Credentials, DONT_USE_KERBEROS +import samba.tests +import samba.dsdb + + +parser = optparse.OptionParser("acl.py [options] ") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) + +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) + +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] +if "://" not in host: + ldaphost = "ldap://%s" % host +else: + ldaphost = host + start = host.rindex("://") + host = host.lstrip(start + 3) + +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) +creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + +# +# Tests start here +# + + +class AclTests(samba.tests.TestCase): + + def setUp(self): + super(AclTests, self).setUp() + + strict_checking = samba.tests.env_get_var_value('STRICT_CHECKING', allow_missing=True) + if strict_checking is None: + strict_checking = '1' + self.strict_checking = bool(int(strict_checking)) + + self.ldb_admin = SamDB(ldaphost, credentials=creds, session_info=system_session(lp), lp=lp) + self.base_dn = self.ldb_admin.domain_dn() + self.domain_sid = security.dom_sid(self.ldb_admin.get_domain_sid()) + self.user_pass = "samba123@" + self.configuration_dn = self.ldb_admin.get_config_basedn().get_linearized() + self.sd_utils = sd_utils.SDUtils(self.ldb_admin) + self.addCleanup(self.delete_admin_connection) + # used for anonymous login + self.creds_tmp = Credentials() + self.creds_tmp.set_username("") + self.creds_tmp.set_password("") + self.creds_tmp.set_domain(creds.get_domain()) + self.creds_tmp.set_realm(creds.get_realm()) + self.creds_tmp.set_workstation(creds.get_workstation()) + print("baseDN: %s" % self.base_dn) + + # set AttributeAuthorizationOnLDAPAdd and BlockOwnerImplicitRights + self.set_heuristic(samba.dsdb.DS_HR_ATTR_AUTHZ_ON_LDAP_ADD, b'11') + + def set_heuristic(self, index, values): + self.assertGreater(index, 0) + self.assertLess(index, 30) + self.assertIsInstance(values, bytes) + + # Get the old "dSHeuristics" if it was set + dsheuristics = self.ldb_admin.get_dsheuristics() + # Reset the "dSHeuristics" as they were before + self.addCleanup(self.ldb_admin.set_dsheuristics, dsheuristics) + # Set the "dSHeuristics" to activate the correct behaviour + default_heuristics = b"000000000100000000020000000003" + if dsheuristics is None: + dsheuristics = b"" + dsheuristics += default_heuristics[len(dsheuristics):] + dsheuristics = (dsheuristics[:index - 1] + + values + + dsheuristics[index - 1 + len(values):]) + self.ldb_admin.set_dsheuristics(dsheuristics) + + def get_user_dn(self, name): + return "CN=%s,CN=Users,%s" % (name, self.base_dn) + + def get_ldb_connection(self, target_username, target_password): + creds_tmp = Credentials() + creds_tmp.set_username(target_username) + creds_tmp.set_password(target_password) + creds_tmp.set_domain(creds.get_domain()) + creds_tmp.set_realm(creds.get_realm()) + creds_tmp.set_workstation(creds.get_workstation()) + creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() + | gensec.FEATURE_SEAL) + creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop + ldb_target = SamDB(url=ldaphost, credentials=creds_tmp, lp=lp) + return ldb_target + + # Test if we have any additional groups for users than default ones + def assert_user_no_group_member(self, username): + res = self.ldb_admin.search(self.base_dn, expression="(distinguishedName=%s)" % self.get_user_dn(username)) + try: + self.assertEqual(res[0]["memberOf"][0], "") + except KeyError: + pass + else: + self.fail() + + def delete_admin_connection(self): + del self.sd_utils + del self.ldb_admin + + +class AclModifyTests(AclTests): + + def setup_computer_with_hostname(self, account_name): + ou_dn = f'OU={account_name},{self.base_dn}' + dn = f'CN={account_name},{ou_dn}' + + user, password = "mouse", "mus musculus 123!" + self.addCleanup(self.ldb_admin.deleteuser, user) + + self.ldb_admin.newuser(user, password) + self.ldb_user = self.get_ldb_connection(user, password) + + self.addCleanup(self.ldb_admin.delete, ou_dn, + controls=["tree_delete:0"]) + self.ldb_admin.create_ou(ou_dn) + + self.ldb_admin.add({ + 'dn': dn, + 'objectClass': 'computer', + 'sAMAccountName': account_name + '$', + }) + + host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}' + + m = Message(Dn(self.ldb_admin, dn)) + m['dNSHostName'] = MessageElement(host_name, + FLAG_MOD_REPLACE, + 'dNSHostName') + + self.ldb_admin.modify(m) + return host_name, dn + + def test_modify_delete_dns_host_name_specified(self): + '''Test deleting dNSHostName''' + account_name = self.id().rsplit(".", 1)[1][:63] + host_name, dn = self.setup_computer_with_hostname(account_name) + + m = Message(Dn(self.ldb_user, dn)) + m['dNSHostName'] = MessageElement(host_name, + FLAG_MOD_DELETE, + 'dNSHostName') + + self.assertRaisesLdbError( + ERR_INSUFFICIENT_ACCESS_RIGHTS, + "User able to delete dNSHostName (with specified name)", + self.ldb_user.modify, m) + + def test_modify_delete_dns_host_name_unspecified(self): + '''Test deleting dNSHostName''' + account_name = self.id().rsplit(".", 1)[1][:63] + host_name, dn = self.setup_computer_with_hostname(account_name) + + m = Message(Dn(self.ldb_user, dn)) + m['dNSHostName'] = MessageElement([], + FLAG_MOD_DELETE, + 'dNSHostName') + + self.assertRaisesLdbError( + ERR_INSUFFICIENT_ACCESS_RIGHTS, + "User able to delete dNSHostName (without specified name)", + self.ldb_user.modify, m) + + def test_modify_delete_dns_host_name_ldif_specified(self): + '''Test deleting dNSHostName''' + account_name = self.id().rsplit(".", 1)[1][:63] + host_name, dn = self.setup_computer_with_hostname(account_name) + + ldif = f""" +dn: {dn} +changetype: modify +delete: dNSHostName +dNSHostName: {host_name} +""" + self.assertRaisesLdbError( + ERR_INSUFFICIENT_ACCESS_RIGHTS, + "User able to delete dNSHostName (with specified name)", + self.ldb_user.modify_ldif, ldif) + + def test_modify_delete_dns_host_name_ldif_unspecified(self): + '''Test deleting dNSHostName''' + account_name = self.id().rsplit(".", 1)[1][:63] + host_name, dn = self.setup_computer_with_hostname(account_name) + + ldif = f""" +dn: {dn} +changetype: modify +delete: dNSHostName +""" + self.assertRaisesLdbError( + ERR_INSUFFICIENT_ACCESS_RIGHTS, + "User able to delete dNSHostName (without specific name)", + self.ldb_user.modify_ldif, ldif) + + +ldb = SamDB(ldaphost, credentials=creds, session_info=system_session(lp), lp=lp) + +TestProgram(module=__name__, opts=subunitopts) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index a09945f40da..29801bc190b 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1418,6 +1418,7 @@ for env in all_fl_envs + ["schema_dc"]: plantestsuite("samba4.ldap.possibleInferiors.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/samdb/ldb_modules/tests/possibleinferiors.py"), "ldap://$SERVER", '-U"$USERNAME%$PASSWORD"', "-W$DOMAIN"]) plantestsuite_loadlist("samba4.ldap.secdesc.python(%s)" % env, env, [python, os.path.join(DSDB_PYTEST_DIR, "sec_descriptor.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.ldap.acl.python(%s)" % env, env, ["STRICT_CHECKING=0", python, os.path.join(DSDB_PYTEST_DIR, "acl.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) + plantestsuite_loadlist("samba4.ldap.acl_modify.python(%s)" % env, env, ["STRICT_CHECKING=0", python, os.path.join(DSDB_PYTEST_DIR, "acl_modify.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) for env in all_fl_envs + ["schema_dc", "ad_dc_no_ntlm"]: if env != "fl2000dc": -- 2.25.1 From 888c6ae8177d87e408722f67cc03359ae2533402 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 9 Jan 2023 11:22:34 +1300 Subject: [PATCH 40/41] CVE-2023-0225 s4-acl: Don't return early if dNSHostName element has no values This early return would mistakenly allow an unprivileged user to delete the dNSHostName attribute by making an LDAP modify request with no values. We should no longer allow this. Add or replace operations with no values and no privileges are disallowed. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15276 Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- selftest/knownfail.d/dns-host-name-deletion | 2 -- source4/dsdb/samdb/ldb_modules/acl.c | 12 +++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 selftest/knownfail.d/dns-host-name-deletion diff --git a/selftest/knownfail.d/dns-host-name-deletion b/selftest/knownfail.d/dns-host-name-deletion deleted file mode 100644 index ac11619ffc3..00000000000 --- a/selftest/knownfail.d/dns-host-name-deletion +++ /dev/null @@ -1,2 +0,0 @@ -^samba4.ldap.acl_modify.python\(.*\).__main__.AclModifyTests.test_modify_delete_dns_host_name_ldif_unspecified\(.*\) -^samba4.ldap.acl_modify.python\(.*\).__main__.AclModifyTests.test_modify_delete_dns_host_name_unspecified\(.*\) diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index 5c57dd25faa..78a5ddf71ec 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -798,11 +798,6 @@ static int acl_check_dns_host_name(TALLOC_CTX *mem_ctx, NULL }; - if (el->num_values == 0) { - return LDB_SUCCESS; - } - dnsHostName = &el->values[0]; - tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return ldb_oom(ldb); @@ -948,6 +943,13 @@ static int acl_check_dns_host_name(TALLOC_CTX *mem_ctx, --account_name_len; } + /* Check for add or replace requests with no value. */ + if (el->num_values == 0) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + dnsHostName = &el->values[0]; + dnsHostName_str = (const char *)dnsHostName->data; dns_host_name_len = dnsHostName->length; -- 2.25.1 From 04e5a7eb03a1e913f34d77b7b6c2353b41ef546a Mon Sep 17 00:00:00 2001 From: Rob van der Linde Date: Mon, 27 Feb 2023 14:06:23 +1300 Subject: [PATCH 41/41] CVE-2023-0922 set default ldap client sasl wrapping to seal This avoids sending new or reset passwords in the clear (integrity protected only) from samba-tool in particular. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15315 Signed-off-by: Rob van der Linde Signed-off-by: Andrew Bartlett Reviewed-by: Joseph Sutton --- .../ldap/clientldapsaslwrapping.xml | 27 +++++++++---------- lib/param/loadparm.c | 2 +- python/samba/tests/auth_log.py | 2 +- source3/param/loadparm.c | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/docs-xml/smbdotconf/ldap/clientldapsaslwrapping.xml b/docs-xml/smbdotconf/ldap/clientldapsaslwrapping.xml index 3152f0682dd..21bd2090057 100644 --- a/docs-xml/smbdotconf/ldap/clientldapsaslwrapping.xml +++ b/docs-xml/smbdotconf/ldap/clientldapsaslwrapping.xml @@ -18,25 +18,24 @@ - This option is needed in the case of Domain Controllers enforcing - the usage of signed LDAP connections (e.g. Windows 2000 SP3 or higher). - LDAP sign and seal can be controlled with the registry key - "HKLM\System\CurrentControlSet\Services\ - NTDS\Parameters\LDAPServerIntegrity" - on the Windows server side. - + This option is needed firstly to secure the privacy of + administrative connections from samba-tool, + including in particular new or reset passwords for users. For + this reason the default is seal. - - Depending on the used KRB5 library (MIT and older Heimdal versions) - it is possible that the message "integrity only" is not supported. - In this case, sign is just an alias for - seal. + Additionally, winbindd and the + net tool can use LDAP to communicate with + Domain Controllers, so this option also controls the level of + privacy for those connections. All supported AD DC versions + will enforce the usage of at least signed LDAP connections by + default, so a value of at least sign is + required in practice. - The default value is sign. That implies synchronizing the time + The default value is seal. That implies synchronizing the time with the KDC in the case of using Kerberos. -sign +seal diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c index fc0dc4df83f..f70823fe366 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -2992,7 +2992,7 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) lpcfg_do_global_parameter(lp_ctx, "ldap debug threshold", "10"); - lpcfg_do_global_parameter(lp_ctx, "client ldap sasl wrapping", "sign"); + lpcfg_do_global_parameter(lp_ctx, "client ldap sasl wrapping", "seal"); lpcfg_do_global_parameter(lp_ctx, "mdns name", "netbios"); diff --git a/python/samba/tests/auth_log.py b/python/samba/tests/auth_log.py index 9949b0abe4d..b0f4840563d 100644 --- a/python/samba/tests/auth_log.py +++ b/python/samba/tests/auth_log.py @@ -470,7 +470,7 @@ class AuthLogTests(samba.tests.auth_log_base.AuthLogTestBase): def isLastExpectedMessage(msg): return (msg["type"] == "Authorization" and msg["Authorization"]["serviceDescription"] == "LDAP" and - msg["Authorization"]["transportProtection"] == "SIGN" and + msg["Authorization"]["transportProtection"] == "SEAL" and msg["Authorization"]["authType"] == "krb5") self.samdb = SamDB(url="ldap://%s" % os.environ["SERVER"], diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index 0ebdd315bd1..97d02037a89 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -756,7 +756,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) Globals.ldap_debug_level = 0; Globals.ldap_debug_threshold = 10; - Globals.client_ldap_sasl_wrapping = ADS_AUTH_SASL_SIGN; + Globals.client_ldap_sasl_wrapping = ADS_AUTH_SASL_SEAL; Globals.ldap_server_require_strong_auth = LDAP_SERVER_REQUIRE_STRONG_AUTH_YES; -- 2.25.1