From 0e09a1ecd92b380dd318c2e07e13f476069a65a8 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet@samba.org>
Date: Mon, 13 Mar 2023 14:25:56 +1300
Subject: [PATCH 06/42] 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 <abartlet@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
(cherry picked from commit cad96f59a08192df927fb1df4e9787c7f70991a2)

[abartlet@samba.org Included in the security release as this
 makes the new large_ldap.py timeout test more reliable]
---
 lib/ldb/common/ldb_match.c | 60 +++++++++++++++++++++++++++++++-------
 1 file changed, 50 insertions(+), 10 deletions(-)

diff --git ldb/common/ldb_match.c ldb/common/ldb_match.c
index 2f4d41f3441..51376871b4c 100644
--- ldb/common/ldb_match.c
+++ 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.35.3


From 732e821e79e14956338504b560fe04e59304096a Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 27 Jan 2023 08:28:36 +1300
Subject: [PATCH 12/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 lib/ldb/common/ldb_msg.c     | 26 ++++++++++++++++++++++++++
 lib/ldb/include/ldb_module.h |  4 ++++
 2 files changed, 30 insertions(+)

diff --git ldb/common/ldb_msg.c ldb/common/ldb_msg.c
index 9cd7998e21c..cbc7e32b2ba 100644
--- ldb/common/ldb_msg.c
+++ 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 ldb/include/ldb_module.h ldb/include/ldb_module.h
index 4c7c85a17f0..8481fd3991a 100644
--- ldb/include/ldb_module.h
+++ 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.35.3


From a318adc74d80152bc414b1d56628b89a3eeff8b1 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Wed, 15 Feb 2023 12:34:51 +1300
Subject: [PATCH 14/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 lib/ldb/tests/ldb_filter_attrs_test.c | 171 +++++++++++++-------------
 1 file changed, 86 insertions(+), 85 deletions(-)

diff --git ldb/tests/ldb_filter_attrs_test.c ldb/tests/ldb_filter_attrs_test.c
index 7d555e0da2e..442d9c77ed2 100644
--- ldb/tests/ldb_filter_attrs_test.c
+++ ldb/tests/ldb_filter_attrs_test.c
@@ -36,6 +36,7 @@
 #include <stdarg.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <string.h>
 #include <setjmp.h>
 #include <cmocka.h>
 
@@ -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.35.3


From 03814be26daae1173d2f56ec6f1574886daf78aa Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Wed, 15 Feb 2023 14:08:57 +1300
Subject: [PATCH 15/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 lib/ldb/wscript | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git ldb/wscript ldb/wscript
index 6e72f2039a4..a0f04be5fe3 100644
--- ldb/wscript
+++ 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.35.3


From 0686a20946d6b87dc4931a2dea039c5979431b17 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:23:42 +1300
Subject: [PATCH 16/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 lib/ldb/common/ldb_pack.c    | 41 ++++++++++++++++++++++++++++++++++++
 lib/ldb/include/ldb_module.h |  4 ++++
 2 files changed, 45 insertions(+)

diff --git ldb/common/ldb_pack.c ldb/common/ldb_pack.c
index e7dd364008a..028d96a619a 100644
--- ldb/common/ldb_pack.c
+++ 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 ldb/include/ldb_module.h ldb/include/ldb_module.h
index 8481fd3991a..8c7f33496fb 100644
--- ldb/include/ldb_module.h
+++ 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.35.3


From 72a6b248079f4a2c048799f98c497abbd9cbeb2b Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:26:04 +1300
Subject: [PATCH 17/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>

[abartlet@samba.org Adapted to conflict from lack of new
ldb_ascii_toupper() in ldb_private.h]
---
 lib/ldb/common/ldb_msg.c      | 16 ++++++++++++++++
 lib/ldb/include/ldb_private.h |  3 +++
 2 files changed, 19 insertions(+)

diff --git ldb/common/ldb_msg.c ldb/common/ldb_msg.c
index cbc7e32b2ba..2ea2cce2e83 100644
--- ldb/common/ldb_msg.c
+++ 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 ldb/include/ldb_private.h ldb/include/ldb_private.h
index 4deb24691ca..338e71def6d 100644
--- ldb/include/ldb_private.h
+++ 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.35.3


From 5d84acf9588139d985d70eab0555e07bc099be46 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:27:38 +1300
Subject: [PATCH 18/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>

[abartlet@samba.org Adapted to conflict from lack of new
ldb_ascii_toupper() in ldb_private.h]
---
 lib/ldb/common/ldb_pack.c     | 6 +++---
 lib/ldb/include/ldb_private.h | 5 +++++
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git ldb/common/ldb_pack.c ldb/common/ldb_pack.c
index 028d96a619a..b0b0d64a5ba 100644
--- ldb/common/ldb_pack.c
+++ 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 ldb/include/ldb_private.h ldb/include/ldb_private.h
index 338e71def6d..ca43817d07a 100644
--- ldb/include/ldb_private.h
+++ 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.35.3


From 1774cb7d5a7525d25b577c873a5b0fa613e1588e Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:29:03 +1300
Subject: [PATCH 19/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 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 ldb/common/ldb_pack.c ldb/common/ldb_pack.c
index b0b0d64a5ba..f19ac73fa5e 100644
--- ldb/common/ldb_pack.c
+++ 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;j<el->num_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 ldb/include/ldb_module.h ldb/include/ldb_module.h
index 8c7f33496fb..105093cf38c 100644
--- ldb/include/ldb_module.h
+++ 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 ldb/tests/ldb_filter_attrs_in_place_test.c ldb/tests/ldb_filter_attrs_in_place_test.c
new file mode 100644
index 00000000000..bef961f8f9c
--- /dev/null
+++ 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 <abartlet@samba.org> 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#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 ldb/wscript ldb/wscript
index a0f04be5fe3..35143f3860c 100644
--- ldb/wscript
+++ 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.35.3


From b0675810254757b939c04555530fd71be470251d Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:30:19 +1300
Subject: [PATCH 20/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 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 ldb/common/ldb_pack.c ldb/common/ldb_pack.c
index f19ac73fa5e..28b9a8dfe07 100644
--- ldb/common/ldb_pack.c
+++ 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;j<el->num_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 ldb/include/ldb_module.h ldb/include/ldb_module.h
index 105093cf38c..4ae381ba5be 100644
--- ldb/include/ldb_module.h
+++ 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 ldb/tests/ldb_filter_attrs_in_place_test.c ldb/tests/ldb_filter_attrs_in_place_test.c
index bef961f8f9c..da333c73c99 100644
--- ldb/tests/ldb_filter_attrs_in_place_test.c
+++ 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.35.3


From 61f49609ce785ac6accee4a3ff82540184e8d6ea Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Mon, 27 Feb 2023 10:31:52 +1300
Subject: [PATCH 21/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 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 ldb/ldb_key_value/ldb_kv.h ldb/ldb_key_value/ldb_kv.h
index ac474b04b4c..7d5a40e76e9 100644
--- ldb/ldb_key_value/ldb_kv.h
+++ 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 ldb/ldb_key_value/ldb_kv_index.c ldb/ldb_key_value/ldb_kv_index.c
index d70e5f619ef..203266ea8c9 100644
--- ldb/ldb_key_value/ldb_kv_index.c
+++ 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 ldb/ldb_key_value/ldb_kv_search.c ldb/ldb_key_value/ldb_kv_search.c
index 46031b99c16..f3333510eab 100644
--- ldb/ldb_key_value/ldb_kv_search.c
+++ 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
-- 
2.35.3


From 0b331e6e2d6b4e8ad0bba4f439381eb4e1da9730 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:31:54 +1300
Subject: [PATCH 27/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 lib/ldb/common/ldb_parse.c   | 25 +++++++++++++++++++++++++
 lib/ldb/include/ldb_module.h |  3 +++
 2 files changed, 28 insertions(+)

diff --git ldb/common/ldb_parse.c ldb/common/ldb_parse.c
index f0045ad2093..2d102ff750e 100644
--- ldb/common/ldb_parse.c
+++ 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 ldb/include/ldb_module.h ldb/include/ldb_module.h
index 4ae381ba5be..bd369ed9512 100644
--- ldb/include/ldb_module.h
+++ 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.35.3


From 85924d5b94fd84902df9db197c2fa5c84f64b7ca Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:34:29 +1300
Subject: [PATCH 31/42] 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 <abartlet@samba.org>

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>

[abartlet@samba.org adapted due to Samba 4.17 and lower
 not having the patches for CVE-2020-25720 and 4.16 and lower
 not having the patches for CVE-2022-32743 ]
---
 lib/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 ldb/common/ldb_match.c ldb/common/ldb_match.c
index 51376871b4c..b0a33e939eb 100644
--- ldb/common/ldb_match.c
+++ ldb/common/ldb_match.c
@@ -99,6 +99,11 @@ static int ldb_match_present(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
+	if (ldb_msg_element_is_inaccessible(el)) {
+		*matched = false;
+		return LDB_SUCCESS;
+	}
+
 	a = ldb_schema_attribute_by_name(ldb, el->name);
 	if (!a) {
 		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
@@ -140,6 +145,11 @@ static int ldb_match_comparison(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
+	if (ldb_msg_element_is_inaccessible(el)) {
+		*matched = false;
+		return LDB_SUCCESS;
+	}
+
 	a = ldb_schema_attribute_by_name(ldb, el->name);
 	if (!a) {
 		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
@@ -210,6 +220,11 @@ static int ldb_match_equality(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
+	if (ldb_msg_element_is_inaccessible(el)) {
+		*matched = false;
+		return LDB_SUCCESS;
+	}
+
 	a = ldb_schema_attribute_by_name(ldb, el->name);
 	if (a == NULL) {
 		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
@@ -410,6 +425,11 @@ static int ldb_match_substring(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
+	if (ldb_msg_element_is_inaccessible(el)) {
+		*matched = false;
+		return LDB_SUCCESS;
+	}
+
 	for (i = 0; i < el->num_values; i++) {
 		int ret;
 		ret = ldb_wildcard_compare(ldb, tree, el->values[i], matched);
@@ -483,6 +503,11 @@ static int ldb_match_bitmask(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
+	if (ldb_msg_element_is_inaccessible(el)) {
+		*matched = false;
+		return LDB_SUCCESS;
+	}
+
 	for (i=0;i<el->num_values;i++) {
 		int ret;
 		struct ldb_val *v = &el->values[i];
@@ -781,3 +806,15 @@ int ldb_register_extended_match_rule(struct ldb_context *ldb,
 	return LDB_SUCCESS;
 }
 
+int ldb_register_redact_callback(struct ldb_context *ldb,
+				 ldb_redact_fn redact_fn,
+				 struct ldb_module *module)
+{
+	if (ldb->redact.callback != NULL) {
+		return LDB_ERR_ENTRY_ALREADY_EXISTS;
+	}
+
+	ldb->redact.callback = redact_fn;
+	ldb->redact.module = module;
+	return LDB_SUCCESS;
+}
diff --git ldb/include/ldb_module.h ldb/include/ldb_module.h
index bd369ed9512..0f14b1ad346 100644
--- ldb/include/ldb_module.h
+++ 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 ldb/include/ldb_private.h ldb/include/ldb_private.h
index ca43817d07a..b0a42f6421c 100644
--- ldb/include/ldb_private.h
+++ 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 ldb/ldb_key_value/ldb_kv_index.c ldb/ldb_key_value/ldb_kv_index.c
index 203266ea8c9..163052fecf7 100644
--- ldb/ldb_key_value/ldb_kv_index.c
+++ 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 ldb/ldb_key_value/ldb_kv_search.c ldb/ldb_key_value/ldb_kv_search.c
index f3333510eab..d187ba223e1 100644
--- ldb/ldb_key_value/ldb_kv_search.c
+++ 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
-- 
2.35.3


From ac02f91783e1058650f7b65daa21631f8957bc5f Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 14 Feb 2023 13:17:24 +1300
Subject: [PATCH 34/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 lib/lib/ldb-samba/ldb_matching_rules.c |  5 ---
 lib/ldb/common/ldb_match.c         | 56 +++++++++++++++++-------------
 2 files changed, 31 insertions(+), 30 deletions(-)

diff --git ldb/common/ldb_match.c ldb/common/ldb_match.c
index b0a33e939eb..267498560e6 100644
--- ldb/common/ldb_match.c
+++ ldb/common/ldb_match.c
@@ -99,11 +99,6 @@ static int ldb_match_present(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
-	if (ldb_msg_element_is_inaccessible(el)) {
-		*matched = false;
-		return LDB_SUCCESS;
-	}
-
 	a = ldb_schema_attribute_by_name(ldb, el->name);
 	if (!a) {
 		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
@@ -145,11 +140,6 @@ static int ldb_match_comparison(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
-	if (ldb_msg_element_is_inaccessible(el)) {
-		*matched = false;
-		return LDB_SUCCESS;
-	}
-
 	a = ldb_schema_attribute_by_name(ldb, el->name);
 	if (!a) {
 		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
@@ -220,11 +210,6 @@ static int ldb_match_equality(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
-	if (ldb_msg_element_is_inaccessible(el)) {
-		*matched = false;
-		return LDB_SUCCESS;
-	}
-
 	a = ldb_schema_attribute_by_name(ldb, el->name);
 	if (a == NULL) {
 		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
@@ -425,11 +410,6 @@ static int ldb_match_substring(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
-	if (ldb_msg_element_is_inaccessible(el)) {
-		*matched = false;
-		return LDB_SUCCESS;
-	}
-
 	for (i = 0; i < el->num_values; i++) {
 		int ret;
 		ret = ldb_wildcard_compare(ldb, tree, el->values[i], matched);
@@ -503,11 +483,6 @@ static int ldb_match_bitmask(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
-	if (ldb_msg_element_is_inaccessible(el)) {
-		*matched = false;
-		return LDB_SUCCESS;
-	}
-
 	for (i=0;i<el->num_values;i++) {
 		int ret;
 		struct ldb_val *v = &el->values[i];
@@ -596,6 +571,26 @@ static int ldb_match_extended(struct ldb_context *ldb,
 			      &tree->u.extended.value, matched);
 }
 
+static bool ldb_must_suppress_match(const struct ldb_message *msg,
+				    const struct ldb_parse_tree *tree)
+{
+	const char *attr = NULL;
+	struct ldb_message_element *el = NULL;
+
+	attr = ldb_parse_tree_get_attr(tree);
+	if (attr == NULL) {
+		return false;
+	}
+
+	/* find the message element */
+	el = ldb_msg_find_element(msg, attr);
+	if (el == NULL) {
+		return false;
+	}
+
+	return ldb_msg_element_is_inaccessible(el);
+}
+
 /*
   Check if a particular message will match the given filter
 
@@ -620,6 +615,17 @@ int ldb_match_message(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
+	/*
+	 * Suppress matches on confidential attributes (handled
+	 * manually in extended matches as these can do custom things
+	 * like read other parts of the DB or other attributes).
+	 */
+	if (tree->operation != LDB_OP_EXTENDED) {
+		if (ldb_must_suppress_match(msg, tree)) {
+			return LDB_SUCCESS;
+		}
+	}
+
 	switch (tree->operation) {
 	case LDB_OP_AND:
 		for (i=0;i<tree->u.list.num_elements;i++) {
-- 
2.35.3


From 1cef676dbc77fe34cd69c67d6c16d39af47893b6 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Fri, 3 Mar 2023 17:35:55 +1300
Subject: [PATCH 35/42] 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 <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
---
 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 ldb/common/ldb_match.c ldb/common/ldb_match.c
index 267498560e6..463a24ce3bc 100644
--- ldb/common/ldb_match.c
+++ ldb/common/ldb_match.c
@@ -39,10 +39,10 @@
 /*
   check if the scope matches in a search result
 */
-static int ldb_match_scope(struct ldb_context *ldb,
-			   struct ldb_dn *base,
-			   struct ldb_dn *dn,
-			   enum ldb_scope scope)
+int ldb_match_scope(struct ldb_context *ldb,
+		    struct ldb_dn *base,
+		    struct ldb_dn *dn,
+		    enum ldb_scope scope)
 {
 	int ret = 0;
 
diff --git ldb/include/ldb_private.h ldb/include/ldb_private.h
index b0a42f6421c..5e29de34f79 100644
--- ldb/include/ldb_private.h
+++ 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 ldb/ldb_key_value/ldb_kv_index.c ldb/ldb_key_value/ldb_kv_index.c
index 163052fecf7..aac0913f431 100644
--- ldb/ldb_key_value/ldb_kv_index.c
+++ 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 ldb/ldb_key_value/ldb_kv_search.c ldb/ldb_key_value/ldb_kv_search.c
index d187ba223e1..27f68caef01 100644
--- ldb/ldb_key_value/ldb_kv_search.c
+++ 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.35.3


From b5a3151092cb41db86c90145a290d0478d90191f Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power@suse.com>
Date: Mon, 20 Mar 2023 11:27:40 +0000
Subject: [PATCH 41/42] ldb: Release LDB 2.4.5

* CVE-2023-0614 Not-secret but access controlled LDAP attributes can be discovered (bug 15270)

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15270

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
[abartlet@samba.org Adapted to LDB 2.5 series in Samba 4.16]
[npower@samba.org Adapted to LDB 2.4 series in Samba 4.15]

squash
---
 lib/ldb/ABI/ldb-2.4.5.sigs        | 301 ++++++++++++++++++++++++++++++
 lib/ldb/ABI/pyldb-util-2.4.5.sigs |   3 +
 lib/ldb/wscript                   |   2 +-
 3 files changed, 305 insertions(+), 1 deletion(-)
 create mode 100644 lib/ldb/ABI/ldb-2.4.5.sigs
 create mode 100644 lib/ldb/ABI/pyldb-util-2.4.5.sigs

diff --git ldb/ABI/ldb-2.4.5.sigs ldb/ABI/ldb-2.4.5.sigs
new file mode 100644
index 00000000000..b4c5e20e8c7
--- /dev/null
+++ ldb/ABI/ldb-2.4.5.sigs
@@ -0,0 +1,301 @@
+ldb_add: int (struct ldb_context *, const struct ldb_message *)
+ldb_any_comparison: int (struct ldb_context *, void *, ldb_attr_handler_t, const struct ldb_val *, const struct ldb_val *)
+ldb_asprintf_errstring: void (struct ldb_context *, const char *, ...)
+ldb_attr_casefold: char *(TALLOC_CTX *, const char *)
+ldb_attr_dn: int (const char *)
+ldb_attr_in_list: int (const char * const *, const char *)
+ldb_attr_list_copy: const char **(TALLOC_CTX *, const char * const *)
+ldb_attr_list_copy_add: const char **(TALLOC_CTX *, const char * const *, const char *)
+ldb_base64_decode: int (char *)
+ldb_base64_encode: char *(TALLOC_CTX *, const char *, int)
+ldb_binary_decode: struct ldb_val (TALLOC_CTX *, const char *)
+ldb_binary_encode: char *(TALLOC_CTX *, struct ldb_val)
+ldb_binary_encode_string: char *(TALLOC_CTX *, const char *)
+ldb_build_add_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *)
+ldb_build_del_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *)
+ldb_build_extended_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const char *, void *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *)
+ldb_build_mod_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *)
+ldb_build_rename_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, struct ldb_dn *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *)
+ldb_build_search_req: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, const char *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *)
+ldb_build_search_req_ex: int (struct ldb_request **, struct ldb_context *, TALLOC_CTX *, struct ldb_dn *, enum ldb_scope, struct ldb_parse_tree *, const char * const *, struct ldb_control **, void *, ldb_request_callback_t, struct ldb_request *)
+ldb_casefold: char *(struct ldb_context *, TALLOC_CTX *, const char *, size_t)
+ldb_casefold_default: char *(void *, TALLOC_CTX *, const char *, size_t)
+ldb_check_critical_controls: int (struct ldb_control **)
+ldb_comparison_binary: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *)
+ldb_comparison_fold: int (struct ldb_context *, void *, const struct ldb_val *, const struct ldb_val *)
+ldb_connect: int (struct ldb_context *, const char *, unsigned int, const char **)
+ldb_control_to_string: char *(TALLOC_CTX *, const struct ldb_control *)
+ldb_controls_except_specified: struct ldb_control **(struct ldb_control **, TALLOC_CTX *, struct ldb_control *)
+ldb_debug: void (struct ldb_context *, enum ldb_debug_level, const char *, ...)
+ldb_debug_add: void (struct ldb_context *, const char *, ...)
+ldb_debug_end: void (struct ldb_context *, enum ldb_debug_level)
+ldb_debug_set: void (struct ldb_context *, enum ldb_debug_level, const char *, ...)
+ldb_delete: int (struct ldb_context *, struct ldb_dn *)
+ldb_dn_add_base: bool (struct ldb_dn *, struct ldb_dn *)
+ldb_dn_add_base_fmt: bool (struct ldb_dn *, const char *, ...)
+ldb_dn_add_child: bool (struct ldb_dn *, struct ldb_dn *)
+ldb_dn_add_child_fmt: bool (struct ldb_dn *, const char *, ...)
+ldb_dn_add_child_val: bool (struct ldb_dn *, const char *, struct ldb_val)
+ldb_dn_alloc_casefold: char *(TALLOC_CTX *, struct ldb_dn *)
+ldb_dn_alloc_linearized: char *(TALLOC_CTX *, struct ldb_dn *)
+ldb_dn_canonical_ex_string: char *(TALLOC_CTX *, struct ldb_dn *)
+ldb_dn_canonical_string: char *(TALLOC_CTX *, struct ldb_dn *)
+ldb_dn_check_local: bool (struct ldb_module *, struct ldb_dn *)
+ldb_dn_check_special: bool (struct ldb_dn *, const char *)
+ldb_dn_compare: int (struct ldb_dn *, struct ldb_dn *)
+ldb_dn_compare_base: int (struct ldb_dn *, struct ldb_dn *)
+ldb_dn_copy: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *)
+ldb_dn_escape_value: char *(TALLOC_CTX *, struct ldb_val)
+ldb_dn_extended_add_syntax: int (struct ldb_context *, unsigned int, const struct ldb_dn_extended_syntax *)
+ldb_dn_extended_filter: void (struct ldb_dn *, const char * const *)
+ldb_dn_extended_syntax_by_name: const struct ldb_dn_extended_syntax *(struct ldb_context *, const char *)
+ldb_dn_from_ldb_val: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const struct ldb_val *)
+ldb_dn_get_casefold: const char *(struct ldb_dn *)
+ldb_dn_get_comp_num: int (struct ldb_dn *)
+ldb_dn_get_component_name: const char *(struct ldb_dn *, unsigned int)
+ldb_dn_get_component_val: const struct ldb_val *(struct ldb_dn *, unsigned int)
+ldb_dn_get_extended_comp_num: int (struct ldb_dn *)
+ldb_dn_get_extended_component: const struct ldb_val *(struct ldb_dn *, const char *)
+ldb_dn_get_extended_linearized: char *(TALLOC_CTX *, struct ldb_dn *, int)
+ldb_dn_get_ldb_context: struct ldb_context *(struct ldb_dn *)
+ldb_dn_get_linearized: const char *(struct ldb_dn *)
+ldb_dn_get_parent: struct ldb_dn *(TALLOC_CTX *, struct ldb_dn *)
+ldb_dn_get_rdn_name: const char *(struct ldb_dn *)
+ldb_dn_get_rdn_val: const struct ldb_val *(struct ldb_dn *)
+ldb_dn_has_extended: bool (struct ldb_dn *)
+ldb_dn_is_null: bool (struct ldb_dn *)
+ldb_dn_is_special: bool (struct ldb_dn *)
+ldb_dn_is_valid: bool (struct ldb_dn *)
+ldb_dn_map_local: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *)
+ldb_dn_map_rebase_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *)
+ldb_dn_map_remote: struct ldb_dn *(struct ldb_module *, void *, struct ldb_dn *)
+ldb_dn_minimise: bool (struct ldb_dn *)
+ldb_dn_new: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *)
+ldb_dn_new_fmt: struct ldb_dn *(TALLOC_CTX *, struct ldb_context *, const char *, ...)
+ldb_dn_remove_base_components: bool (struct ldb_dn *, unsigned int)
+ldb_dn_remove_child_components: bool (struct ldb_dn *, unsigned int)
+ldb_dn_remove_extended_components: void (struct ldb_dn *)
+ldb_dn_replace_components: bool (struct ldb_dn *, struct ldb_dn *)
+ldb_dn_set_component: int (struct ldb_dn *, int, const char *, const struct ldb_val)
+ldb_dn_set_extended_component: int (struct ldb_dn *, const char *, const struct ldb_val *)
+ldb_dn_update_components: int (struct ldb_dn *, const struct ldb_dn *)
+ldb_dn_validate: bool (struct ldb_dn *)
+ldb_dump_results: void (struct ldb_context *, struct ldb_result *, FILE *)
+ldb_error_at: int (struct ldb_context *, int, const char *, const char *, int)
+ldb_errstring: const char *(struct ldb_context *)
+ldb_extended: int (struct ldb_context *, const char *, void *, struct ldb_result **)
+ldb_extended_default_callback: int (struct ldb_request *, struct ldb_reply *)
+ldb_filter_attrs: int (struct ldb_context *, const struct ldb_message *, const char * const *, struct ldb_message *)
+ldb_filter_attrs_in_place: int (struct ldb_message *, const char * const *)
+ldb_filter_from_tree: char *(TALLOC_CTX *, const struct ldb_parse_tree *)
+ldb_get_config_basedn: struct ldb_dn *(struct ldb_context *)
+ldb_get_create_perms: unsigned int (struct ldb_context *)
+ldb_get_default_basedn: struct ldb_dn *(struct ldb_context *)
+ldb_get_event_context: struct tevent_context *(struct ldb_context *)
+ldb_get_flags: unsigned int (struct ldb_context *)
+ldb_get_opaque: void *(struct ldb_context *, const char *)
+ldb_get_root_basedn: struct ldb_dn *(struct ldb_context *)
+ldb_get_schema_basedn: struct ldb_dn *(struct ldb_context *)
+ldb_global_init: int (void)
+ldb_handle_get_event_context: struct tevent_context *(struct ldb_handle *)
+ldb_handle_new: struct ldb_handle *(TALLOC_CTX *, struct ldb_context *)
+ldb_handle_use_global_event_context: void (struct ldb_handle *)
+ldb_handler_copy: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *)
+ldb_handler_fold: int (struct ldb_context *, void *, const struct ldb_val *, struct ldb_val *)
+ldb_init: struct ldb_context *(TALLOC_CTX *, struct tevent_context *)
+ldb_ldif_message_redacted_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *)
+ldb_ldif_message_string: char *(struct ldb_context *, TALLOC_CTX *, enum ldb_changetype, const struct ldb_message *)
+ldb_ldif_parse_modrdn: int (struct ldb_context *, const struct ldb_ldif *, TALLOC_CTX *, struct ldb_dn **, struct ldb_dn **, bool *, struct ldb_dn **, struct ldb_dn **)
+ldb_ldif_read: struct ldb_ldif *(struct ldb_context *, int (*)(void *), void *)
+ldb_ldif_read_file: struct ldb_ldif *(struct ldb_context *, FILE *)
+ldb_ldif_read_file_state: struct ldb_ldif *(struct ldb_context *, struct ldif_read_file_state *)
+ldb_ldif_read_free: void (struct ldb_context *, struct ldb_ldif *)
+ldb_ldif_read_string: struct ldb_ldif *(struct ldb_context *, const char **)
+ldb_ldif_write: int (struct ldb_context *, int (*)(void *, const char *, ...), void *, const struct ldb_ldif *)
+ldb_ldif_write_file: int (struct ldb_context *, FILE *, const struct ldb_ldif *)
+ldb_ldif_write_redacted_trace_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *)
+ldb_ldif_write_string: char *(struct ldb_context *, TALLOC_CTX *, const struct ldb_ldif *)
+ldb_load_modules: int (struct ldb_context *, const char **)
+ldb_map_add: int (struct ldb_module *, struct ldb_request *)
+ldb_map_delete: int (struct ldb_module *, struct ldb_request *)
+ldb_map_init: int (struct ldb_module *, const struct ldb_map_attribute *, const struct ldb_map_objectclass *, const char * const *, const char *, const char *)
+ldb_map_modify: int (struct ldb_module *, struct ldb_request *)
+ldb_map_rename: int (struct ldb_module *, struct ldb_request *)
+ldb_map_search: int (struct ldb_module *, struct ldb_request *)
+ldb_match_message: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, enum ldb_scope, bool *)
+ldb_match_msg: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope)
+ldb_match_msg_error: int (struct ldb_context *, const struct ldb_message *, const struct ldb_parse_tree *, struct ldb_dn *, enum ldb_scope, bool *)
+ldb_match_msg_objectclass: int (const struct ldb_message *, const char *)
+ldb_match_scope: int (struct ldb_context *, struct ldb_dn *, struct ldb_dn *, enum ldb_scope)
+ldb_mod_register_control: int (struct ldb_module *, const char *)
+ldb_modify: int (struct ldb_context *, const struct ldb_message *)
+ldb_modify_default_callback: int (struct ldb_request *, struct ldb_reply *)
+ldb_module_call_chain: char *(struct ldb_request *, TALLOC_CTX *)
+ldb_module_connect_backend: int (struct ldb_context *, const char *, const char **, struct ldb_module **)
+ldb_module_done: int (struct ldb_request *, struct ldb_control **, struct ldb_extended *, int)
+ldb_module_flags: uint32_t (struct ldb_context *)
+ldb_module_get_ctx: struct ldb_context *(struct ldb_module *)
+ldb_module_get_name: const char *(struct ldb_module *)
+ldb_module_get_ops: const struct ldb_module_ops *(struct ldb_module *)
+ldb_module_get_private: void *(struct ldb_module *)
+ldb_module_init_chain: int (struct ldb_context *, struct ldb_module *)
+ldb_module_load_list: int (struct ldb_context *, const char **, struct ldb_module *, struct ldb_module **)
+ldb_module_new: struct ldb_module *(TALLOC_CTX *, struct ldb_context *, const char *, const struct ldb_module_ops *)
+ldb_module_next: struct ldb_module *(struct ldb_module *)
+ldb_module_popt_options: struct poptOption **(struct ldb_context *)
+ldb_module_send_entry: int (struct ldb_request *, struct ldb_message *, struct ldb_control **)
+ldb_module_send_referral: int (struct ldb_request *, char *)
+ldb_module_set_next: void (struct ldb_module *, struct ldb_module *)
+ldb_module_set_private: void (struct ldb_module *, void *)
+ldb_modules_hook: int (struct ldb_context *, enum ldb_module_hook_type)
+ldb_modules_list_from_string: const char **(struct ldb_context *, TALLOC_CTX *, const char *)
+ldb_modules_load: int (const char *, const char *)
+ldb_msg_add: int (struct ldb_message *, const struct ldb_message_element *, int)
+ldb_msg_add_distinguished_name: int (struct ldb_message *)
+ldb_msg_add_empty: int (struct ldb_message *, const char *, int, struct ldb_message_element **)
+ldb_msg_add_fmt: int (struct ldb_message *, const char *, const char *, ...)
+ldb_msg_add_linearized_dn: int (struct ldb_message *, const char *, struct ldb_dn *)
+ldb_msg_add_steal_string: int (struct ldb_message *, const char *, char *)
+ldb_msg_add_steal_value: int (struct ldb_message *, const char *, struct ldb_val *)
+ldb_msg_add_string: int (struct ldb_message *, const char *, const char *)
+ldb_msg_add_string_flags: int (struct ldb_message *, const char *, const char *, int)
+ldb_msg_add_value: int (struct ldb_message *, const char *, const struct ldb_val *, struct ldb_message_element **)
+ldb_msg_append_fmt: int (struct ldb_message *, int, const char *, const char *, ...)
+ldb_msg_append_linearized_dn: int (struct ldb_message *, const char *, struct ldb_dn *, int)
+ldb_msg_append_steal_string: int (struct ldb_message *, const char *, char *, int)
+ldb_msg_append_steal_value: int (struct ldb_message *, const char *, struct ldb_val *, int)
+ldb_msg_append_string: int (struct ldb_message *, const char *, const char *, int)
+ldb_msg_append_value: int (struct ldb_message *, const char *, const struct ldb_val *, int)
+ldb_msg_canonicalize: struct ldb_message *(struct ldb_context *, const struct ldb_message *)
+ldb_msg_check_string_attribute: int (const struct ldb_message *, const char *, const char *)
+ldb_msg_copy: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *)
+ldb_msg_copy_attr: int (struct ldb_message *, const char *, const char *)
+ldb_msg_copy_shallow: struct ldb_message *(TALLOC_CTX *, const struct ldb_message *)
+ldb_msg_diff: struct ldb_message *(struct ldb_context *, struct ldb_message *, struct ldb_message *)
+ldb_msg_difference: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message *, struct ldb_message *, struct ldb_message **)
+ldb_msg_element_add_value: int (TALLOC_CTX *, struct ldb_message_element *, const struct ldb_val *)
+ldb_msg_element_compare: int (struct ldb_message_element *, struct ldb_message_element *)
+ldb_msg_element_compare_name: int (struct ldb_message_element *, struct ldb_message_element *)
+ldb_msg_element_equal_ordered: bool (const struct ldb_message_element *, const struct ldb_message_element *)
+ldb_msg_element_is_inaccessible: bool (const struct ldb_message_element *)
+ldb_msg_element_mark_inaccessible: void (struct ldb_message_element *)
+ldb_msg_elements_take_ownership: int (struct ldb_message *)
+ldb_msg_find_attr_as_bool: int (const struct ldb_message *, const char *, int)
+ldb_msg_find_attr_as_dn: struct ldb_dn *(struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, const char *)
+ldb_msg_find_attr_as_double: double (const struct ldb_message *, const char *, double)
+ldb_msg_find_attr_as_int: int (const struct ldb_message *, const char *, int)
+ldb_msg_find_attr_as_int64: int64_t (const struct ldb_message *, const char *, int64_t)
+ldb_msg_find_attr_as_string: const char *(const struct ldb_message *, const char *, const char *)
+ldb_msg_find_attr_as_uint: unsigned int (const struct ldb_message *, const char *, unsigned int)
+ldb_msg_find_attr_as_uint64: uint64_t (const struct ldb_message *, const char *, uint64_t)
+ldb_msg_find_common_values: int (struct ldb_context *, TALLOC_CTX *, struct ldb_message_element *, struct ldb_message_element *, uint32_t)
+ldb_msg_find_duplicate_val: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message_element *, struct ldb_val **, uint32_t)
+ldb_msg_find_element: struct ldb_message_element *(const struct ldb_message *, const char *)
+ldb_msg_find_ldb_val: const struct ldb_val *(const struct ldb_message *, const char *)
+ldb_msg_find_val: struct ldb_val *(const struct ldb_message_element *, struct ldb_val *)
+ldb_msg_new: struct ldb_message *(TALLOC_CTX *)
+ldb_msg_normalize: int (struct ldb_context *, TALLOC_CTX *, const struct ldb_message *, struct ldb_message **)
+ldb_msg_remove_attr: void (struct ldb_message *, const char *)
+ldb_msg_remove_element: void (struct ldb_message *, struct ldb_message_element *)
+ldb_msg_remove_inaccessible: void (struct ldb_message *)
+ldb_msg_rename_attr: int (struct ldb_message *, const char *, const char *)
+ldb_msg_sanity_check: int (struct ldb_context *, const struct ldb_message *)
+ldb_msg_shrink_to_fit: void (struct ldb_message *)
+ldb_msg_sort_elements: void (struct ldb_message *)
+ldb_next_del_trans: int (struct ldb_module *)
+ldb_next_end_trans: int (struct ldb_module *)
+ldb_next_init: int (struct ldb_module *)
+ldb_next_prepare_commit: int (struct ldb_module *)
+ldb_next_read_lock: int (struct ldb_module *)
+ldb_next_read_unlock: int (struct ldb_module *)
+ldb_next_remote_request: int (struct ldb_module *, struct ldb_request *)
+ldb_next_request: int (struct ldb_module *, struct ldb_request *)
+ldb_next_start_trans: int (struct ldb_module *)
+ldb_op_default_callback: int (struct ldb_request *, struct ldb_reply *)
+ldb_options_copy: const char **(TALLOC_CTX *, const char **)
+ldb_options_find: const char *(struct ldb_context *, const char **, const char *)
+ldb_options_get: const char **(struct ldb_context *)
+ldb_pack_data: int (struct ldb_context *, const struct ldb_message *, struct ldb_val *, uint32_t)
+ldb_parse_control_from_string: struct ldb_control *(struct ldb_context *, TALLOC_CTX *, const char *)
+ldb_parse_control_strings: struct ldb_control **(struct ldb_context *, TALLOC_CTX *, const char **)
+ldb_parse_tree: struct ldb_parse_tree *(TALLOC_CTX *, const char *)
+ldb_parse_tree_attr_replace: void (struct ldb_parse_tree *, const char *, const char *)
+ldb_parse_tree_copy_shallow: struct ldb_parse_tree *(TALLOC_CTX *, const struct ldb_parse_tree *)
+ldb_parse_tree_get_attr: const char *(const struct ldb_parse_tree *)
+ldb_parse_tree_walk: int (struct ldb_parse_tree *, int (*)(struct ldb_parse_tree *, void *), void *)
+ldb_qsort: void (void * const, size_t, size_t, void *, ldb_qsort_cmp_fn_t)
+ldb_register_backend: int (const char *, ldb_connect_fn, bool)
+ldb_register_extended_match_rule: int (struct ldb_context *, const struct ldb_extended_match_rule *)
+ldb_register_hook: int (ldb_hook_fn)
+ldb_register_module: int (const struct ldb_module_ops *)
+ldb_register_redact_callback: int (struct ldb_context *, ldb_redact_fn, struct ldb_module *)
+ldb_rename: int (struct ldb_context *, struct ldb_dn *, struct ldb_dn *)
+ldb_reply_add_control: int (struct ldb_reply *, const char *, bool, void *)
+ldb_reply_get_control: struct ldb_control *(struct ldb_reply *, const char *)
+ldb_req_get_custom_flags: uint32_t (struct ldb_request *)
+ldb_req_is_untrusted: bool (struct ldb_request *)
+ldb_req_location: const char *(struct ldb_request *)
+ldb_req_mark_trusted: void (struct ldb_request *)
+ldb_req_mark_untrusted: void (struct ldb_request *)
+ldb_req_set_custom_flags: void (struct ldb_request *, uint32_t)
+ldb_req_set_location: void (struct ldb_request *, const char *)
+ldb_request: int (struct ldb_context *, struct ldb_request *)
+ldb_request_add_control: int (struct ldb_request *, const char *, bool, void *)
+ldb_request_done: int (struct ldb_request *, int)
+ldb_request_get_control: struct ldb_control *(struct ldb_request *, const char *)
+ldb_request_get_status: int (struct ldb_request *)
+ldb_request_replace_control: int (struct ldb_request *, const char *, bool, void *)
+ldb_request_set_state: void (struct ldb_request *, int)
+ldb_reset_err_string: void (struct ldb_context *)
+ldb_save_controls: int (struct ldb_control *, struct ldb_request *, struct ldb_control ***)
+ldb_schema_attribute_add: int (struct ldb_context *, const char *, unsigned int, const char *)
+ldb_schema_attribute_add_with_syntax: int (struct ldb_context *, const char *, unsigned int, const struct ldb_schema_syntax *)
+ldb_schema_attribute_by_name: const struct ldb_schema_attribute *(struct ldb_context *, const char *)
+ldb_schema_attribute_fill_with_syntax: int (struct ldb_context *, TALLOC_CTX *, const char *, unsigned int, const struct ldb_schema_syntax *, struct ldb_schema_attribute *)
+ldb_schema_attribute_remove: void (struct ldb_context *, const char *)
+ldb_schema_attribute_remove_flagged: void (struct ldb_context *, unsigned int)
+ldb_schema_attribute_set_override_handler: void (struct ldb_context *, ldb_attribute_handler_override_fn_t, void *)
+ldb_schema_set_override_GUID_index: void (struct ldb_context *, const char *, const char *)
+ldb_schema_set_override_indexlist: void (struct ldb_context *, bool)
+ldb_search: int (struct ldb_context *, TALLOC_CTX *, struct ldb_result **, struct ldb_dn *, enum ldb_scope, const char * const *, const char *, ...)
+ldb_search_default_callback: int (struct ldb_request *, struct ldb_reply *)
+ldb_sequence_number: int (struct ldb_context *, enum ldb_sequence_type, uint64_t *)
+ldb_set_create_perms: void (struct ldb_context *, unsigned int)
+ldb_set_debug: int (struct ldb_context *, void (*)(void *, enum ldb_debug_level, const char *, va_list), void *)
+ldb_set_debug_stderr: int (struct ldb_context *)
+ldb_set_default_dns: void (struct ldb_context *)
+ldb_set_errstring: void (struct ldb_context *, const char *)
+ldb_set_event_context: void (struct ldb_context *, struct tevent_context *)
+ldb_set_flags: void (struct ldb_context *, unsigned int)
+ldb_set_modules_dir: void (struct ldb_context *, const char *)
+ldb_set_opaque: int (struct ldb_context *, const char *, void *)
+ldb_set_require_private_event_context: void (struct ldb_context *)
+ldb_set_timeout: int (struct ldb_context *, struct ldb_request *, int)
+ldb_set_timeout_from_prev_req: int (struct ldb_context *, struct ldb_request *, struct ldb_request *)
+ldb_set_utf8_default: void (struct ldb_context *)
+ldb_set_utf8_fns: void (struct ldb_context *, void *, char *(*)(void *, void *, const char *, size_t))
+ldb_setup_wellknown_attributes: int (struct ldb_context *)
+ldb_should_b64_encode: int (struct ldb_context *, const struct ldb_val *)
+ldb_standard_syntax_by_name: const struct ldb_schema_syntax *(struct ldb_context *, const char *)
+ldb_strerror: const char *(int)
+ldb_string_to_time: time_t (const char *)
+ldb_string_utc_to_time: time_t (const char *)
+ldb_timestring: char *(TALLOC_CTX *, time_t)
+ldb_timestring_utc: char *(TALLOC_CTX *, time_t)
+ldb_transaction_cancel: int (struct ldb_context *)
+ldb_transaction_cancel_noerr: int (struct ldb_context *)
+ldb_transaction_commit: int (struct ldb_context *)
+ldb_transaction_prepare_commit: int (struct ldb_context *)
+ldb_transaction_start: int (struct ldb_context *)
+ldb_unpack_data: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *)
+ldb_unpack_data_flags: int (struct ldb_context *, const struct ldb_val *, struct ldb_message *, unsigned int)
+ldb_unpack_get_format: int (const struct ldb_val *, uint32_t *)
+ldb_val_dup: struct ldb_val (TALLOC_CTX *, const struct ldb_val *)
+ldb_val_equal_exact: int (const struct ldb_val *, const struct ldb_val *)
+ldb_val_map_local: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *)
+ldb_val_map_remote: struct ldb_val (struct ldb_module *, void *, const struct ldb_map_attribute *, const struct ldb_val *)
+ldb_val_string_cmp: int (const struct ldb_val *, const char *)
+ldb_val_to_time: int (const struct ldb_val *, time_t *)
+ldb_valid_attr_name: int (const char *)
+ldb_vdebug: void (struct ldb_context *, enum ldb_debug_level, const char *, va_list)
+ldb_wait: int (struct ldb_handle *, enum ldb_wait_type)
diff --git ldb/ABI/pyldb-util-2.4.5.sigs ldb/ABI/pyldb-util-2.4.5.sigs
new file mode 100644
index 00000000000..164a806b2ff
--- /dev/null
+++ ldb/ABI/pyldb-util-2.4.5.sigs
@@ -0,0 +1,3 @@
+pyldb_Dn_FromDn: PyObject *(struct ldb_dn *)
+pyldb_Object_AsDn: bool (TALLOC_CTX *, PyObject *, struct ldb_context *, struct ldb_dn **)
+pyldb_check_type: bool (PyObject *, const char *)
diff --git ldb/wscript ldb/wscript
index 35143f3860c..ec2d80fe052 100644
--- ldb/wscript
+++ ldb/wscript
@@ -2,7 +2,7 @@
 
 APPNAME = 'ldb'
 # For Samba 4.15.x
-VERSION = '2.4.4'
+VERSION = '2.4.5'
 
 import sys, os
 
-- 
2.35.3


