Commit message: CVE-2022-32746 ldb: Add functions for appending to an ldb_message

Currently, there are many places where we use ldb_msg_add_empty() to add
an empty element to a message, and then call ldb_msg_add_value() or
similar to add values to that element. However, this performs an
unnecessary search of the message's elements to locate the new element.
Moreover, if an element with the same attribute name already exists
earlier in the message, the values will be added to that element,
instead of to the intended newly added element.

A similar pattern exists where we add values to a message, and then call
ldb_msg_find_element() to locate that message element and sets its flags
to (e.g.) LDB_FLAG_MOD_REPLACE. This also performs an unnecessary
search, and may locate the wrong message element for setting the flags.

To avoid these problems, add functions for appending a value to a
message, so that a particular value can be added to the end of a message
in a single operation.

For ADD requests, it is important that no two message elements share the
same attribute name, otherwise things will break. (Normally,
ldb_msg_normalize() is called before processing the request to help
ensure this.) Thus, we must be careful not to append an attribute to an
ADD message, unless we are sure (e.g. through ldb_msg_find_element())
that an existing element for that attribute is not present.

These functions will be used in the next commit.

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

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
CVE: None
CWE: CWE-200
Vulnerability description: None

Project: samba
File: ldb_msg.c

static int ldb_msg_append_value_impl(struct ldb_message *msg,
				     const char *attr_name,
				     const struct ldb_val *val,
				     int flags,
				     struct ldb_message_element **return_el)
{
	struct ldb_message_element *el = NULL;
	int ret;

	ret = ldb_msg_add_empty(msg, attr_name, flags, &el);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	ret = ldb_msg_element_add_value(msg->elements, el, val);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	if (return_el != NULL) {
		*return_el = el;
	}

	return LDB_SUCCESS;
}

Project: samba
File: ldb_msg.c

int ldb_msg_add_string(struct ldb_message *msg,
		       const char *attr_name, const char *str)
{
	return ldb_msg_add_string_flags(msg, attr_name, str, 0);
}

Project: samba
File: ldb_msg.c

int ldb_msg_append_steal_value(struct ldb_message *msg,
			       const char *attr_name,
			       struct ldb_val *val,
			       int flags)
{
	int ret;
	struct ldb_message_element *el = NULL;

	ret = ldb_msg_append_value_impl(msg, attr_name, val, flags, &el);
	if (ret == LDB_SUCCESS) {
		talloc_steal(el->values, val->data);
	}
	return ret;
}

Project: samba
File: ldb_msg.c

int ldb_msg_append_steal_string(struct ldb_message *msg,
				const char *attr_name, char *str,
				int flags)
{
	struct ldb_val val;

	val.data = (uint8_t *)str;
	val.length = strlen(str);

	if (val.length == 0) {
		/* allow empty strings as non-existent attributes */
		return LDB_SUCCESS;
	}

	return ldb_msg_append_steal_value(msg, attr_name, &val, flags);
}

Project: samba
File: ldb_msg.c

int ldb_msg_append_fmt(struct ldb_message *msg, int flags,
		       const char *attr_name, const char *fmt, ...)
{
	struct ldb_val val;
	va_list ap;
	char *str = NULL;

	va_start(ap, fmt);
	str = talloc_vasprintf(msg, fmt, ap);
	va_end(ap);

	if (str == NULL) {
		return LDB_ERR_OPERATIONS_ERROR;
	}

	val.data   = (uint8_t *)str;
	val.length = strlen(str);

	return ldb_msg_append_steal_value(msg, attr_name, &val, flags);
}

Project: samba
File: ldb_msg.c

int ldb_msg_add_string_flags(struct ldb_message *msg,
			     const char *attr_name, const char *str,
			     int flags)
{
	struct ldb_val val;
	int ret;
	struct ldb_message_element *el = NULL;

	val.data = discard_const_p(uint8_t, str);
	val.length = strlen(str);

	if (val.length == 0) {
		/* allow empty strings as non-existent attributes */
		return LDB_SUCCESS;
	}

	ret = ldb_msg_add_value(msg, attr_name, &val, &el);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	if (flags != 0) {
		el->flags = flags;
	}

	return LDB_SUCCESS;
}

Project: samba
File: ldb_msg.c

int ldb_msg_append_linearized_dn(struct ldb_message *msg, const char *attr_name,
				 struct ldb_dn *dn, int flags)
{
	char *str = ldb_dn_alloc_linearized(msg, dn);

	if (str == NULL) {
		/* we don't want to have unknown DNs added */
		return LDB_ERR_OPERATIONS_ERROR;
	}

	return ldb_msg_append_steal_string(msg, attr_name, str, flags);
}

Project: samba
File: ldb_msg.c

int ldb_msg_append_string(struct ldb_message *msg,
			  const char *attr_name, const char *str, int flags)
{
	struct ldb_val val;

	val.data = discard_const_p(uint8_t, str);
	val.length = strlen(str);

	if (val.length == 0) {
		/* allow empty strings as non-existent attributes */
		return LDB_SUCCESS;
	}

	return ldb_msg_append_value(msg, attr_name, &val, flags);
}

Project: samba
File: ldb_msg.c

int ldb_msg_append_value(struct ldb_message *msg,
			 const char *attr_name,
			 const struct ldb_val *val,
			 int flags)
{
	return ldb_msg_append_value_impl(msg, attr_name, val, flags, NULL);
}