From 4760bc63531e3f5039e70ede91a20e1194410892 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Mon, 18 Nov 2024 17:23:46 +0900
Subject: [PATCH] x509: optimize name constraints processing

This switches the representation name constraints from linked lists to
array lists to optimize the lookup performance from O(n) to O(1), also
enforces a limit of name constraint checks against subject alternative
names.

Signed-off-by: Daiki Ueno <ueno@gnu.org>

CVE: CVE-2024-12243
Upstream-Status: Backport [https://gitlab.com/gnutls/gnutls/-/commit/4760bc63531e3f5039e70ede91a20e1194410892]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 lib/datum.c                 |   7 +-
 lib/x509/name_constraints.c | 595 +++++++++++++++++++++---------------
 lib/x509/x509_ext.c         |  80 +++--
 lib/x509/x509_ext_int.h     |   5 +
 lib/x509/x509_int.h         |  21 +-
 5 files changed, 399 insertions(+), 309 deletions(-)

diff --git a/lib/datum.c b/lib/datum.c
index 66e016965..5577c2b4a 100644
--- a/lib/datum.c
+++ b/lib/datum.c
@@ -29,6 +29,7 @@
 #include "num.h"
 #include "datum.h"
 #include "errors.h"
+#include "intprops.h"
 
 /* On error, @dat is not changed. */
 int _gnutls_set_datum(gnutls_datum_t *dat, const void *data, size_t data_size)
@@ -60,7 +61,11 @@ int _gnutls_set_strdatum(gnutls_datum_t *dat, const void *data,
 	if (data == NULL)
 		return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
 
-	unsigned char *m = gnutls_malloc(data_size + 1);
+	size_t capacity;
+	if (!INT_ADD_OK(data_size, 1, &capacity))
+		return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+	unsigned char *m = gnutls_malloc(capacity);
 	if (!m)
 		return GNUTLS_E_MEMORY_ERROR;
 
diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c
index 8327a9d94..3c6e30630 100644
--- a/lib/x509/name_constraints.c
+++ b/lib/x509/name_constraints.c
@@ -33,51 +33,98 @@
 #include <gnutls/x509-ext.h>
 #include "x509_b64.h"
 #include "x509_int.h"
+#include "x509_ext_int.h"
 #include <libtasn1.h>
 
 #include "ip.h"
 #include "ip-in-cidr.h"
+#include "intprops.h"
+
+#define MAX_NC_CHECKS (1 << 20)
+
+struct name_constraints_node_st {
+	unsigned type;
+	gnutls_datum_t name;
+};
+
+struct name_constraints_node_list_st {
+	struct name_constraints_node_st **data;
+	size_t size;
+	size_t capacity;
+};
+
+struct gnutls_name_constraints_st {
+	struct name_constraints_node_list_st nodes; /* owns elements */
+	struct name_constraints_node_list_st permitted; /* borrows elements */
+	struct name_constraints_node_list_st excluded; /* borrows elements */
+};
+
+static struct name_constraints_node_st *
+name_constraints_node_new(gnutls_x509_name_constraints_t nc, unsigned type,
+			  unsigned char *data, unsigned int size);
+
+static int
+name_constraints_node_list_add(struct name_constraints_node_list_st *list,
+			       struct name_constraints_node_st *node)
+{
+	if (!list->capacity || list->size == list->capacity) {
+		size_t new_capacity = list->capacity;
+		struct name_constraints_node_st **new_data;
+
+		if (!INT_MULTIPLY_OK(new_capacity, 2, &new_capacity) ||
+		    !INT_ADD_OK(new_capacity, 1, &new_capacity))
+			return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+		new_data = _gnutls_reallocarray(
+			list->data, new_capacity,
+			sizeof(struct name_constraints_node_st *));
+		if (!new_data)
+			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+		list->capacity = new_capacity;
+		list->data = new_data;
+	}
+	list->data[list->size++] = node;
+	return 0;
+}
 
 // for documentation see the implementation
-static int
-name_constraints_intersect_nodes(name_constraints_node_st *nc1,
-				 name_constraints_node_st *nc2,
-				 name_constraints_node_st **intersection);
+static int name_constraints_intersect_nodes(
+	gnutls_x509_name_constraints_t nc,
+	const struct name_constraints_node_st *node1,
+	const struct name_constraints_node_st *node2,
+	struct name_constraints_node_st **intersection);
 
 /*-
- * is_nc_empty:
+ * _gnutls_x509_name_constraints_is_empty:
  * @nc: name constraints structure
- * @type: type (gnutls_x509_subject_alt_name_t)
+ * @type: type (gnutls_x509_subject_alt_name_t or 0)
  *
  * Test whether given name constraints structure has any constraints (permitted
  * or excluded) of a given type. @nc must be allocated (not NULL) before the call.
+ * If @type is 0, type checking will be skipped.
  *
- * Returns: 0 if @nc contains constraints of type @type, 1 otherwise
+ * Returns: false if @nc contains constraints of type @type, true otherwise
  -*/
-static unsigned is_nc_empty(struct gnutls_name_constraints_st *nc,
-			    unsigned type)
+bool _gnutls_x509_name_constraints_is_empty(gnutls_x509_name_constraints_t nc,
+					    unsigned type)
 {
-	name_constraints_node_st *t;
+	if (nc->permitted.size == 0 && nc->excluded.size == 0)
+		return true;
 
-	if (nc->permitted == NULL && nc->excluded == NULL)
-		return 1;
+	if (type == 0)
+		return false;
 
-	t = nc->permitted;
-	while (t != NULL) {
-		if (t->type == type)
-			return 0;
-		t = t->next;
+	for (size_t i = 0; i < nc->permitted.size; i++) {
+		if (nc->permitted.data[i]->type == type)
+			return false;
 	}
 
-	t = nc->excluded;
-	while (t != NULL) {
-		if (t->type == type)
-			return 0;
-		t = t->next;
+	for (size_t i = 0; i < nc->excluded.size; i++) {
+		if (nc->excluded.data[i]->type == type)
+			return false;
 	}
 
 	/* no constraint for that type exists */
-	return 1;
+	return true;
 }
 
 /*-
@@ -115,21 +162,16 @@ static int validate_name_constraints_node(gnutls_x509_subject_alt_name_t type,
 	return GNUTLS_E_SUCCESS;
 }
 
-int _gnutls_extract_name_constraints(asn1_node c2, const char *vstr,
-				     name_constraints_node_st **_nc)
+static int extract_name_constraints(gnutls_x509_name_constraints_t nc,
+				    asn1_node c2, const char *vstr,
+				    struct name_constraints_node_list_st *nodes)
 {
 	int ret;
 	char tmpstr[128];
 	unsigned indx;
 	gnutls_datum_t tmp = { NULL, 0 };
 	unsigned int type;
-	struct name_constraints_node_st *nc, *prev;
-
-	prev = *_nc;
-	if (prev != NULL) {
-		while (prev->next != NULL)
-			prev = prev->next;
-	}
+	struct name_constraints_node_st *node;
 
 	for (indx = 1;; indx++) {
 		snprintf(tmpstr, sizeof(tmpstr), "%s.?%u.base", vstr, indx);
@@ -172,25 +214,19 @@ int _gnutls_extract_name_constraints(asn1_node c2, const char *vstr,
 			goto cleanup;
 		}
 
-		nc = gnutls_malloc(sizeof(struct name_constraints_node_st));
-		if (nc == NULL) {
+		node = name_constraints_node_new(nc, type, tmp.data, tmp.size);
+		_gnutls_free_datum(&tmp);
+		if (node == NULL) {
 			gnutls_assert();
 			ret = GNUTLS_E_MEMORY_ERROR;
 			goto cleanup;
 		}
 
-		memcpy(&nc->name, &tmp, sizeof(gnutls_datum_t));
-		nc->type = type;
-		nc->next = NULL;
-
-		if (prev == NULL) {
-			*_nc = prev = nc;
-		} else {
-			prev->next = nc;
-			prev = nc;
+		ret = name_constraints_node_list_add(nodes, node);
+		if (ret < 0) {
+			gnutls_assert();
+			goto cleanup;
 		}
-
-		tmp.data = NULL;
 	}
 
 	assert(ret < 0);
@@ -205,84 +241,104 @@ cleanup:
 	return ret;
 }
 
+int _gnutls_x509_name_constraints_extract(asn1_node c2,
+					  const char *permitted_name,
+					  const char *excluded_name,
+					  gnutls_x509_name_constraints_t nc)
+{
+	int ret;
+
+	ret = extract_name_constraints(nc, c2, permitted_name, &nc->permitted);
+	if (ret < 0)
+		return gnutls_assert_val(ret);
+	ret = extract_name_constraints(nc, c2, excluded_name, &nc->excluded);
+	if (ret < 0)
+		return gnutls_assert_val(ret);
+
+	return ret;
+}
+
 /*-
- * _gnutls_name_constraints_node_free:
+ * name_constraints_node_free:
  * @node: name constraints node
  *
- * Deallocate a list of name constraints nodes starting at the given node.
+ * Deallocate a name constraints node.
  -*/
-void _gnutls_name_constraints_node_free(name_constraints_node_st *node)
+static void name_constraints_node_free(struct name_constraints_node_st *node)
 {
-	name_constraints_node_st *next, *t;
-
-	t = node;
-	while (t != NULL) {
-		next = t->next;
-		gnutls_free(t->name.data);
-		gnutls_free(t);
-		t = next;
+	if (node) {
+		gnutls_free(node->name.data);
+		gnutls_free(node);
 	}
 }
 
 /*-
  * name_constraints_node_new:
  * @type: name constraints type to set (gnutls_x509_subject_alt_name_t)
+ * @nc: a %gnutls_x509_name_constraints_t
  * @data: name.data to set or NULL
  * @size: name.size to set
  *
  * Allocate a new name constraints node and set its type, name size and name data.
- * If @data is set to NULL, name data will be an array of \x00 (the length of @size).
- * The .next pointer is set to NULL.
  *
  * Returns: Pointer to newly allocated node or NULL in case of memory error.
  -*/
-static name_constraints_node_st *
-name_constraints_node_new(unsigned type, unsigned char *data, unsigned int size)
+static struct name_constraints_node_st *
+name_constraints_node_new(gnutls_x509_name_constraints_t nc, unsigned type,
+			  unsigned char *data, unsigned int size)
 {
-	name_constraints_node_st *tmp =
-		gnutls_malloc(sizeof(struct name_constraints_node_st));
+	struct name_constraints_node_st *tmp;
+	int ret;
+
+	tmp = gnutls_calloc(1, sizeof(struct name_constraints_node_st));
 	if (tmp == NULL)
 		return NULL;
 	tmp->type = type;
-	tmp->next = NULL;
-	tmp->name.size = size;
-	tmp->name.data = NULL;
-	if (tmp->name.size > 0) {
-		tmp->name.data = gnutls_malloc(tmp->name.size);
-		if (tmp->name.data == NULL) {
+
+	if (data) {
+		ret = _gnutls_set_strdatum(&tmp->name, data, size);
+		if (ret < 0) {
+			gnutls_assert();
 			gnutls_free(tmp);
 			return NULL;
 		}
-		if (data != NULL) {
-			memcpy(tmp->name.data, data, size);
-		} else {
-			memset(tmp->name.data, 0, size);
-		}
 	}
+
+	ret = name_constraints_node_list_add(&nc->nodes, tmp);
+	if (ret < 0) {
+		gnutls_assert();
+		name_constraints_node_free(tmp);
+		return NULL;
+	}
+
 	return tmp;
 }
 
 /*-
- * @brief _gnutls_name_constraints_intersect:
- * @_nc: first name constraints list (permitted)
- * @_nc2: name constraints list to merge with (permitted)
- * @_nc_excluded: Corresponding excluded name constraints list
+ * @brief name_constraints_node_list_intersect:
+ * @nc: %gnutls_x509_name_constraints_t
+ * @permitted: first name constraints list (permitted)
+ * @permitted2: name constraints list to merge with (permitted)
+ * @excluded: Corresponding excluded name constraints list
  *
- * This function finds the intersection of @_nc and @_nc2. The result is placed in @_nc,
- * the original @_nc is deallocated. @_nc2 is not changed. If necessary, a universal
+ * This function finds the intersection of @permitted and @permitted2. The result is placed in @permitted,
+ * the original @permitted is modified. @permitted2 is not changed. If necessary, a universal
  * excluded name constraint node of the right type is added to the list provided
- * in @_nc_excluded.
+ * in @excluded.
  *
  * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value.
  -*/
-static int
-_gnutls_name_constraints_intersect(name_constraints_node_st **_nc,
-				   name_constraints_node_st *_nc2,
-				   name_constraints_node_st **_nc_excluded)
+static int name_constraints_node_list_intersect(
+	gnutls_x509_name_constraints_t nc,
+	struct name_constraints_node_list_st *permitted,
+	const struct name_constraints_node_list_st *permitted2,
+	struct name_constraints_node_list_st *excluded)
 {
-	name_constraints_node_st *nc, *nc2, *t, *tmp, *dest = NULL,
-						      *prev = NULL;
+	struct name_constraints_node_st *tmp;
 	int ret, type, used;
+	struct name_constraints_node_list_st removed = { .data = NULL,
+							 .size = 0,
+							 .capacity = 0 };
 
 	/* temporary array to see, if we need to add universal excluded constraints
 	 * (see phase 3 for details)
@@ -291,61 +347,73 @@ _gnutls_name_constraints_intersect(name_constraints_node_st **_nc,
 	memset(types_with_empty_intersection, 0,
 	       sizeof(types_with_empty_intersection));
 
-	if (*_nc == NULL || _nc2 == NULL)
+	if (permitted->size == 0 || permitted2->size == 0)
 		return 0;
 
 	/* Phase 1
-	 * For each name in _NC, if a _NC2 does not contain a name
-	 * with the same type, preserve the original name.
-	 * Do this also for node of unknown type (not DNS, email, IP */
-	t = nc = *_nc;
-	while (t != NULL) {
-		name_constraints_node_st *next = t->next;
-		nc2 = _nc2;
-		while (nc2 != NULL) {
-			if (t->type == nc2->type) {
+	 * For each name in PERMITTED, if a PERMITTED2 does not contain a name
+	 * with the same type, move the original name to REMOVED.
+	 * Do this also for node of unknown type (not DNS, email, IP) */
+	for (size_t i = 0; i < permitted->size;) {
+		struct name_constraints_node_st *t = permitted->data[i];
+		const struct name_constraints_node_st *found = NULL;
+
+		for (size_t j = 0; j < permitted2->size; j++) {
+			const struct name_constraints_node_st *t2 =
+				permitted2->data[j];
+			if (t->type == t2->type) {
 				// check bounds (we will use 't->type' as index)
-				if (t->type > GNUTLS_SAN_MAX || t->type == 0)
-					return gnutls_assert_val(
-						GNUTLS_E_INTERNAL_ERROR);
+				if (t->type > GNUTLS_SAN_MAX || t->type == 0) {
+					gnutls_assert();
+					ret = GNUTLS_E_INTERNAL_ERROR;
+					goto cleanup;
+				}
 				// note the possibility of empty intersection for this type
 				// if we add something to the intersection in phase 2,
 				// we will reset this flag back to 0 then
 				types_with_empty_intersection[t->type - 1] = 1;
+				found = t2;
 				break;
 			}
-			nc2 = nc2->next;
 		}
-		if (nc2 == NULL || (t->type != GNUTLS_SAN_DNSNAME &&
-				    t->type != GNUTLS_SAN_RFC822NAME &&
-				    t->type != GNUTLS_SAN_IPADDRESS)) {
-			/* move node from NC to DEST */
-			if (prev != NULL)
-				prev->next = next;
-			else
-				prev = nc = next;
-			t->next = dest;
-			dest = t;
-		} else {
-			prev = t;
+
+		if (found != NULL && (t->type == GNUTLS_SAN_DNSNAME ||
+				      t->type == GNUTLS_SAN_RFC822NAME ||
+				      t->type == GNUTLS_SAN_IPADDRESS)) {
+			/* move node from PERMITTED to REMOVED */
+			ret = name_constraints_node_list_add(&removed, t);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
+			/* remove node by swapping */
+			if (i < permitted->size - 1)
+				permitted->data[i] =
+					permitted->data[permitted->size - 1];
+			permitted->size--;
+			continue;
 		}
-		t = next;
+		i++;
 	}
 
 	/* Phase 2
-	 * iterate through all combinations from nc2 and nc1
+	 * iterate through all combinations from PERMITTED2 and PERMITTED
 	 * and create intersections of nodes with same type */
-	nc2 = _nc2;
-	while (nc2 != NULL) {
-		// current nc2 node has not yet been used for any intersection
-		// (and is not in DEST either)
+	for (size_t i = 0; i < permitted2->size; i++) {
+		const struct name_constraints_node_st *t2 = permitted2->data[i];
+
+		// current PERMITTED2 node has not yet been used for any intersection
+		// (and is not in REMOVED either)
 		used = 0;
-		t = nc;
-		while (t != NULL) {
+		for (size_t j = 0; j < removed.size; j++) {
+			const struct name_constraints_node_st *t =
+				removed.data[j];
 			// save intersection of name constraints into tmp
-			ret = name_constraints_intersect_nodes(t, nc2, &tmp);
-			if (ret < 0)
-				return gnutls_assert_val(ret);
+			ret = name_constraints_intersect_nodes(nc, t, t2, &tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
+			}
 			used = 1;
 			// if intersection is not empty
 			if (tmp !=
@@ -360,32 +428,34 @@ _gnutls_name_constraints_intersect(name_constraints_node_st **_nc,
 				// we will not add universal excluded constraint for this type
 				types_with_empty_intersection[tmp->type - 1] =
 					0;
-				// add intersection node to DEST
-				tmp->next = dest;
-				dest = tmp;
+				// add intersection node to PERMITTED
+				ret = name_constraints_node_list_add(permitted,
+								     tmp);
+				if (ret < 0) {
+					gnutls_assert();
+					goto cleanup;
+				}
 			}
-			t = t->next;
 		}
-		// if the node from nc2 was not used for intersection, copy it to DEST
+		// if the node from PERMITTED2 was not used for intersection, copy it to DEST
 		// Beware: also copies nodes other than DNS, email, IP,
 		//       since their counterpart may have been moved in phase 1.
 		if (!used) {
 			tmp = name_constraints_node_new(
-				nc2->type, nc2->name.data, nc2->name.size);
+				nc, t2->type, t2->name.data, t2->name.size);
 			if (tmp == NULL) {
-				_gnutls_name_constraints_node_free(dest);
-				return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(permitted, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
 			}
-			tmp->next = dest;
-			dest = tmp;
 		}
-		nc2 = nc2->next;
 	}
 
-	/* replace the original with the new */
-	_gnutls_name_constraints_node_free(nc);
-	*_nc = dest;
-
 	/* Phase 3
 	 * For each type: If we have empty permitted name constraints now
 	 * and we didn't have at the beginning, we have to add a new
@@ -400,63 +470,77 @@ _gnutls_name_constraints_intersect(name_constraints_node_st **_nc,
 		switch (type) {
 		case GNUTLS_SAN_IPADDRESS:
 			// add universal restricted range for IPv4
-			tmp = name_constraints_node_new(GNUTLS_SAN_IPADDRESS,
-							NULL, 8);
+			tmp = name_constraints_node_new(
+				nc, GNUTLS_SAN_IPADDRESS, NULL, 8);
 			if (tmp == NULL) {
-				_gnutls_name_constraints_node_free(dest);
-				return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(excluded, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
 			}
-			tmp->next = *_nc_excluded;
-			*_nc_excluded = tmp;
 			// add universal restricted range for IPv6
-			tmp = name_constraints_node_new(GNUTLS_SAN_IPADDRESS,
-							NULL, 32);
+			tmp = name_constraints_node_new(
+				nc, GNUTLS_SAN_IPADDRESS, NULL, 32);
 			if (tmp == NULL) {
-				_gnutls_name_constraints_node_free(dest);
-				return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(excluded, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
 			}
-			tmp->next = *_nc_excluded;
-			*_nc_excluded = tmp;
 			break;
 		case GNUTLS_SAN_DNSNAME:
 		case GNUTLS_SAN_RFC822NAME:
-			tmp = name_constraints_node_new(type, NULL, 0);
+			tmp = name_constraints_node_new(nc, type, NULL, 0);
 			if (tmp == NULL) {
-				_gnutls_name_constraints_node_free(dest);
-				return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+				gnutls_assert();
+				ret = GNUTLS_E_MEMORY_ERROR;
+				goto cleanup;
+			}
+			ret = name_constraints_node_list_add(excluded, tmp);
+			if (ret < 0) {
+				gnutls_assert();
+				goto cleanup;
 			}
-			tmp->next = *_nc_excluded;
-			*_nc_excluded = tmp;
 			break;
 		default: // do nothing, at least one node was already moved in phase 1
 			break;
 		}
 	}
-	return GNUTLS_E_SUCCESS;
+	ret = GNUTLS_E_SUCCESS;
+
+cleanup:
+	gnutls_free(removed.data);
+	return ret;
 }
 
-static int _gnutls_name_constraints_append(name_constraints_node_st **_nc,
-					   name_constraints_node_st *_nc2)
+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)
 {
-	name_constraints_node_st *nc, *nc2;
-	struct name_constraints_node_st *tmp;
-
-	if (_nc2 == NULL)
-		return 0;
-
-	nc2 = _nc2;
-	while (nc2) {
-		nc = *_nc;
-
-		tmp = name_constraints_node_new(nc2->type, nc2->name.data,
-						nc2->name.size);
-		if (tmp == NULL)
+	for (size_t i = 0; i < nodes2->size; i++) {
+		const struct name_constraints_node_st *node = nodes2->data[i];
+		struct name_constraints_node_st *tmp;
+		int ret;
+
+		tmp = name_constraints_node_new(nc, node->type, node->name.data,
+						node->name.size);
+		if (tmp == NULL) {
 			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
-
-		tmp->next = nc;
-		*_nc = tmp;
-
-		nc2 = nc2->next;
+		}
+		ret = name_constraints_node_list_add(nodes, tmp);
+		if (ret < 0) {
+			name_constraints_node_free(tmp);
+			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+		}
 	}
 
 	return 0;
@@ -524,6 +608,25 @@ cleanup:
 	return ret;
 }
 
+void _gnutls_x509_name_constraints_clear(gnutls_x509_name_constraints_t nc)
+{
+	for (size_t i = 0; i < nc->nodes.size; i++) {
+		struct name_constraints_node_st *node = nc->nodes.data[i];
+		name_constraints_node_free(node);
+	}
+	gnutls_free(nc->nodes.data);
+	nc->nodes.capacity = 0;
+	nc->nodes.size = 0;
+
+	gnutls_free(nc->permitted.data);
+	nc->permitted.capacity = 0;
+	nc->permitted.size = 0;
+
+	gnutls_free(nc->excluded.data);
+	nc->excluded.capacity = 0;
+	nc->excluded.size = 0;
+}
+
 /**
  * gnutls_x509_name_constraints_deinit:
  * @nc: The nameconstraints
@@ -534,9 +637,7 @@ cleanup:
  **/
 void gnutls_x509_name_constraints_deinit(gnutls_x509_name_constraints_t nc)
 {
-	_gnutls_name_constraints_node_free(nc->permitted);
-	_gnutls_name_constraints_node_free(nc->excluded);
-
+	_gnutls_x509_name_constraints_clear(nc);
 	gnutls_free(nc);
 }
 
@@ -552,12 +653,15 @@ void gnutls_x509_name_constraints_deinit(gnutls_x509_name_constraints_t nc)
  **/
 int gnutls_x509_name_constraints_init(gnutls_x509_name_constraints_t *nc)
 {
-	*nc = gnutls_calloc(1, sizeof(struct gnutls_name_constraints_st));
-	if (*nc == NULL) {
+	struct gnutls_name_constraints_st *tmp;
+
+	tmp = gnutls_calloc(1, sizeof(struct gnutls_name_constraints_st));
+	if (tmp == NULL) {
 		gnutls_assert();
 		return GNUTLS_E_MEMORY_ERROR;
 	}
 
+	*nc = tmp;
 	return 0;
 }
 
@@ -565,36 +669,25 @@ static int name_constraints_add(gnutls_x509_name_constraints_t nc,
 				gnutls_x509_subject_alt_name_t type,
 				const gnutls_datum_t *name, unsigned permitted)
 {
-	struct name_constraints_node_st *tmp, *prev = NULL;
+	struct name_constraints_node_st *tmp;
+	struct name_constraints_node_list_st *nodes;
 	int ret;
 
 	ret = validate_name_constraints_node(type, name);
 	if (ret < 0)
 		return gnutls_assert_val(ret);
 
-	if (permitted != 0)
-		prev = tmp = nc->permitted;
-	else
-		prev = tmp = nc->excluded;
+	nodes = permitted ? &nc->permitted : &nc->excluded;
 
-	while (tmp != NULL) {
-		tmp = tmp->next;
-		if (tmp != NULL)
-			prev = tmp;
-	}
-
-	tmp = name_constraints_node_new(type, name->data, name->size);
+	tmp = name_constraints_node_new(nc, type, name->data, name->size);
 	if (tmp == NULL)
 		return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
-	tmp->next = NULL;
 
-	if (prev == NULL) {
-		if (permitted != 0)
-			nc->permitted = tmp;
-		else
-			nc->excluded = tmp;
-	} else
-		prev->next = tmp;
+	ret = name_constraints_node_list_add(nodes, tmp);
+	if (ret < 0) {
+		name_constraints_node_free(tmp);
+		return gnutls_assert_val(ret);
+	}
 
 	return 0;
 }
@@ -620,14 +713,15 @@ int _gnutls_x509_name_constraints_merge(gnutls_x509_name_constraints_t nc,
 {
 	int ret;
 
-	ret = _gnutls_name_constraints_intersect(&nc->permitted, nc2->permitted,
-						 &nc->excluded);
+	ret = name_constraints_node_list_intersect(
+		nc, &nc->permitted, &nc2->permitted, &nc->excluded);
 	if (ret < 0) {
 		gnutls_assert();
 		return ret;
 	}
 
-	ret = _gnutls_name_constraints_append(&nc->excluded, nc2->excluded);
+	ret = name_constraints_node_list_concat(nc, &nc->excluded,
+						&nc2->excluded);
 	if (ret < 0) {
 		gnutls_assert();
 		return ret;
@@ -804,50 +898,51 @@ static unsigned email_matches(const gnutls_datum_t *name,
  *
  * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value.
  -*/
-static int
-name_constraints_intersect_nodes(name_constraints_node_st *nc1,
-				 name_constraints_node_st *nc2,
-				 name_constraints_node_st **_intersection)
+static int name_constraints_intersect_nodes(
+	gnutls_x509_name_constraints_t nc,
+	const struct name_constraints_node_st *node1,
+	const struct name_constraints_node_st *node2,
+	struct name_constraints_node_st **_intersection)
 {
 	// presume empty intersection
-	name_constraints_node_st *intersection = NULL;
-	name_constraints_node_st *to_copy = NULL;
+	struct name_constraints_node_st *intersection = NULL;
+	const struct name_constraints_node_st *to_copy = NULL;
 	unsigned iplength = 0;
 	unsigned byte;
 
 	*_intersection = NULL;
 
-	if (nc1->type != nc2->type) {
+	if (node1->type != node2->type) {
 		return GNUTLS_E_SUCCESS;
 	}
-	switch (nc1->type) {
+	switch (node1->type) {
 	case GNUTLS_SAN_DNSNAME:
-		if (!dnsname_matches(&nc2->name, &nc1->name))
+		if (!dnsname_matches(&node2->name, &node1->name))
 			return GNUTLS_E_SUCCESS;
-		to_copy = nc2;
+		to_copy = node2;
 		break;
 	case GNUTLS_SAN_RFC822NAME:
-		if (!email_matches(&nc2->name, &nc1->name))
+		if (!email_matches(&node2->name, &node1->name))
 			return GNUTLS_E_SUCCESS;
-		to_copy = nc2;
+		to_copy = node2;
 		break;
 	case GNUTLS_SAN_IPADDRESS:
-		if (nc1->name.size != nc2->name.size)
+		if (node1->name.size != node2->name.size)
 			return GNUTLS_E_SUCCESS;
-		iplength = nc1->name.size / 2;
+		iplength = node1->name.size / 2;
 		for (byte = 0; byte < iplength; byte++) {
-			if (((nc1->name.data[byte] ^
-			      nc2->name.data[byte]) // XOR of addresses
-			     &
-			     nc1->name.data[byte + iplength] // AND mask from nc1
-			     &
-			     nc2->name.data[byte + iplength]) // AND mask from nc2
+			if (((node1->name.data[byte] ^
+			      node2->name.data[byte]) // XOR of addresses
+			     & node1->name.data[byte +
+						iplength] // AND mask from nc1
+			     & node2->name.data[byte +
+						iplength]) // AND mask from nc2
 			    != 0) {
 				// CIDRS do not intersect
 				return GNUTLS_E_SUCCESS;
 			}
 		}
-		to_copy = nc2;
+		to_copy = node2;
 		break;
 	default:
 		// for other types, we don't know how to do the intersection, assume empty
@@ -856,8 +951,9 @@ name_constraints_intersect_nodes(name_constraints_node_st *nc1,
 
 	// copy existing node if applicable
 	if (to_copy != NULL) {
-		*_intersection = name_constraints_node_new(
-			to_copy->type, to_copy->name.data, to_copy->name.size);
+		*_intersection = name_constraints_node_new(nc, to_copy->type,
+							   to_copy->name.data,
+							   to_copy->name.size);
 		if (*_intersection == NULL)
 			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
 		intersection = *_intersection;
@@ -869,12 +965,12 @@ name_constraints_intersect_nodes(name_constraints_node_st *nc1,
 			_gnutls_mask_ip(intersection->name.data,
 					intersection->name.data + iplength,
 					iplength);
-			_gnutls_mask_ip(nc1->name.data,
-					nc1->name.data + iplength, iplength);
+			_gnutls_mask_ip(node1->name.data,
+					node1->name.data + iplength, iplength);
 			// update intersection, if necessary (we already know one is subset of other)
 			for (byte = 0; byte < 2 * iplength; byte++) {
 				intersection->name.data[byte] |=
-					nc1->name.data[byte];
+					node1->name.data[byte];
 			}
 		}
 	}
@@ -1177,10 +1273,17 @@ gnutls_x509_name_constraints_check_crt(gnutls_x509_name_constraints_t nc,
 	unsigned idx, t, san_type;
 	gnutls_datum_t n;
 	unsigned found_one;
+	size_t checks;
 
-	if (is_nc_empty(nc, type) != 0)
+	if (_gnutls_x509_name_constraints_is_empty(nc, type) != 0)
 		return 1; /* shortcut; no constraints to check */
 
+	if (!INT_ADD_OK(nc->permitted.size, nc->excluded.size, &checks) ||
+	    !INT_MULTIPLY_OK(checks, cert->san->size, &checks) ||
+	    checks > MAX_NC_CHECKS) {
+		return gnutls_assert_val(0);
+	}
+
 	if (type == GNUTLS_SAN_RFC822NAME) {
 		found_one = 0;
 		for (idx = 0;; idx++) {
@@ -1378,20 +1481,13 @@ int gnutls_x509_name_constraints_get_permitted(gnutls_x509_name_constraints_t nc
 					       unsigned idx, unsigned *type,
 					       gnutls_datum_t *name)
 {
-	unsigned int i;
-	struct name_constraints_node_st *tmp = nc->permitted;
+	const struct name_constraints_node_st *tmp;
 
-	for (i = 0; i < idx; i++) {
-		if (tmp == NULL)
-			return gnutls_assert_val(
-				GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
-
-		tmp = tmp->next;
-	}
-
-	if (tmp == NULL)
+	if (idx >= nc->permitted.size)
 		return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
 
+	tmp = nc->permitted.data[idx];
+
 	*type = tmp->type;
 	*name = tmp->name;
 
@@ -1421,20 +1517,13 @@ int gnutls_x509_name_constraints_get_excluded(gnutls_x509_name_constraints_t nc,
 					      unsigned idx, unsigned *type,
 					      gnutls_datum_t *name)
 {
-	unsigned int i;
-	struct name_constraints_node_st *tmp = nc->excluded;
+	const struct name_constraints_node_st *tmp;
 
-	for (i = 0; i < idx; i++) {
-		if (tmp == NULL)
-			return gnutls_assert_val(
-				GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
-
-		tmp = tmp->next;
-	}
-
-	if (tmp == NULL)
+	if (idx >= nc->excluded.size)
 		return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
 
+	tmp = nc->excluded.data[idx];
+
 	*type = tmp->type;
 	*name = tmp->name;
 
diff --git a/lib/x509/x509_ext.c b/lib/x509/x509_ext.c
index ad3af1430..064ca8357 100644
--- a/lib/x509/x509_ext.c
+++ b/lib/x509/x509_ext.c
@@ -34,10 +34,6 @@
 #include "intprops.h"
 
 #define MAX_ENTRIES 64
-struct gnutls_subject_alt_names_st {
-	struct name_st *names;
-	unsigned int size;
-};
 
 /**
  * gnutls_subject_alt_names_init:
@@ -389,22 +385,15 @@ int gnutls_x509_ext_import_name_constraints(const gnutls_datum_t *ext,
 	}
 
 	if (flags & GNUTLS_NAME_CONSTRAINTS_FLAG_APPEND &&
-	    (nc->permitted != NULL || nc->excluded != NULL)) {
+	    !_gnutls_x509_name_constraints_is_empty(nc, 0)) {
 		ret = gnutls_x509_name_constraints_init(&nc2);
 		if (ret < 0) {
 			gnutls_assert();
 			goto cleanup;
 		}
 
-		ret = _gnutls_extract_name_constraints(c2, "permittedSubtrees",
-						       &nc2->permitted);
-		if (ret < 0) {
-			gnutls_assert();
-			goto cleanup;
-		}
-
-		ret = _gnutls_extract_name_constraints(c2, "excludedSubtrees",
-						       &nc2->excluded);
+		ret = _gnutls_x509_name_constraints_extract(
+			c2, "permittedSubtrees", "excludedSubtrees", nc2);
 		if (ret < 0) {
 			gnutls_assert();
 			goto cleanup;
@@ -416,18 +405,10 @@ int gnutls_x509_ext_import_name_constraints(const gnutls_datum_t *ext,
 			goto cleanup;
 		}
 	} else {
-		_gnutls_name_constraints_node_free(nc->permitted);
-		_gnutls_name_constraints_node_free(nc->excluded);
+		_gnutls_x509_name_constraints_clear(nc);
 
-		ret = _gnutls_extract_name_constraints(c2, "permittedSubtrees",
-						       &nc->permitted);
-		if (ret < 0) {
-			gnutls_assert();
-			goto cleanup;
-		}
-
-		ret = _gnutls_extract_name_constraints(c2, "excludedSubtrees",
-						       &nc->excluded);
+		ret = _gnutls_x509_name_constraints_extract(
+			c2, "permittedSubtrees", "excludedSubtrees", nc);
 		if (ret < 0) {
 			gnutls_assert();
 			goto cleanup;
@@ -463,9 +444,10 @@ int gnutls_x509_ext_export_name_constraints(gnutls_x509_name_constraints_t nc,
 	int ret, result;
 	uint8_t null = 0;
 	asn1_node c2 = NULL;
-	struct name_constraints_node_st *tmp;
+	unsigned rtype;
+	gnutls_datum_t rname;
 
-	if (nc->permitted == NULL && nc->excluded == NULL)
+	if (_gnutls_x509_name_constraints_is_empty(nc, 0))
 		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
 
 	result = asn1_create_element(_gnutls_get_pkix(),
@@ -475,11 +457,20 @@ int gnutls_x509_ext_export_name_constraints(gnutls_x509_name_constraints_t nc,
 		return _gnutls_asn2err(result);
 	}
 
-	if (nc->permitted == NULL) {
+	ret = gnutls_x509_name_constraints_get_permitted(nc, 0, &rtype, &rname);
+	if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
 		(void)asn1_write_value(c2, "permittedSubtrees", NULL, 0);
 	} else {
-		tmp = nc->permitted;
-		do {
+		for (unsigned i = 0;; i++) {
+			ret = gnutls_x509_name_constraints_get_permitted(
+				nc, i, &rtype, &rname);
+			if (ret < 0) {
+				if (ret ==
+				    GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+					break;
+				gnutls_assert();
+				goto cleanup;
+			}
 			result = asn1_write_value(c2, "permittedSubtrees",
 						  "NEW", 1);
 			if (result != ASN1_SUCCESS) {
@@ -506,21 +497,29 @@ int gnutls_x509_ext_export_name_constraints(gnutls_x509_name_constraints_t nc,
 			}
 
 			ret = _gnutls_write_general_name(
-				c2, "permittedSubtrees.?LAST.base", tmp->type,
-				tmp->name.data, tmp->name.size);
+				c2, "permittedSubtrees.?LAST.base", rtype,
+				rname.data, rname.size);
 			if (ret < 0) {
 				gnutls_assert();
 				goto cleanup;
 			}
-			tmp = tmp->next;
-		} while (tmp != NULL);
+		}
 	}
 
-	if (nc->excluded == NULL) {
+	ret = gnutls_x509_name_constraints_get_excluded(nc, 0, &rtype, &rname);
+	if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
 		(void)asn1_write_value(c2, "excludedSubtrees", NULL, 0);
 	} else {
-		tmp = nc->excluded;
-		do {
+		for (unsigned i = 0;; i++) {
+			ret = gnutls_x509_name_constraints_get_excluded(
+				nc, i, &rtype, &rname);
+			if (ret < 0) {
+				if (ret ==
+				    GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+					break;
+				gnutls_assert();
+				goto cleanup;
+			}
 			result = asn1_write_value(c2, "excludedSubtrees", "NEW",
 						  1);
 			if (result != ASN1_SUCCESS) {
@@ -546,14 +545,13 @@ int gnutls_x509_ext_export_name_constraints(gnutls_x509_name_constraints_t nc,
 			}
 
 			ret = _gnutls_write_general_name(
-				c2, "excludedSubtrees.?LAST.base", tmp->type,
-				tmp->name.data, tmp->name.size);
+				c2, "excludedSubtrees.?LAST.base", rtype,
+				rname.data, rname.size);
 			if (ret < 0) {
 				gnutls_assert();
 				goto cleanup;
 			}
-			tmp = tmp->next;
-		} while (tmp != NULL);
+		}
 	}
 
 	ret = _gnutls_x509_der_encode(c2, "", ext, 0);
diff --git a/lib/x509/x509_ext_int.h b/lib/x509/x509_ext_int.h
index 558d61956..b37d74997 100644
--- a/lib/x509/x509_ext_int.h
+++ b/lib/x509/x509_ext_int.h
@@ -29,6 +29,11 @@ struct name_st {
 	gnutls_datum_t othername_oid;
 };
 
+struct gnutls_subject_alt_names_st {
+	struct name_st *names;
+	unsigned int size;
+};
+
 int _gnutls_alt_name_process(gnutls_datum_t *out, unsigned type,
 			     const gnutls_datum_t *san, unsigned raw);
 
diff --git a/lib/x509/x509_int.h b/lib/x509/x509_int.h
index 4ec55bd75..211743ced 100644
--- a/lib/x509/x509_int.h
+++ b/lib/x509/x509_int.h
@@ -485,20 +485,13 @@ int _gnutls_x509_crt_check_revocation(gnutls_x509_crt_t cert,
 				      int crl_list_length,
 				      gnutls_verify_output_function func);
 
-typedef struct gnutls_name_constraints_st {
-	struct name_constraints_node_st *permitted;
-	struct name_constraints_node_st *excluded;
-} gnutls_name_constraints_st;
-
-typedef struct name_constraints_node_st {
-	unsigned type;
-	gnutls_datum_t name;
-	struct name_constraints_node_st *next;
-} name_constraints_node_st;
-
-int _gnutls_extract_name_constraints(asn1_node c2, const char *vstr,
-				     name_constraints_node_st **_nc);
-void _gnutls_name_constraints_node_free(name_constraints_node_st *node);
+bool _gnutls_x509_name_constraints_is_empty(gnutls_x509_name_constraints_t nc,
+					    unsigned type);
+int _gnutls_x509_name_constraints_extract(asn1_node c2,
+					  const char *permitted_name,
+					  const char *excluded_name,
+					  gnutls_x509_name_constraints_t nc);
+void _gnutls_x509_name_constraints_clear(gnutls_x509_name_constraints_t nc);
 int _gnutls_x509_name_constraints_merge(gnutls_x509_name_constraints_t nc,
 					gnutls_x509_name_constraints_t nc2);
 
