From 80db5e90fa18d3e34bb91dd027bdf76d31e93dcd Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 4 Feb 2026 13:30:08 +0100
Subject: [PATCH] x509/name_constraints: implement
 name_constraints_node_list_union

Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>

Upstream-Status: Backport [https://gitlab.com/gnutls/gnutls/-/commit/80db5e90fa18d3e34bb91dd027bdf76d31e93dcd]
CVE: CVE-2025-14831
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
---
 lib/x509/name_constraints.c | 98 ++++++++++++++++++++++++++++++++-----
 1 file changed, 86 insertions(+), 12 deletions(-)

--- a/lib/x509/name_constraints.c
+++ b/lib/x509/name_constraints.c
@@ -41,6 +41,7 @@
 #include "intprops.h"
 #include "minmax.h"
 
+#include <assert.h>
 #include <string.h>
 
 #define MAX_NC_CHECKS (1 << 20)
@@ -867,22 +868,95 @@ cleanup:
 	return ret;
 }
 
-static int name_constraints_node_list_concat(
-	gnutls_x509_name_constraints_t nc,
-	struct name_constraints_node_list_st *nodes,
-	const struct name_constraints_node_list_st *nodes2)
+static int
+name_constraints_node_list_union(gnutls_x509_name_constraints_t nc,
+				 struct name_constraints_node_list_st *nodes,
+				 struct name_constraints_node_list_st *nodes2)
 {
 	int ret;
+	size_t i = 0, j = 0;
+	struct name_constraints_node_st *nc1;
+	const struct name_constraints_node_st *nc2;
+	enum name_constraint_relation rel;
+	struct name_constraints_node_list_st result = { 0 };
+
+	if (nodes2->size == 0) /* nothing to do */
+		return GNUTLS_E_SUCCESS;
+
+	ret = ensure_sorted(nodes);
+	if (ret < 0) {
+		gnutls_assert();
+		goto cleanup;
+	}
+	ret = ensure_sorted(nodes2);
+	if (ret < 0) {
+		gnutls_assert();
+		goto cleanup;
+	}
+
+	/* traverse both lists in a single pass and merge them w/o duplicates */
+	while (i < nodes->size || j < nodes2->size) {
+		nc1 = (i < nodes->size) ? nodes->sorted_view[i] : NULL;
+		nc2 = (j < nodes2->size) ? nodes2->sorted_view[j] : NULL;
 
-	for (size_t i = 0; i < nodes2->size; i++) {
-		ret = name_constraints_node_add_copy(nc, nodes,
-						     nodes2->data[i]);
+		rel = compare_name_constraint_nodes(nc1, nc2);
+		switch (rel) {
+		case NC_SORTS_BEFORE:
+			assert(nc1 != NULL); /* comparator-guaranteed */
+			ret = name_constraints_node_list_add(&result, nc1);
+			i++;
+			break;
+		case NC_SORTS_AFTER:
+			assert(nc2 != NULL); /* comparator-guaranteed */
+			ret = name_constraints_node_add_copy(nc, &result, nc2);
+			j++;
+			break;
+		case NC_INCLUDES: /* nc1 is broader, shallow-copy it */
+			assert(nc1 != NULL && nc2 != NULL); /* comparator */
+			ret = name_constraints_node_list_add(&result, nc1);
+			i++;
+			j++;
+			break;
+		case NC_INCLUDED_BY: /* nc2 is broader, deep-copy it */
+			assert(nc1 != NULL && nc2 != NULL); /* comparator */
+			ret = name_constraints_node_add_copy(nc, &result, nc2);
+			i++;
+			j++;
+			break;
+		case NC_EQUAL:
+			assert(nc1 != NULL && nc2 != NULL); /* loop condition */
+			ret = name_constraints_node_list_add(&result, nc1);
+			i++;
+			j++;
+			break;
+		}
 		if (ret < 0) {
-			return gnutls_assert_val(ret);
+			gnutls_assert();
+			goto cleanup;
 		}
 	}
 
-	return 0;
+	gnutls_free(nodes->data);
+	gnutls_free(nodes->sorted_view);
+	nodes->data = result.data;
+	nodes->sorted_view = NULL;
+	nodes->size = result.size;
+	nodes->capacity = result.capacity;
+	nodes->dirty = true;
+	/* since we know it's sorted, populate sorted_view almost for free */
+	nodes->sorted_view = gnutls_calloc(
+		nodes->size, sizeof(struct name_constraints_node_st *));
+	if (!nodes->sorted_view)
+		return GNUTLS_E_SUCCESS; /* we tried, no harm done */
+	memcpy(nodes->sorted_view, nodes->data,
+	       nodes->size * sizeof(struct name_constraints_node_st *));
+	nodes->dirty = false;
+
+	result.data = NULL;
+	return GNUTLS_E_SUCCESS;
+cleanup:
+	name_constraints_node_list_clear(&result);
+	return gnutls_assert_val(ret);
 }
 
 /**
@@ -1023,7 +1097,7 @@ static int name_constraints_add(gnutls_x
  * @nc2: The name constraints to be merged with
  *
  * This function will merge the provided name constraints structures
- * as per RFC5280 p6.1.4. That is, the excluded constraints will be appended,
+ * as per RFC5280 p6.1.4. That is, the excluded constraints will be unioned,
  * and permitted will be intersected. The intersection assumes that @nc
  * is the root CA constraints.
  *
@@ -1045,8 +1119,8 @@ int _gnutls_x509_name_constraints_merge(
 		return ret;
 	}
 
-	ret = name_constraints_node_list_concat(nc, &nc->excluded,
-						&nc2->excluded);
+	ret = name_constraints_node_list_union(nc, &nc->excluded,
+					       &nc2->excluded);
 	if (ret < 0) {
 		gnutls_assert();
 		return ret;
