From e265696f2bfc2b5e81626ba933827ae6e2a3b459 Mon Sep 17 00:00:00 2001 From: xinghe Date: Sat, 1 Apr 2023 06:26:51 +0000 Subject: [PATCH] fix CVE-2023-0225 CVE-2023-0614 CVE-2023-0922 (cherry picked from commit 683f80ad7389af29811d54c09a96074e7f287c2a) --- backport-0001-CVE-2023-0225.patch | 93 ++ backport-0001-CVE-2023-0614.patch | 174 +++ backport-0002-CVE-2023-0225.patch | 68 ++ backport-0002-CVE-2023-0614.patch | 137 +++ backport-0003-CVE-2023-0225.patch | 291 +++++ backport-0003-CVE-2023-0614.patch | 77 ++ backport-0004-CVE-2023-0225.patch | 67 ++ backport-0004-CVE-2023-0614.patch | 79 ++ backport-0005-CVE-2023-0614.patch | 86 ++ backport-0006-CVE-2023-0614.patch | 36 + backport-0007-CVE-2023-0614.patch | 72 ++ backport-0008-CVE-2023-0614.patch | 140 +++ backport-0009-CVE-2023-0614.patch | 409 +++++++ backport-0010-CVE-2023-0614.patch | 48 + backport-0011-CVE-2023-0614.patch | 104 ++ backport-0012-CVE-2023-0614.patch | 62 ++ backport-0013-CVE-2023-0614.patch | 68 ++ backport-0014-CVE-2023-0614.patch | 1224 +++++++++++++++++++++ backport-0015-CVE-2023-0614.patch | 1275 +++++++++++++++++++++ backport-0016-CVE-2023-0614.patch | 293 +++++ backport-0017-CVE-2023-0614.patch | 143 +++ backport-0018-CVE-2023-0614.patch | 58 + backport-0019-CVE-2023-0614.patch | 49 + backport-0020-CVE-2023-0614.patch | 34 + backport-0021-CVE-2023-0614.patch | 205 ++++ backport-0022-CVE-2023-0614.patch | 66 ++ backport-0023-CVE-2023-0614.patch | 102 ++ backport-0024-CVE-2023-0614.patch | 52 + backport-0025-CVE-2023-0614.patch | 218 ++++ backport-0026-CVE-2023-0614.patch | 1711 +++++++++++++++++++++++++++++ backport-0027-CVE-2023-0614.patch | 54 + backport-0028-CVE-2023-0614.patch | 131 +++ backport-0029-CVE-2023-0614.patch | 147 +++ backport-0030-CVE-2023-0614.patch | 155 +++ backport-0031-CVE-2023-0614.patch | 108 ++ backport-0032-CVE-2023-0614.patch | 49 + backport-0033-CVE-2023-0614.patch | 43 + backport-0034-CVE-2023-0614.patch | 319 ++++++ backport-0035-CVE-2023-0614.patch | 39 + backport-CVE-2023-0922.patch | 108 ++ samba.spec | 52 +- 41 files changed, 8644 insertions(+), 2 deletions(-) create mode 100644 backport-0001-CVE-2023-0225.patch create mode 100644 backport-0001-CVE-2023-0614.patch create mode 100644 backport-0002-CVE-2023-0225.patch create mode 100644 backport-0002-CVE-2023-0614.patch create mode 100644 backport-0003-CVE-2023-0225.patch create mode 100644 backport-0003-CVE-2023-0614.patch create mode 100644 backport-0004-CVE-2023-0225.patch create mode 100644 backport-0004-CVE-2023-0614.patch create mode 100644 backport-0005-CVE-2023-0614.patch create mode 100644 backport-0006-CVE-2023-0614.patch create mode 100644 backport-0007-CVE-2023-0614.patch create mode 100644 backport-0008-CVE-2023-0614.patch create mode 100644 backport-0009-CVE-2023-0614.patch create mode 100644 backport-0010-CVE-2023-0614.patch create mode 100644 backport-0011-CVE-2023-0614.patch create mode 100644 backport-0012-CVE-2023-0614.patch create mode 100644 backport-0013-CVE-2023-0614.patch create mode 100644 backport-0014-CVE-2023-0614.patch create mode 100644 backport-0015-CVE-2023-0614.patch create mode 100644 backport-0016-CVE-2023-0614.patch create mode 100644 backport-0017-CVE-2023-0614.patch create mode 100644 backport-0018-CVE-2023-0614.patch create mode 100644 backport-0019-CVE-2023-0614.patch create mode 100644 backport-0020-CVE-2023-0614.patch create mode 100644 backport-0021-CVE-2023-0614.patch create mode 100644 backport-0022-CVE-2023-0614.patch create mode 100644 backport-0023-CVE-2023-0614.patch create mode 100644 backport-0024-CVE-2023-0614.patch create mode 100644 backport-0025-CVE-2023-0614.patch create mode 100644 backport-0026-CVE-2023-0614.patch create mode 100644 backport-0027-CVE-2023-0614.patch create mode 100644 backport-0028-CVE-2023-0614.patch create mode 100644 backport-0029-CVE-2023-0614.patch create mode 100644 backport-0030-CVE-2023-0614.patch create mode 100644 backport-0031-CVE-2023-0614.patch create mode 100644 backport-0032-CVE-2023-0614.patch create mode 100644 backport-0033-CVE-2023-0614.patch create mode 100644 backport-0034-CVE-2023-0614.patch create mode 100644 backport-0035-CVE-2023-0614.patch create mode 100644 backport-CVE-2023-0922.patch diff --git a/backport-0001-CVE-2023-0225.patch b/backport-0001-CVE-2023-0225.patch new file mode 100644 index 0000000..1f6fe7d --- /dev/null +++ b/backport-0001-CVE-2023-0225.patch @@ -0,0 +1,93 @@ +From 0bc08daf4c191c370cb218e9a0ecac51b8c36468 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Thu, 28 Apr 2022 20:34:36 +1200 +Subject: [PATCH 1/4] 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.] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17833 +--- + 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 \ No newline at end of file diff --git a/backport-0001-CVE-2023-0614.patch b/backport-0001-CVE-2023-0614.patch new file mode 100644 index 0000000..47c3049 --- /dev/null +++ b/backport-0001-CVE-2023-0614.patch @@ -0,0 +1,174 @@ +From 197d1f09158ee0575cd92c461ed12bdbf4efc074 Mon Sep 17 00:00:00 2001 +From: Andrew Bartlett +Date: Mon, 13 Mar 2023 14:25:56 +1300 +Subject: [PATCH 01/35] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0002-CVE-2023-0225.patch b/backport-0002-CVE-2023-0225.patch new file mode 100644 index 0000000..445e1b6 --- /dev/null +++ b/backport-0002-CVE-2023-0225.patch @@ -0,0 +1,68 @@ +From 47f8a529885d321c4f787832d5934757656e8094 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Tue, 6 Sep 2022 19:23:13 +1200 +Subject: [PATCH 2/4] 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.] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17833 +--- + 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 \ No newline at end of file diff --git a/backport-0002-CVE-2023-0614.patch b/backport-0002-CVE-2023-0614.patch new file mode 100644 index 0000000..1653dec --- /dev/null +++ b/backport-0002-CVE-2023-0614.patch @@ -0,0 +1,137 @@ +From ba8c9eae1392bf21d3e36530eda1780defd22300 Mon Sep 17 00:00:00 2001 +From: Andrew Bartlett +Date: Mon, 13 Mar 2023 17:20:00 +1300 +Subject: [PATCH 02/35] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0003-CVE-2023-0225.patch b/backport-0003-CVE-2023-0225.patch new file mode 100644 index 0000000..5a997ac --- /dev/null +++ b/backport-0003-CVE-2023-0225.patch @@ -0,0 +1,291 @@ +From 417fda9bfde2f8db315ea0460fb7da6e780b859c Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Wed, 4 Jan 2023 21:37:49 +1300 +Subject: [PATCH 3/4] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17833 +--- + 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 c6bf668aa9c..9ef9600d886 100755 +--- a/source4/selftest/tests.py ++++ b/source4/selftest/tests.py +@@ -1417,6 +1417,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 \ No newline at end of file diff --git a/backport-0003-CVE-2023-0614.patch b/backport-0003-CVE-2023-0614.patch new file mode 100644 index 0000000..9608e66 --- /dev/null +++ b/backport-0003-CVE-2023-0614.patch @@ -0,0 +1,77 @@ +From e7aa14a5405735234b989bdeba384c7c9249257a Mon Sep 17 00:00:00 2001 +From: Andrew Bartlett +Date: Fri, 3 Mar 2023 10:31:40 +1300 +Subject: [PATCH 01/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + source4/dsdb/tests/python/large_ldap.py | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/source4/dsdb/tests/python/large_ldap.py b/source4/dsdb/tests/python/large_ldap.py +index 0805119a700..f7569607cb2 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,' +@@ -283,9 +286,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 \ No newline at end of file diff --git a/backport-0004-CVE-2023-0225.patch b/backport-0004-CVE-2023-0225.patch new file mode 100644 index 0000000..5638424 --- /dev/null +++ b/backport-0004-CVE-2023-0225.patch @@ -0,0 +1,67 @@ +From 0d753cc8f2b072175f994ede8b3a541303a8a2d5 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Mon, 9 Jan 2023 11:22:34 +1300 +Subject: [PATCH 4/4] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17833 +--- + 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 4098ae2d671..b602520ca2b 100644 +--- a/source4/dsdb/samdb/ldb_modules/acl.c ++++ b/source4/dsdb/samdb/ldb_modules/acl.c +@@ -900,11 +900,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); +@@ -1050,6 +1045,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 + diff --git a/backport-0004-CVE-2023-0614.patch b/backport-0004-CVE-2023-0614.patch new file mode 100644 index 0000000..3b6d2e9 --- /dev/null +++ b/backport-0004-CVE-2023-0614.patch @@ -0,0 +1,79 @@ +From ad6945f667329d75174cfb9e90786f811c579355 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 27 Jan 2023 07:57:27 +1300 +Subject: [PATCH 02/34] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0005-CVE-2023-0614.patch b/backport-0005-CVE-2023-0614.patch new file mode 100644 index 0000000..62df673 --- /dev/null +++ b/backport-0005-CVE-2023-0614.patch @@ -0,0 +1,86 @@ +From 89f882b49d2669ba8b51e9b5de644164f5c1995e Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Tue, 7 Feb 2023 09:29:51 +1300 +Subject: [PATCH 03/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0006-CVE-2023-0614.patch b/backport-0006-CVE-2023-0614.patch new file mode 100644 index 0000000..f90a1d9 --- /dev/null +++ b/backport-0006-CVE-2023-0614.patch @@ -0,0 +1,36 @@ +From 003c65be1c6d8f8ea853896a75b315ef8e98cfb3 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 27 Jan 2023 08:00:32 +1300 +Subject: [PATCH 04/34] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0007-CVE-2023-0614.patch b/backport-0007-CVE-2023-0614.patch new file mode 100644 index 0000000..cc293a5 --- /dev/null +++ b/backport-0007-CVE-2023-0614.patch @@ -0,0 +1,72 @@ +From b01d3ae3261264236504475a26c54ab45dd2175f Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 27 Jan 2023 08:28:36 +1300 +Subject: [PATCH 05/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0008-CVE-2023-0614.patch b/backport-0008-CVE-2023-0614.patch new file mode 100644 index 0000000..a56265d --- /dev/null +++ b/backport-0008-CVE-2023-0614.patch @@ -0,0 +1,140 @@ +From c818c16912f5af248b91f0688c3e57012db89011 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 27 Jan 2023 08:29:33 +1300 +Subject: [PATCH 06/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0009-CVE-2023-0614.patch b/backport-0009-CVE-2023-0614.patch new file mode 100644 index 0000000..8c50625 --- /dev/null +++ b/backport-0009-CVE-2023-0614.patch @@ -0,0 +1,409 @@ +From e7445d18badee6c3b1bbee48c689eb2629c31681 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Wed, 15 Feb 2023 12:34:51 +1300 +Subject: [PATCH 07/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0010-CVE-2023-0614.patch b/backport-0010-CVE-2023-0614.patch new file mode 100644 index 0000000..4e79b61 --- /dev/null +++ b/backport-0010-CVE-2023-0614.patch @@ -0,0 +1,48 @@ +From 936bfcb6ef804d2224072f3770ca09fe2596ee1f Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Wed, 15 Feb 2023 14:08:57 +1300 +Subject: [PATCH 08/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0011-CVE-2023-0614.patch b/backport-0011-CVE-2023-0614.patch new file mode 100644 index 0000000..9542362 --- /dev/null +++ b/backport-0011-CVE-2023-0614.patch @@ -0,0 +1,104 @@ +From 83217ce77381f8faa3cde948e15a36db234d3033 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:23:42 +1300 +Subject: [PATCH 09/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0012-CVE-2023-0614.patch b/backport-0012-CVE-2023-0614.patch new file mode 100644 index 0000000..7861a1c --- /dev/null +++ b/backport-0012-CVE-2023-0614.patch @@ -0,0 +1,62 @@ +From a9b625bc8ab00b83b55bcd21ba0df48e73e4df29 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:26:04 +1300 +Subject: [PATCH 10/34] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0013-CVE-2023-0614.patch b/backport-0013-CVE-2023-0614.patch new file mode 100644 index 0000000..c0f961b --- /dev/null +++ b/backport-0013-CVE-2023-0614.patch @@ -0,0 +1,68 @@ +From 8b7374780e3e7b67e51a1b54a09bf48d89fa9f26 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:27:38 +1300 +Subject: [PATCH 11/34] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0014-CVE-2023-0614.patch b/backport-0014-CVE-2023-0614.patch new file mode 100644 index 0000000..ed63e9f --- /dev/null +++ b/backport-0014-CVE-2023-0614.patch @@ -0,0 +1,1224 @@ +From 4f8b4ce403ff68ca26d33d7272276052829c96f7 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:29:03 +1300 +Subject: [PATCH 12/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0015-CVE-2023-0614.patch b/backport-0015-CVE-2023-0614.patch new file mode 100644 index 0000000..5e88588 --- /dev/null +++ b/backport-0015-CVE-2023-0614.patch @@ -0,0 +1,1275 @@ +From fd0f06e43f03bc8b8c5b4a978a119e4401c02160 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:30:19 +1300 +Subject: [PATCH 13/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0016-CVE-2023-0614.patch b/backport-0016-CVE-2023-0614.patch new file mode 100644 index 0000000..0b8c1d8 --- /dev/null +++ b/backport-0016-CVE-2023-0614.patch @@ -0,0 +1,293 @@ +From 8e84201f508b2893b6ec9383c656cccb08b148ef Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Mon, 27 Feb 2023 10:31:52 +1300 +Subject: [PATCH 14/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0017-CVE-2023-0614.patch b/backport-0017-CVE-2023-0614.patch new file mode 100644 index 0000000..fcd8daa --- /dev/null +++ b/backport-0017-CVE-2023-0614.patch @@ -0,0 +1,143 @@ +From 9e07df4e268fd49358382e8d90c2d151efb66569 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Tue, 7 Feb 2023 09:35:24 +1300 +Subject: [PATCH 15/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + .../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 \ No newline at end of file diff --git a/backport-0018-CVE-2023-0614.patch b/backport-0018-CVE-2023-0614.patch new file mode 100644 index 0000000..d2f85e5 --- /dev/null +++ b/backport-0018-CVE-2023-0614.patch @@ -0,0 +1,58 @@ +From 13e0b2190c802dc876b465a201b8dc9f35f9720c Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Tue, 7 Feb 2023 09:48:37 +1300 +Subject: [PATCH 16/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0019-CVE-2023-0614.patch b/backport-0019-CVE-2023-0614.patch new file mode 100644 index 0000000..ec217de --- /dev/null +++ b/backport-0019-CVE-2023-0614.patch @@ -0,0 +1,49 @@ +From e167524e306241ba14f925f412e3f89828ed8c61 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Thu, 25 Aug 2022 20:15:33 +1200 +Subject: [PATCH 17/34] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0020-CVE-2023-0614.patch b/backport-0020-CVE-2023-0614.patch new file mode 100644 index 0000000..3547310 --- /dev/null +++ b/backport-0020-CVE-2023-0614.patch @@ -0,0 +1,34 @@ +From 829157cf07ec887a7164600a53bdc688aa4717be Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Tue, 7 Feb 2023 09:25:48 +1300 +Subject: [PATCH 18/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0021-CVE-2023-0614.patch b/backport-0021-CVE-2023-0614.patch new file mode 100644 index 0000000..0db2c42 --- /dev/null +++ b/backport-0021-CVE-2023-0614.patch @@ -0,0 +1,205 @@ +From 009026fae8eea85789bd912cbf397423480485da Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 27 Jan 2023 08:32:41 +1300 +Subject: [PATCH 19/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0022-CVE-2023-0614.patch b/backport-0022-CVE-2023-0614.patch new file mode 100644 index 0000000..228cd21 --- /dev/null +++ b/backport-0022-CVE-2023-0614.patch @@ -0,0 +1,66 @@ +From b23fb132e63a6d3d791469614593c43906686ac9 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:31:54 +1300 +Subject: [PATCH 20/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0023-CVE-2023-0614.patch b/backport-0023-CVE-2023-0614.patch new file mode 100644 index 0000000..2be9e01 --- /dev/null +++ b/backport-0023-CVE-2023-0614.patch @@ -0,0 +1,102 @@ +From 3127ba92bf91fa6f666552ac31cb06ffdc6d7e63 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Mon, 27 Feb 2023 13:40:33 +1300 +Subject: [PATCH 21/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0024-CVE-2023-0614.patch b/backport-0024-CVE-2023-0614.patch new file mode 100644 index 0000000..247eb14 --- /dev/null +++ b/backport-0024-CVE-2023-0614.patch @@ -0,0 +1,52 @@ +From 652fecd7d037992b89ed1a4eb17f9f467c2aadf7 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Mon, 27 Feb 2023 12:19:08 +1300 +Subject: [PATCH 22/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0025-CVE-2023-0614.patch b/backport-0025-CVE-2023-0614.patch new file mode 100644 index 0000000..78069a2 --- /dev/null +++ b/backport-0025-CVE-2023-0614.patch @@ -0,0 +1,218 @@ +From 3ebfe8666c02ab2de823457c68a922d0b0437cec Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Mon, 27 Feb 2023 13:55:36 +1300 +Subject: [PATCH 23/34] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0026-CVE-2023-0614.patch b/backport-0026-CVE-2023-0614.patch new file mode 100644 index 0000000..1fa10f6 --- /dev/null +++ b/backport-0026-CVE-2023-0614.patch @@ -0,0 +1,1711 @@ +From f87af853e2db99269acea572dcc109bc6f797aa9 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:34:29 +1300 +Subject: [PATCH 24/34] 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] + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 2f4d41f3441..17314f1b9ca 100644 +--- a/lib/ldb/common/ldb_match.c ++++ b/lib/ldb/common/ldb_match.c +@@ -98,6 +98,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; +@@ -139,6 +144,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; +@@ -209,6 +219,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; +@@ -370,6 +385,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); +@@ -443,6 +463,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]; +@@ -741,3 +766,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 \ No newline at end of file diff --git a/backport-0027-CVE-2023-0614.patch b/backport-0027-CVE-2023-0614.patch new file mode 100644 index 0000000..dffacf7 --- /dev/null +++ b/backport-0027-CVE-2023-0614.patch @@ -0,0 +1,54 @@ +From 976b0c37d82daf1269664a1fce8dfcca5770456b Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Mon, 27 Feb 2023 13:31:44 +1300 +Subject: [PATCH 25/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0028-CVE-2023-0614.patch b/backport-0028-CVE-2023-0614.patch new file mode 100644 index 0000000..4ced8ae --- /dev/null +++ b/backport-0028-CVE-2023-0614.patch @@ -0,0 +1,131 @@ +From 8cf6c358e27a3926397e0d5fbb562e76926589d9 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Thu, 16 Feb 2023 12:35:34 +1300 +Subject: [PATCH 26/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0029-CVE-2023-0614.patch b/backport-0029-CVE-2023-0614.patch new file mode 100644 index 0000000..bdd885b --- /dev/null +++ b/backport-0029-CVE-2023-0614.patch @@ -0,0 +1,147 @@ +From 94efa3fc3053a623a7a5c3a4a6428356bc334152 Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Tue, 14 Feb 2023 13:17:24 +1300 +Subject: [PATCH 27/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 17314f1b9ca..645cb695ef1 100644 +--- a/lib/ldb/common/ldb_match.c ++++ b/lib/ldb/common/ldb_match.c +@@ -98,11 +98,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; +@@ -144,11 +139,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; +@@ -219,11 +209,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; +@@ -385,11 +370,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); +@@ -463,11 +443,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]; +@@ -556,6 +531,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 + +@@ -580,6 +575,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 \ No newline at end of file diff --git a/backport-0030-CVE-2023-0614.patch b/backport-0030-CVE-2023-0614.patch new file mode 100644 index 0000000..8534930 --- /dev/null +++ b/backport-0030-CVE-2023-0614.patch @@ -0,0 +1,155 @@ +From a0c888bd0ed2ec5b4d84f9df241bebd5d428818c Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 3 Mar 2023 17:35:55 +1300 +Subject: [PATCH 28/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 645cb695ef1..8e27ecbe723 100644 +--- a/lib/ldb/common/ldb_match.c ++++ b/lib/ldb/common/ldb_match.c +@@ -38,10 +38,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 \ No newline at end of file diff --git a/backport-0031-CVE-2023-0614.patch b/backport-0031-CVE-2023-0614.patch new file mode 100644 index 0000000..6d808bc --- /dev/null +++ b/backport-0031-CVE-2023-0614.patch @@ -0,0 +1,108 @@ +From 62d100b64a25c740187f687dd058a543d43984ec Mon Sep 17 00:00:00 2001 +From: Joseph Sutton +Date: Fri, 24 Feb 2023 10:03:25 +1300 +Subject: [PATCH 29/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0032-CVE-2023-0614.patch b/backport-0032-CVE-2023-0614.patch new file mode 100644 index 0000000..c37f48e --- /dev/null +++ b/backport-0032-CVE-2023-0614.patch @@ -0,0 +1,49 @@ +From d857b406e44254b37aa91d0c8d226e417ce123ce Mon Sep 17 00:00:00 2001 +From: Andrew Bartlett +Date: Thu, 2 Mar 2023 16:31:17 +1300 +Subject: [PATCH 30/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0033-CVE-2023-0614.patch b/backport-0033-CVE-2023-0614.patch new file mode 100644 index 0000000..409473f --- /dev/null +++ b/backport-0033-CVE-2023-0614.patch @@ -0,0 +1,43 @@ +From 0151a2a4c4cea64b0d931aa0e227b187abf28280 Mon Sep 17 00:00:00 2001 +From: Andrew Bartlett +Date: Fri, 3 Mar 2023 16:49:00 +1300 +Subject: [PATCH 31/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0034-CVE-2023-0614.patch b/backport-0034-CVE-2023-0614.patch new file mode 100644 index 0000000..0665c5d --- /dev/null +++ b/backport-0034-CVE-2023-0614.patch @@ -0,0 +1,319 @@ +From 91ecb27e706ac4500fb0d5242cf412173f8a5e7a Mon Sep 17 00:00:00 2001 +From: Andrew Bartlett +Date: Thu, 2 Mar 2023 16:51:25 +1300 +Subject: [PATCH 32/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-0035-CVE-2023-0614.patch b/backport-0035-CVE-2023-0614.patch new file mode 100644 index 0000000..1622cbd --- /dev/null +++ b/backport-0035-CVE-2023-0614.patch @@ -0,0 +1,39 @@ +From bb092fc576868e30edf78136894472f95c4b039d Mon Sep 17 00:00:00 2001 +From: Andrew Bartlett +Date: Thu, 2 Mar 2023 17:24:15 +1300 +Subject: [PATCH 33/34] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17821 +--- + 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 \ No newline at end of file diff --git a/backport-CVE-2023-0922.patch b/backport-CVE-2023-0922.patch new file mode 100644 index 0000000..9133496 --- /dev/null +++ b/backport-CVE-2023-0922.patch @@ -0,0 +1,108 @@ +From faa7babcd8db9ef14398848d0c398578d1a79d85 Mon Sep 17 00:00:00 2001 +From: Rob van der Linde +Date: Mon, 27 Feb 2023 14:06:23 +1300 +Subject: [PATCH] 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 + +Conflict: NA +Reference: https://attachments.samba.org/attachment.cgi?id=17831 +--- + .../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 6ab7fa89db7..16cb0d47f31 100644 +--- a/lib/param/loadparm.c ++++ b/lib/param/loadparm.c +@@ -2990,7 +2990,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 d166b93d90a..8f9f487f82a 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 05a5ae20abe..12718ced9e7 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 + diff --git a/samba.spec b/samba.spec index 6fefe02..0f9d06f 100644 --- a/samba.spec +++ b/samba.spec @@ -5,7 +5,7 @@ %global talloc_version 2.3.4 %global tdb_version 1.4.7 %global tevent_version 0.13.0 -%global ldb_version 2.6.1 +%global ldb_version 2.6.1-2 %undefine _strict_symbol_defs_build @@ -48,7 +48,7 @@ Name: samba Version: 4.17.5 -Release: 2 +Release: 3 Summary: A suite for Linux to interoperate with Windows License: GPLv3+ and LGPLv3+ @@ -66,6 +66,48 @@ Source8: usershares.conf.vendor Source201: README.downgrade +Patch0001: backport-0001-CVE-2023-0614.patch +Patch0002: backport-0002-CVE-2023-0614.patch +Patch0003: backport-0003-CVE-2023-0614.patch +Patch0004: backport-0004-CVE-2023-0614.patch +Patch0005: backport-0005-CVE-2023-0614.patch +Patch0006: backport-0006-CVE-2023-0614.patch +Patch0007: backport-0007-CVE-2023-0614.patch +Patch0008: backport-0008-CVE-2023-0614.patch +Patch0009: backport-0009-CVE-2023-0614.patch +Patch0010: backport-0010-CVE-2023-0614.patch +Patch0011: backport-0011-CVE-2023-0614.patch +Patch0012: backport-0012-CVE-2023-0614.patch +Patch0013: backport-0013-CVE-2023-0614.patch +Patch0014: backport-0014-CVE-2023-0614.patch +Patch0015: backport-0015-CVE-2023-0614.patch +Patch0016: backport-0016-CVE-2023-0614.patch +Patch0017: backport-0017-CVE-2023-0614.patch +Patch0018: backport-0018-CVE-2023-0614.patch +Patch0019: backport-0019-CVE-2023-0614.patch +Patch0020: backport-0020-CVE-2023-0614.patch +Patch0021: backport-0021-CVE-2023-0614.patch +Patch0022: backport-0022-CVE-2023-0614.patch +Patch0023: backport-0023-CVE-2023-0614.patch +Patch0024: backport-0024-CVE-2023-0614.patch +Patch0025: backport-0025-CVE-2023-0614.patch +Patch0026: backport-0026-CVE-2023-0614.patch +Patch0027: backport-0027-CVE-2023-0614.patch +Patch0028: backport-0028-CVE-2023-0614.patch +Patch0029: backport-0029-CVE-2023-0614.patch +Patch0030: backport-0030-CVE-2023-0614.patch +Patch0031: backport-0031-CVE-2023-0614.patch +Patch0032: backport-0032-CVE-2023-0614.patch +Patch0033: backport-0033-CVE-2023-0614.patch +Patch0034: backport-0034-CVE-2023-0614.patch +Patch0035: backport-0035-CVE-2023-0614.patch +Patch0036: backport-0001-CVE-2023-0225.patch +Patch0037: backport-0002-CVE-2023-0225.patch +Patch0038: backport-0003-CVE-2023-0225.patch +Patch0039: backport-0004-CVE-2023-0225.patch +Patch0040: backport-CVE-2023-0922.patch + + BuildRequires: avahi-devel bison dbus-devel docbook-style-xsl e2fsprogs-devel flex gawk gnupg2 gnutls-devel >= 3.4.7 gpgme-devel BuildRequires: jansson-devel krb5-devel >= %{required_mit_krb5} libacl-devel libaio-devel libarchive-devel libattr-devel BuildRequires: libcap-devel libicu-devel libcmocka-devel libtirpc-devel libuuid-devel libxslt lmdb ncurses-devel openldap-devel @@ -3479,6 +3521,12 @@ fi %endif %changelog +* Sat Apr 01 2023 xinghe - 4.17.5-3 +- Type:cves +- ID:CVE-2023-0225 CVE-2023-0614 CVE-2023-0922 +- SUG:NA +- DESC:fix CVE-2023-0225 CVE-2023-0614 CVE-2023-0922 + * Wed Feb 08 2023 laokz - 4.17.5-2 - Type:enhancement - ID:NA -- Gitee