From 24f7eb574d2e9ea57746f9696491b50dc4ef5352 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Mon, 29 Jun 2026 19:09:28 +0200
Subject: [PATCH 21/23] CVE-2026-58221: s4:dsdb: provide
 dsdb_audit_{log_attributes,operation_human_readable}() functions

They are useful outside of audit_log.c soon.

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

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>

[backport: modified for v4.22, not removing the static versions used
 in audit_log.c or adjusting the tests]
---
 source4/dsdb/samdb/ldb_modules/audit_util.c | 180 ++++++++++++++++++++
 1 file changed, 180 insertions(+)

--- a/source4/dsdb/samdb/ldb_modules/audit_util.c
+++ b/source4/dsdb/samdb/ldb_modules/audit_util.c
@@ -695,3 +695,183 @@ failure:
 	DBG_ERR("Unable to create ldb attributes JSON audit message\n");
 	return attributes;
 }
+
+/*
+ * @brief Generate a human readable string, detailing attributes in a message
+ *
+ * For modify operations each attribute is prefixed with the action.
+ * Normal values are enclosed in []
+ * Base64 values are enclosed in {}
+ * Truncated values are indicated by three trailing dots "..."
+ *
+ * @param[in]  ldb       the ldb_context
+ * @param[out] buffer    the attributes will be appended to the buffer.
+ *                       assumed to have been allocated via talloc.
+ * @param[in]  operation the operation type
+ * @param[in]  message   the message to process
+ *
+ * @return a pointer to buffer
+ *
+ */
+char *dsdb_audit_log_attributes(
+	struct ldb_context *ldb,
+	char *buffer,
+	enum ldb_request_type operation,
+	const struct ldb_message *message)
+{
+	size_t i, j;
+	for (i=0;i<message->num_elements;i++) {
+		if (i > 0) {
+			buffer = talloc_asprintf_append_buffer(buffer, " ");
+		}
+
+		if (message->elements[i].name == NULL) {
+			ldb_debug(
+				ldb,
+				LDB_DEBUG_ERROR,
+				"Error: Invalid element name (NULL) at "
+				"position %zu", i);
+			return NULL;
+		}
+
+		if (operation == LDB_MODIFY) {
+			const char *action =NULL;
+			action = dsdb_audit_get_modification_action(
+				message->elements[i].flags);
+			buffer = talloc_asprintf_append_buffer(
+				buffer,
+				"%s: %s ",
+				action,
+				message->elements[i].name);
+		} else {
+			buffer = talloc_asprintf_append_buffer(
+				buffer,
+				"%s ",
+				message->elements[i].name);
+		}
+
+		if (dsdb_audit_redact_attribute(message->elements[i].name)) {
+			/*
+			 * Do not log the value of any secret or password
+			 * attributes
+			 */
+			buffer = talloc_asprintf_append_buffer(
+				buffer,
+				"[REDACTED SECRET ATTRIBUTE]");
+			continue;
+		}
+
+		for (j=0;j<message->elements[i].num_values;j++) {
+			struct ldb_val v;
+			bool use_b64_encode = false;
+			size_t length;
+			if (j > 0) {
+				buffer = talloc_asprintf_append_buffer(
+					buffer,
+					" ");
+			}
+
+			v = message->elements[i].values[j];
+			length = MIN(MAX_LENGTH, v.length);
+			use_b64_encode = ldb_should_b64_encode(ldb, &v);
+			if (use_b64_encode) {
+				const char *encoded = ldb_base64_encode(
+					buffer,
+					(char *)v.data,
+					length);
+				buffer = talloc_asprintf_append_buffer(
+					buffer,
+				        "{%s%s}",
+					encoded,
+					(v.length > MAX_LENGTH ? "..." : ""));
+			} else {
+				buffer = talloc_asprintf_append_buffer(
+					buffer,
+					"[%*.*s%s]",
+					(int)length,
+					(int)length,
+					(char *)v.data,
+					(v.length > MAX_LENGTH ? "..." : ""));
+			}
+		}
+	}
+	return buffer;
+}
+
+/*
+ * @brief generate a human readable log entry detailing an ldb operation.
+ *
+ * Generate a human readable log entry detailing an ldb operation.
+ *
+ * @param[in] mem_ctx the talloc context owning the returned string.
+ * @param[in] module  the ldb module
+ * @param[in] request the request
+ * @param[in] reply   the result of the operation
+ *
+ * @return the log entry.
+ *
+ */
+char *dsdb_audit_operation_human_readable(
+	TALLOC_CTX *mem_ctx,
+	struct ldb_module *module,
+	const struct ldb_request *request,
+	const struct ldb_reply *reply)
+{
+	struct ldb_context *ldb = NULL;
+	const char *remote_host = NULL;
+	const struct tsocket_address *remote = NULL;
+	const struct dom_sid *sid = NULL;
+	struct dom_sid_buf user_sid;
+	const char *timestamp = NULL;
+	const char *op_name = NULL;
+	char *log_entry = NULL;
+	const char *dn = NULL;
+	const char *new_dn = NULL;
+	const struct ldb_message *message = NULL;
+
+	TALLOC_CTX *ctx = talloc_new(NULL);
+
+	ldb = ldb_module_get_ctx(module);
+
+	remote_host = dsdb_audit_get_remote_host(ldb, ctx);
+	remote = dsdb_audit_get_remote_address(ldb);
+	if (remote != NULL && dsdb_audit_is_system_session(module)) {
+		sid = dsdb_audit_get_actual_sid(ldb);
+	} else {
+		sid = dsdb_audit_get_user_sid(module);
+	}
+	timestamp = audit_get_timestamp(ctx);
+	op_name = dsdb_audit_get_operation_name(request);
+	dn = dsdb_audit_get_primary_dn(request);
+	new_dn = dsdb_audit_get_secondary_dn(request);
+
+	message = dsdb_audit_get_message(request);
+
+	log_entry = talloc_asprintf(
+		mem_ctx,
+		"[%s] at [%s] status [%s] "
+		"remote host [%s] SID [%s] DN [%s]",
+		op_name,
+		timestamp,
+		ldb_strerror(reply->error),
+		remote_host,
+		dom_sid_str_buf(sid, &user_sid),
+		dn);
+	if (new_dn != NULL) {
+		log_entry = talloc_asprintf_append_buffer(
+			log_entry,
+			" New DN [%s]",
+			new_dn);
+	}
+	if (message != NULL) {
+		log_entry = talloc_asprintf_append_buffer(log_entry,
+							  " attributes [");
+		log_entry = dsdb_audit_log_attributes(ldb,
+						      log_entry,
+						      request->operation,
+						      message);
+		log_entry = talloc_asprintf_append_buffer(log_entry, "]");
+	}
+	TALLOC_FREE(ctx);
+	return log_entry;
+}
