new file mode 100644
@@ -0,0 +1,1149 @@
+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);
+
@@ -23,6 +23,7 @@ SRC_URI = "https://www.gnupg.org/ftp/gcrypt/gnutls/v${SHRT_VER}/gnutls-${PV}.tar
file://0001-Creating-.hmac-file-should-be-excuted-in-target-envi.patch \
file://run-ptest \
file://Add-ptest-support.patch \
+ file://CVE-2024-12243.patch \
"
SRC_URI[sha256sum] = "2bea4e154794f3f00180fa2a5c51fe8b005ac7a31cd58bd44cdfa7f36ebc3a9b"