Version in base suite: 3.7.9-2+deb12u5 Base version: gnutls28_3.7.9-2+deb12u5 Target version: gnutls28_3.7.9-2+deb12u6 Base file: /srv/ftp-master.debian.org/ftp/pool/main/g/gnutls28/gnutls28_3.7.9-2+deb12u5.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/g/gnutls28/gnutls28_3.7.9-2+deb12u6.dsc changelog | 13 patches/70_0001-pkcs11-try-to-initialize-modules-in-thread-safe-mode.patch | 679 ++++++++++ patches/70_0002-pkcs11-avoid-stack-overwrite-when-initializing-a-tok.patch | 241 +++ patches/71_0001-x509-name_constraints-use-actual-zeroes-in-universal.patch | 99 + patches/71_0002-tests-name-constraints-ip-stop-swallowing-errors.patch | 23 patches/71_0003-x509-name_constraints-reject-some-malformed-domain-n.patch | 40 patches/71_0004-x509-name_constraints-name_constraints_node_add_-new.patch | 227 +++ patches/71_0005-x509-name_constraints-introduce-a-rich-comparator.patch | 516 +++++++ patches/71_0006-x509-name_constraints-add-sorted_view-in-preparation.patch | 160 ++ patches/71_0007-x509-name_constraints-implement-name_constraints_nod.patch | 183 ++ patches/71_0008-x509-name_constraints-make-types_with_empty_intersec.patch | 128 + patches/71_0009-x509-name_constraints-name_constraints_node_list_int.patch | 428 ++++++ patches/series | 11 13 files changed, 2748 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpua90awin/gnutls28_3.7.9-2+deb12u5.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpua90awin/gnutls28_3.7.9-2+deb12u6.dsc: no acceptable signature found diff -Nru gnutls28-3.7.9/debian/changelog gnutls28-3.7.9/debian/changelog --- gnutls28-3.7.9/debian/changelog 2025-07-15 05:02:19.000000000 +0000 +++ gnutls28-3.7.9/debian/changelog 2026-02-14 14:49:14.000000000 +0000 @@ -1,3 +1,16 @@ +gnutls28 (3.7.9-2+deb12u6) bookworm-security; urgency=high + + * Add patch for CVE-2025-9820 / GNUTLS-SA-2025-11-18 from 3.8.11. + Closes: #1121146 + * libgnutls: Fix name constraint processing performance issue + Verifying certificates with pathological amounts of name constraints + could lead to a denial of service attack via resource exhaustion. + Reworked processing algorithms exhibit better performance + characteristics. Reported by Tim Scheckenbach. + [Fixes: GNUTLS-SA-2026-02-09-2, CVSS: medium] [CVE-2025-14831] + + -- Andreas Metzler Sat, 14 Feb 2026 15:49:14 +0100 + gnutls28 (3.7.9-2+deb12u5) bookworm-security; urgency=medium * Cherry-pick fixes from 3.8.10 release: diff -Nru gnutls28-3.7.9/debian/patches/70_0001-pkcs11-try-to-initialize-modules-in-thread-safe-mode.patch gnutls28-3.7.9/debian/patches/70_0001-pkcs11-try-to-initialize-modules-in-thread-safe-mode.patch --- gnutls28-3.7.9/debian/patches/70_0001-pkcs11-try-to-initialize-modules-in-thread-safe-mode.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/70_0001-pkcs11-try-to-initialize-modules-in-thread-safe-mode.patch 2026-02-14 12:49:16.000000000 +0000 @@ -0,0 +1,679 @@ +From aa5f15a872e62e54abe58624ee393e68d1faf689 Mon Sep 17 00:00:00 2001 +From: Daiki Ueno +Date: Tue, 2 Sep 2025 06:53:34 +0900 +Subject: [PATCH] pkcs11: try to initialize modules in thread-safe mode + +When modules are initialized without CKF_OS_LOCKING_OK nor custom +locking functions, they may skip their internal locking assuming that +the applications will take care of thread-safety, which is costly and +GnuTLS currently doesn't do that. + +To mitigate this, this patch changes the module initialization code to +tell the modules to guarantee thread-safety by themselves. If they are +unable to do that, this falls back to the normal initialization +without C_Initialize parameters. This also omits the custom_init flag, +which indicated whether the module is initialized with +p11_kit_module_initialize or a direct call to C_Initialize, now that +modules are always initialized with C_Initialize. + +Signed-off-by: Daiki Ueno +--- + .gitignore | 2 + + lib/pkcs11.c | 186 ++++++++++++++++++++++++----------- + tests/Makefile.am | 10 +- + tests/pkcs11/os-locking-ok.c | 165 +++++++++++++++++++++++++++++++ + tests/pkcs11/pkcs11-mock4.c | 115 ++++++++++++++++++++++ + 5 files changed, 419 insertions(+), 59 deletions(-) + create mode 100644 tests/pkcs11/os-locking-ok.c + create mode 100644 tests/pkcs11/pkcs11-mock4.c + +--- a/lib/pkcs11.c ++++ b/lib/pkcs11.c +@@ -49,14 +49,14 @@ + GNUTLS_STATIC_MUTEX(pkcs11_mutex); + + struct gnutls_pkcs11_provider_st { + struct ck_function_list *module; + unsigned active; +- unsigned custom_init; + unsigned trusted; /* in the sense of p11-kit trusted: + * it can be used for verification */ + struct ck_info info; ++ struct ck_c_initialize_args init_args; + }; + + struct find_flags_data_st { + struct p11_kit_uri *info; + unsigned int slot_flags; /* Slot Information Flags */ +@@ -226,45 +226,126 @@ static int scan_slots(struct gnutls_pkcs + return pkcs11_rv_to_err(rv); + } + return 0; + } + +-static int +-pkcs11_add_module(const char* name, struct ck_function_list *module, unsigned custom_init, const char *params) ++static const struct ck_c_initialize_args default_init_args = { ++ NULL, ++ NULL, ++ NULL, ++ NULL, ++ CKF_LIBRARY_CANT_CREATE_OS_THREADS | CKF_OS_LOCKING_OK, ++ NULL, ++}; ++ ++static const struct ck_c_initialize_args no_thread_init_args = { ++ NULL, NULL, NULL, NULL, CKF_LIBRARY_CANT_CREATE_OS_THREADS, NULL, ++}; ++ ++static void pkcs11_provider_deinit(struct gnutls_pkcs11_provider_st *provider) ++{ ++ p11_kit_module_finalize(provider->module); ++ p11_kit_module_release(provider->module); ++ gnutls_free(provider->init_args.reserved); ++} ++ ++static int pkcs11_provider_init(struct gnutls_pkcs11_provider_st *provider, ++ struct ck_function_list *module, ++ const void *params) ++{ ++ struct ck_c_initialize_args args; ++ const void *reserved = NULL; ++ ck_rv_t rv; ++ char *name; ++ char *p; ++ ++ name = p11_kit_module_get_name(module); ++ ++ _gnutls_debug_log("p11: Initializing module: %s\n", name); ++ ++ if (params && (p = strstr(params, "p11-kit:")) != 0) { ++ reserved = (char *)(p + sizeof("p11-kit:") - 1); ++ } ++ ++ /* First try with CKF_OS_LOCKING_OK, then fall back without it */ ++ args = default_init_args; ++ args.reserved = (void *)reserved; ++ rv = module->C_Initialize(&args); ++ ++ if (rv == CKR_CANT_LOCK) { ++ args = no_thread_init_args; ++ args.reserved = (void *)reserved; ++ rv = module->C_Initialize(&args); ++ } ++ ++ if (rv != CKR_OK) { ++ int ret; ++ ++ gnutls_assert(); ++ free(name); ++ ret = pkcs11_rv_to_err(rv); ++ assert(ret < 0); ++ return ret; ++ } ++ ++ if (args.flags & CKF_OS_LOCKING_OK) { ++ _gnutls_debug_log( ++ "p11: Module %s is initialized in a thread-safe mode\n", ++ name); ++ } else { ++ _gnutls_debug_log( ++ "p11: Module %s is initialized NOT in a thread-safe mode\n", ++ name); ++ } ++ free(name); ++ ++ if (args.reserved) { ++ args.reserved = gnutls_strdup((const char *)args.reserved); ++ if (!args.reserved) ++ return GNUTLS_E_MEMORY_ERROR; ++ } ++ ++ memset(provider, 0, sizeof(*provider)); ++ provider->module = module; ++ provider->init_args = args; ++ if (p11_kit_module_get_flags(module) & P11_KIT_MODULE_TRUSTED || ++ (params != NULL && strstr(params, "trusted") != 0)) ++ provider->trusted = 1; ++ ++ return 0; ++} ++ ++static int pkcs11_provider_add(const struct gnutls_pkcs11_provider_st *provider) + { + unsigned int i; + struct ck_info info; + + if (active_providers >= MAX_PROVIDERS) { + gnutls_assert(); + return GNUTLS_E_CONSTRAINT_ERROR; + } + + memset(&info, 0, sizeof(info)); +- pkcs11_get_module_info(module, &info); ++ pkcs11_get_module_info(provider->module, &info); + + /* initially check if this module is a duplicate */ + for (i = 0; i < active_providers; i++) { + /* already loaded, skip the rest */ +- if (module == providers[i].module || ++ if (provider->module == providers[i].module || + memcmp(&info, &providers[i].info, sizeof(info)) == 0) { ++ char *name = p11_kit_module_get_name(provider->module); + _gnutls_debug_log("p11: module %s is already loaded.\n", name); ++ free(name); + return GNUTLS_E_INT_RET_0; + } + } + +- active_providers++; +- providers[active_providers - 1].module = module; +- providers[active_providers - 1].active = 1; +- providers[active_providers - 1].trusted = 0; +- providers[active_providers - 1].custom_init = custom_init; ++ memcpy(&providers[active_providers], provider, sizeof(*provider)); ++ memcpy(&providers[active_providers].info, &info, sizeof(info)); ++ providers[active_providers].active = 1; + +- if (p11_kit_module_get_flags(module) & P11_KIT_MODULE_TRUSTED || +- (params != NULL && strstr(params, "trusted") != 0)) +- providers[active_providers - 1].trusted = 1; +- +- memcpy(&providers[active_providers - 1].info, &info, sizeof(info)); ++ active_providers++; + + return 0; + } + + /* Returns: +@@ -377,56 +458,37 @@ int _gnutls_pkcs11_check_init(init_level + * Since: 2.12.0 + **/ + int gnutls_pkcs11_add_provider(const char *name, const char *params) + { + struct ck_function_list *module; +- unsigned custom_init = 0, flags = 0; +- struct ck_c_initialize_args args; +- const char *p; ++ struct gnutls_pkcs11_provider_st provider; ++ int flags = 0; + int ret; + +- if (params && (p = strstr(params, "p11-kit:")) != 0) { +- memset (&args, 0, sizeof (args)); +- args.reserved = (char*)(p + sizeof("p11-kit:")-1); +- args.flags = CKF_OS_LOCKING_OK; +- +- custom_init = 1; ++ if (params && strstr(params, "p11-kit:") != NULL) { + flags = P11_KIT_MODULE_UNMANAGED; + } + + module = p11_kit_module_load(name, P11_KIT_MODULE_CRITICAL|flags); + if (module == NULL) { + gnutls_assert(); + _gnutls_debug_log("p11: Cannot load provider %s\n", name); + return GNUTLS_E_PKCS11_LOAD_ERROR; + } + +- _gnutls_debug_log +- ("p11: Initializing module: %s\n", name); +- +- /* check if we have special information for a p11-kit trust module */ +- if (custom_init) { +- ret = module->C_Initialize(&args); +- } else { +- ret = p11_kit_module_initialize(module); +- } +- +- if (ret != CKR_OK) { ++ ret = pkcs11_provider_init(&provider, module, params); ++ if (ret != 0) { + p11_kit_module_release(module); + gnutls_assert(); +- return pkcs11_rv_to_err(ret); ++ return ret; + } + +- ret = pkcs11_add_module(name, module, custom_init, params); ++ ret = pkcs11_provider_add(&provider); + if (ret != 0) { + if (ret == GNUTLS_E_INT_RET_0) + ret = 0; +- if (!custom_init) +- p11_kit_module_finalize(module); +- else +- module->C_Finalize(NULL); +- p11_kit_module_release(module); ++ pkcs11_provider_deinit(&provider); + gnutls_assert(); + } + + return ret; + } +@@ -928,33 +990,43 @@ static void compat_load(const char *conf + + static int auto_load(unsigned trusted) + { + struct ck_function_list **modules; + int i, ret; +- char* name; +- +- modules = p11_kit_modules_load_and_initialize(trusted?P11_KIT_MODULE_TRUSTED:0); ++ modules = p11_kit_modules_load(NULL, ++ trusted ? P11_KIT_MODULE_TRUSTED : 0); + if (modules == NULL) { + gnutls_assert(); +- _gnutls_debug_log +- ("Cannot initialize registered modules: %s\n", ++ _gnutls_debug_log("Cannot load registered modules: %s\n", + p11_kit_message()); + return GNUTLS_E_PKCS11_LOAD_ERROR; + } + + for (i = 0; modules[i] != NULL; i++) { +- name = p11_kit_module_get_name(modules[i]); +- _gnutls_debug_log +- ("p11: Initializing module: %s\n", name); ++ struct gnutls_pkcs11_provider_st provider; + +- ret = pkcs11_add_module(name, modules[i], 0, NULL); ++ ret = pkcs11_provider_init(&provider, modules[i], NULL); + if (ret < 0) { + gnutls_assert(); +- _gnutls_debug_log +- ("Cannot load PKCS #11 module: %s\n", name); ++ char *name = p11_kit_module_get_name(modules[i]); ++ _gnutls_debug_log( ++ "Cannot initialize PKCS #11 module: %s\n", ++ name); ++ free(name); ++ continue; ++ } ++ ++ ret = pkcs11_provider_add(&provider); ++ if (ret < 0) { ++ gnutls_assert(); ++ char *name = p11_kit_module_get_name(provider.module); ++ _gnutls_debug_log("Cannot add PKCS #11 module: %s\n", ++ name); ++ free(name); ++ pkcs11_provider_deinit(&provider); ++ continue; + } +- free(name); + } + + /* Shallow free */ + free(modules); + return 0; +@@ -1028,12 +1100,12 @@ static int _gnutls_pkcs11_reinit(void) + unsigned i; + ck_rv_t rv; + + for (i = 0; i < active_providers; i++) { + if (providers[i].module != NULL) { +- rv = p11_kit_module_initialize(providers +- [i].module); ++ rv = providers[i].module->C_Initialize( ++ &providers[i].init_args); + if (rv == CKR_OK || rv == CKR_CRYPTOKI_ALREADY_INITIALIZED) { + providers[i].active = 1; + } else { + providers[i].active = 0; + _gnutls_debug_log +@@ -1096,18 +1168,11 @@ void gnutls_pkcs11_deinit(void) + init--; + if (init > 0) + return; + + for (i = 0; i < active_providers; i++) { +- if (providers[i].active) { +- +- if (!providers[i].custom_init) +- p11_kit_module_finalize(providers[i].module); +- else +- providers[i].module->C_Finalize(NULL); +- } +- p11_kit_module_release(providers[i].module); ++ pkcs11_provider_deinit(&providers[i]); + } + active_providers = 0; + providers_initialized = PROV_UNINITIALIZED; + + gnutls_pkcs11_set_pin_function(NULL, NULL); +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -342,10 +342,15 @@ libpkcs11mock1_la_LIBADD = ../gl/libgnu + noinst_LTLIBRARIES += libpkcs11mock2.la + libpkcs11mock2_la_SOURCES = pkcs11/pkcs11-mock2.c + libpkcs11mock2_la_LDFLAGS = -shared -rpath $(pkglibdir) -module -no-undefined -avoid-version + libpkcs11mock2_la_LIBADD = ../gl/libgnu.la + ++noinst_LTLIBRARIES += libpkcs11mock4.la ++libpkcs11mock4_la_SOURCES = pkcs11/pkcs11-mock4.c ++libpkcs11mock4_la_LDFLAGS = -shared -rpath $(pkglibdir) -module -no-undefined -avoid-version ++libpkcs11mock4_la_LIBADD = ../gl/libgnu.la ++ + pkcs11_cert_import_url_exts_SOURCES = pkcs11/pkcs11-cert-import-url-exts.c + pkcs11_cert_import_url_exts_DEPENDENCIES = libpkcs11mock1.la libutils.la + + pkcs11_cert_import_url4_exts_SOURCES = pkcs11/pkcs11-cert-import-url4-exts.c + pkcs11_cert_import_url4_exts_DEPENDENCIES = libpkcs11mock1.la libutils.la +@@ -483,15 +488,17 @@ buffer_CPPFLAGS = $(AM_CPPFLAGS) \ + -I$(top_builddir)/gl + + if ENABLE_PKCS11 + if !WINDOWS + ctests += tls13/post-handshake-with-cert-pkcs11 pkcs11/tls-neg-pkcs11-no-key \ +- global-init-override ++ global-init-override pkcs11/os-locking-ok + tls13_post_handshake_with_cert_pkcs11_DEPENDENCIES = libpkcs11mock2.la libutils.la + tls13_post_handshake_with_cert_pkcs11_LDADD = $(LDADD) $(LIBDL) + pkcs11_tls_neg_pkcs11_no_key_DEPENDENCIES = libpkcs11mock2.la libutils.la + pkcs11_tls_neg_pkcs11_no_key_LDADD = $(LDADD) $(LIBDL) ++pkcs11_os_locking_ok_DEPENDENCIES = libpkcs11mock4.la libutils.la ++pkcs11_os_locking_ok_LDADD = $(LDADD) $(LIBDL) + endif + endif + + dist_check_SCRIPTS = rfc2253-escape-test.sh rsa-md5-collision/rsa-md5-collision.sh systemkey.sh + +@@ -606,10 +613,11 @@ TESTS_ENVIRONMENT += \ + LC_ALL="C" \ + LSAN_OPTIONS=suppressions=$(srcdir)/gnutls-asan.supp \ + CAFILE=$(srcdir)/cert-tests/data/ca-certs.pem \ + P11MOCKLIB1=$(abs_builddir)/.libs/libpkcs11mock1.so \ + P11MOCKLIB2=$(abs_builddir)/.libs/libpkcs11mock2.so \ ++ P11MOCKLIB4=$(abs_builddir)/.libs/libpkcs11mock4.so \ + PKCS12_MANY_CERTS_FILE=$(srcdir)/cert-tests/data/pkcs12_5certs.p12 \ + PKCS12FILE=$(srcdir)/cert-tests/data/client.p12 \ + PKCS12PASSWORD=foobar \ + PKCS12FILE_2=$(srcdir)/cert-tests/data/pkcs12_2certs.p12 \ + PKCS12PASSWORD_2="" \ +--- /dev/null ++++ b/tests/pkcs11/os-locking-ok.c +@@ -0,0 +1,165 @@ ++/* ++ * Copyright (C) 2025 Red Hat, Inc. ++ * ++ * Author: Daiki Ueno ++ * ++ * This file is part of GnuTLS. ++ * ++ * GnuTLS is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * GnuTLS is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program. If not, see ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++#include ++#include ++ ++#if defined(_WIN32) ++ ++int main(void) ++{ ++ exit(77); ++} ++ ++#else ++ ++#include ++#include ++#include ++ ++#include "cert-common.h" ++#include "pkcs11/softhsm.h" ++#include "utils.h" ++ ++/* This program tests that a module can be initialized with ++ * CKF_OS_LOCKING_OK, even if it's not supported by the module. ++ */ ++ ++static void tls_log_func(int level, const char *str) ++{ ++ fprintf(stderr, "server|<%d>| %s", level, str); ++} ++ ++#define PIN "1234" ++ ++#define CONFIG_NAME "softhsm-os-locking-ok" ++#define CONFIG CONFIG_NAME ".config" ++ ++static int pin_func(void *userdata, int attempt, const char *url, ++ const char *label, unsigned flags, char *pin, ++ size_t pin_max) ++{ ++ if (attempt == 0) { ++ strcpy(pin, PIN); ++ return 0; ++ } ++ return -1; ++} ++ ++static void test(const char *provider) ++{ ++ int ret; ++ gnutls_x509_trust_list_t tl; ++ ++ gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL); ++ ++ success("test with %s\n", provider); ++ ++ if (debug) { ++ gnutls_global_set_log_function(tls_log_func); ++ gnutls_global_set_log_level(4711); ++ } ++ ++ /* point to SoftHSM token that libpkcs11mock4.so internally uses */ ++ setenv(SOFTHSM_ENV, CONFIG, 1); ++ ++ gnutls_pkcs11_set_pin_function(pin_func, NULL); ++ ++ ret = gnutls_pkcs11_add_provider(provider, "trusted"); ++ if (ret != 0) { ++ fail("gnutls_pkcs11_add_provider: %s\n", gnutls_strerror(ret)); ++ } ++ ++ /* initialize softhsm token */ ++ ret = gnutls_pkcs11_token_init(SOFTHSM_URL, PIN, "test"); ++ if (ret < 0) { ++ fail("gnutls_pkcs11_token_init: %s\n", gnutls_strerror(ret)); ++ } ++ ++ ret = gnutls_pkcs11_token_set_pin(SOFTHSM_URL, NULL, PIN, ++ GNUTLS_PIN_USER); ++ if (ret < 0) { ++ fail("gnutls_pkcs11_token_set_pin: %s\n", gnutls_strerror(ret)); ++ } ++ ++ gnutls_x509_trust_list_init(&tl, 0); ++ ++ ret = gnutls_x509_trust_list_add_trust_file(tl, SOFTHSM_URL, NULL, 0, 0, ++ 0); ++ if (ret < 0) { ++ fail("gnutls_x509_trust_list_add_trust_file\n"); ++ } ++ ++ gnutls_x509_trust_list_deinit(tl, 0); ++ ++ gnutls_pkcs11_deinit(); ++} ++ ++void doit(void) ++{ ++ const char *bin; ++ const char *lib; ++ char buf[128]; ++ ++ if (gnutls_fips140_mode_enabled()) ++ exit(77); ++ ++ /* this must be called once in the program */ ++ global_init(); ++ ++ /* we call gnutls_pkcs11_init manually */ ++ gnutls_pkcs11_deinit(); ++ ++ /* check if softhsm module is loadable */ ++ lib = softhsm_lib(); ++ ++ /* initialize SoftHSM token that libpkcs11mock4.so internally uses */ ++ bin = softhsm_bin(); ++ ++ set_softhsm_conf(CONFIG); ++ snprintf(buf, sizeof(buf), ++ "%s --init-token --slot 0 --label test --so-pin " PIN ++ " --pin " PIN, ++ bin); ++ system(buf); ++ ++ test(lib); ++ ++ lib = getenv("P11MOCKLIB4"); ++ if (lib == NULL) { ++ fail("P11MOCKLIB4 is not set\n"); ++ } ++ ++ set_softhsm_conf(CONFIG); ++ snprintf(buf, sizeof(buf), ++ "%s --init-token --slot 0 --label test --so-pin " PIN ++ " --pin " PIN, ++ bin); ++ system(buf); ++ ++ test(lib); ++} ++#endif /* _WIN32 */ +--- /dev/null ++++ b/tests/pkcs11/pkcs11-mock4.c +@@ -0,0 +1,115 @@ ++/* ++ * Copyright (C) 2025 Red Hat, Inc. ++ * ++ * Author: Daiki Ueno ++ * ++ * This file is part of GnuTLS. ++ * ++ * GnuTLS is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * GnuTLS is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program. If not, see ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "softhsm.h" ++ ++/* This provides a mock PKCS #11 module that delegates all the ++ * operations to SoftHSM except that it returns CKR_CANT_LOCK upon ++ * C_Initialize if CKF_OS_LOCKING_OK is set. ++ */ ++ ++static void *dl; ++static CK_C_Initialize base_C_Initialize; ++static CK_FUNCTION_LIST override_funcs; ++ ++#ifdef __sun ++#pragma fini(mock_deinit) ++#pragma init(mock_init) ++#define _CONSTRUCTOR ++#define _DESTRUCTOR ++#else ++#define _CONSTRUCTOR __attribute__((constructor)) ++#define _DESTRUCTOR __attribute__((destructor)) ++#endif ++ ++static CK_RV override_C_Initialize(void *args) ++{ ++ CK_C_INITIALIZE_ARGS *init_args = args; ++ static bool first = true; ++ ++ assert(init_args); ++ ++ if (first) { ++ assert(init_args->flags & CKF_OS_LOCKING_OK); ++ first = false; ++ return CKR_CANT_LOCK; ++ } else { ++ assert(!(init_args->flags & CKF_OS_LOCKING_OK)); ++ } ++ ++ return base_C_Initialize(args); ++} ++ ++CK_RV C_GetFunctionList(CK_FUNCTION_LIST **function_list) ++{ ++ CK_C_GetFunctionList func; ++ CK_FUNCTION_LIST *funcs; ++ ++ assert(dl); ++ ++ func = dlsym(dl, "C_GetFunctionList"); ++ if (func == NULL) { ++ return CKR_GENERAL_ERROR; ++ } ++ ++ func(&funcs); ++ ++ base_C_Initialize = funcs->C_Initialize; ++ ++ memcpy(&override_funcs, funcs, sizeof(CK_FUNCTION_LIST)); ++ override_funcs.C_Initialize = override_C_Initialize; ++ *function_list = &override_funcs; ++ ++ return CKR_OK; ++} ++ ++static _CONSTRUCTOR void mock_init(void) ++{ ++ const char *lib; ++ ++ /* suppress compiler warning */ ++ (void)set_softhsm_conf; ++ ++ lib = softhsm_lib(); ++ ++ dl = dlopen(lib, RTLD_NOW); ++ if (dl == NULL) ++ exit(77); ++} ++ ++static _DESTRUCTOR void mock_deinit(void) ++{ ++ dlclose(dl); ++} diff -Nru gnutls28-3.7.9/debian/patches/70_0002-pkcs11-avoid-stack-overwrite-when-initializing-a-tok.patch gnutls28-3.7.9/debian/patches/70_0002-pkcs11-avoid-stack-overwrite-when-initializing-a-tok.patch --- gnutls28-3.7.9/debian/patches/70_0002-pkcs11-avoid-stack-overwrite-when-initializing-a-tok.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/70_0002-pkcs11-avoid-stack-overwrite-when-initializing-a-tok.patch 2026-02-14 12:49:16.000000000 +0000 @@ -0,0 +1,241 @@ +From 1d56f96f6ab5034d677136b9d50b5a75dff0faf5 Mon Sep 17 00:00:00 2001 +From: Daiki Ueno +Date: Tue, 18 Nov 2025 13:17:55 +0900 +Subject: [PATCH] pkcs11: avoid stack overwrite when initializing a token + +If gnutls_pkcs11_token_init is called with label longer than 32 +characters, the internal storage used to blank-fill it would +overflow. This adds a guard to prevent that. + +Signed-off-by: Daiki Ueno +--- + .gitignore | 2 + + NEWS | 4 + + lib/pkcs11_write.c | 5 +- + tests/Makefile.am | 2 +- + tests/pkcs11/long-label.c | 164 ++++++++++++++++++++++++++++++++++++++ + 5 files changed, 174 insertions(+), 3 deletions(-) + create mode 100644 tests/pkcs11/long-label.c + +--- a/lib/pkcs11_write.c ++++ b/lib/pkcs11_write.c +@@ -26,10 +26,11 @@ + #include + #include + #include "pkcs11x.h" + #include + #include "pk.h" ++#include "minmax.h" + + static const ck_bool_t tval = 1; + static const ck_bool_t fval = 0; + + #define MAX_ASIZE 24 +@@ -1197,11 +1198,11 @@ int gnutls_pkcs11_delete_url(const char + + /** + * gnutls_pkcs11_token_init: + * @token_url: A PKCS #11 URL specifying a token + * @so_pin: Security Officer's PIN +- * @label: A name to be used for the token ++ * @label: A name to be used for the token, at most 32 characters + * + * This function will initialize (format) a token. If the token is + * at a factory defaults state the security officer's PIN given will be + * set to be the default. Otherwise it should match the officer's PIN. + * +@@ -1236,11 +1237,11 @@ gnutls_pkcs11_token_init(const char *tok + } + + /* so it seems memset has other uses than zeroing! */ + memset(flabel, ' ', sizeof(flabel)); + if (label != NULL) +- memcpy(flabel, label, strlen(label)); ++ memcpy(flabel, label, MIN(sizeof(flabel), strlen(label))); + + rv = pkcs11_init_token(module, slot, (uint8_t *) so_pin, + strlen(so_pin), (uint8_t *) flabel); + if (rv != CKR_OK) { + gnutls_assert(); +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -488,11 +488,11 @@ buffer_CPPFLAGS = $(AM_CPPFLAGS) \ + -I$(top_builddir)/gl + + if ENABLE_PKCS11 + if !WINDOWS + ctests += tls13/post-handshake-with-cert-pkcs11 pkcs11/tls-neg-pkcs11-no-key \ +- global-init-override pkcs11/os-locking-ok ++ global-init-override pkcs11/os-locking-ok pkcs11/long-label + tls13_post_handshake_with_cert_pkcs11_DEPENDENCIES = libpkcs11mock2.la libutils.la + tls13_post_handshake_with_cert_pkcs11_LDADD = $(LDADD) $(LIBDL) + pkcs11_tls_neg_pkcs11_no_key_DEPENDENCIES = libpkcs11mock2.la libutils.la + pkcs11_tls_neg_pkcs11_no_key_LDADD = $(LDADD) $(LIBDL) + pkcs11_os_locking_ok_DEPENDENCIES = libpkcs11mock4.la libutils.la +--- /dev/null ++++ b/tests/pkcs11/long-label.c +@@ -0,0 +1,164 @@ ++/* ++ * Copyright (C) 2025 Red Hat, Inc. ++ * ++ * Author: Daiki Ueno ++ * ++ * This file is part of GnuTLS. ++ * ++ * GnuTLS is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * GnuTLS is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program. If not, see ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++#include ++#include ++ ++#if defined(_WIN32) ++ ++int main(void) ++{ ++ exit(77); ++} ++ ++#else ++ ++#include ++#include ++#include ++ ++#include "cert-common.h" ++#include "pkcs11/softhsm.h" ++#include "utils.h" ++ ++/* This program tests that a token can be initialized with ++ * a label longer than 32 characters. ++ */ ++ ++static void tls_log_func(int level, const char *str) ++{ ++ fprintf(stderr, "server|<%d>| %s", level, str); ++} ++ ++#define PIN "1234" ++ ++#define CONFIG_NAME "softhsm-long-label" ++#define CONFIG CONFIG_NAME ".config" ++ ++static int pin_func(void *userdata, int attempt, const char *url, ++ const char *label, unsigned flags, char *pin, ++ size_t pin_max) ++{ ++ if (attempt == 0) { ++ strcpy(pin, PIN); ++ return 0; ++ } ++ return -1; ++} ++ ++static void test(const char *provider) ++{ ++ int ret; ++ size_t i; ++ ++ gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL); ++ ++ success("test with %s\n", provider); ++ ++ if (debug) { ++ gnutls_global_set_log_function(tls_log_func); ++ gnutls_global_set_log_level(4711); ++ } ++ ++ /* point to SoftHSM token that libpkcs11mock4.so internally uses */ ++ setenv(SOFTHSM_ENV, CONFIG, 1); ++ ++ gnutls_pkcs11_set_pin_function(pin_func, NULL); ++ ++ ret = gnutls_pkcs11_add_provider(provider, "trusted"); ++ if (ret != 0) { ++ fail("gnutls_pkcs11_add_provider: %s\n", gnutls_strerror(ret)); ++ } ++ ++ /* initialize softhsm token */ ++ ret = gnutls_pkcs11_token_init( ++ SOFTHSM_URL, PIN, ++ "this is a very long label whose length exceeds 32"); ++ if (ret < 0) { ++ fail("gnutls_pkcs11_token_init: %s\n", gnutls_strerror(ret)); ++ } ++ ++ for (i = 0;; i++) { ++ char *url = NULL; ++ ++ ret = gnutls_pkcs11_token_get_url(i, 0, &url); ++ if (ret < 0) ++ break; ++ if (strstr(url, ++ "token=this%20is%20a%20very%20long%20label%20whose")) ++ break; ++ } ++ if (ret < 0) ++ fail("gnutls_pkcs11_token_get_url: %s\n", gnutls_strerror(ret)); ++ ++ gnutls_pkcs11_deinit(); ++} ++ ++void doit(void) ++{ ++ const char *bin; ++ const char *lib; ++ char buf[128]; ++ ++ if (gnutls_fips140_mode_enabled()) ++ exit(77); ++ ++ /* this must be called once in the program */ ++ global_init(); ++ ++ /* we call gnutls_pkcs11_init manually */ ++ gnutls_pkcs11_deinit(); ++ ++ /* check if softhsm module is loadable */ ++ lib = softhsm_lib(); ++ ++ /* initialize SoftHSM token that libpkcs11mock4.so internally uses */ ++ bin = softhsm_bin(); ++ ++ set_softhsm_conf(CONFIG); ++ snprintf(buf, sizeof(buf), ++ "%s --init-token --slot 0 --label test --so-pin " PIN ++ " --pin " PIN, ++ bin); ++ system(buf); ++ ++ test(lib); ++ ++ lib = getenv("P11MOCKLIB4"); ++ if (lib == NULL) { ++ fail("P11MOCKLIB4 is not set\n"); ++ } ++ ++ set_softhsm_conf(CONFIG); ++ snprintf(buf, sizeof(buf), ++ "%s --init-token --slot 0 --label test --so-pin " PIN ++ " --pin " PIN, ++ bin); ++ system(buf); ++ ++ test(lib); ++} ++#endif /* _WIN32 */ diff -Nru gnutls28-3.7.9/debian/patches/71_0001-x509-name_constraints-use-actual-zeroes-in-universal.patch gnutls28-3.7.9/debian/patches/71_0001-x509-name_constraints-use-actual-zeroes-in-universal.patch --- gnutls28-3.7.9/debian/patches/71_0001-x509-name_constraints-use-actual-zeroes-in-universal.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0001-x509-name_constraints-use-actual-zeroes-in-universal.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,99 @@ +From 0b2377dfccd99be641bf3f1a0de9f0dc8dc0d4b1 Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Mon, 26 Jan 2026 19:02:27 +0100 +Subject: [PATCH 1/9] x509/name_constraints: use actual zeroes in universal + exclude IP NC + +Signed-off-by: Alexander Sosedkin +--- + lib/x509/name_constraints.c | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c +index 2be6a2aaa6..d07482e3c9 100644 +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -57,15 +57,15 @@ 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); ++ const 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; +@@ -281,15 +281,15 @@ static void name_constraints_node_free(struct name_constraints_node_st *node) + * + * Allocate a new name constraints node and set its type, name size and name data. + * + * Returns: Pointer to newly allocated node or NULL in case of memory error. + -*/ + static struct name_constraints_node_st * + name_constraints_node_new(gnutls_x509_name_constraints_t nc, unsigned type, +- unsigned char *data, unsigned int size) ++ const unsigned char *data, unsigned int size) + { + struct name_constraints_node_st *tmp; + int ret; + + tmp = gnutls_calloc(1, sizeof(struct name_constraints_node_st)); + if (tmp == NULL) + return NULL; +@@ -335,14 +335,15 @@ static int name_constraints_node_list_intersect( + struct name_constraints_node_list_st *excluded) + { + struct name_constraints_node_st *tmp; + int ret, type, used; + struct name_constraints_node_list_st removed = { .data = NULL, + .size = 0, + .capacity = 0 }; ++ static const unsigned char universal_ip[32] = { 0 }; + + /* temporary array to see, if we need to add universal excluded constraints + * (see phase 3 for details) + * indexed directly by (gnutls_x509_subject_alt_name_t enum - 1) */ + unsigned char types_with_empty_intersection[GNUTLS_SAN_MAX]; + memset(types_with_empty_intersection, 0, + sizeof(types_with_empty_intersection)); +@@ -470,28 +471,28 @@ static int name_constraints_node_list_intersect( + _gnutls_hard_log( + "Adding universal excluded name constraint for type %d.\n", + type); + switch (type) { + case GNUTLS_SAN_IPADDRESS: + // add universal restricted range for IPv4 + tmp = name_constraints_node_new( +- nc, GNUTLS_SAN_IPADDRESS, NULL, 8); ++ nc, GNUTLS_SAN_IPADDRESS, universal_ip, 8); + if (tmp == NULL) { + gnutls_assert(); + ret = GNUTLS_E_MEMORY_ERROR; + goto cleanup; + } + ret = name_constraints_node_list_add(excluded, tmp); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + // add universal restricted range for IPv6 + tmp = name_constraints_node_new( +- nc, GNUTLS_SAN_IPADDRESS, NULL, 32); ++ nc, GNUTLS_SAN_IPADDRESS, universal_ip, 32); + if (tmp == NULL) { + gnutls_assert(); + ret = GNUTLS_E_MEMORY_ERROR; + goto cleanup; + } + ret = name_constraints_node_list_add(excluded, tmp); + if (ret < 0) { +-- +2.51.0 + diff -Nru gnutls28-3.7.9/debian/patches/71_0002-tests-name-constraints-ip-stop-swallowing-errors.patch gnutls28-3.7.9/debian/patches/71_0002-tests-name-constraints-ip-stop-swallowing-errors.patch --- gnutls28-3.7.9/debian/patches/71_0002-tests-name-constraints-ip-stop-swallowing-errors.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0002-tests-name-constraints-ip-stop-swallowing-errors.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,23 @@ +From 85d6348a30c74d4ee3710e0f4652f634eaad6914 Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Mon, 26 Jan 2026 19:10:58 +0100 +Subject: [PATCH 2/9] tests/name-constraints-ip: stop swallowing errors... + +... now when it started to pass + +Signed-off-by: Alexander Sosedkin +--- + tests/name-constraints-ip.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/tests/name-constraints-ip.c ++++ b/tests/name-constraints-ip.c +@@ -714,7 +714,7 @@ int main(int argc, char **argv) + cmocka_unit_test_setup_teardown(check_ipv6_intersection, setup, teardown), + cmocka_unit_test_setup_teardown(check_empty_ipv4_intersection_ipv6_remains, setup, teardown), + cmocka_unit_test_setup_teardown(check_empty_ipv4v6_intersections, setup, teardown), + cmocka_unit_test_setup_teardown(check_ipv4v6_single_constraint_each, setup, teardown) + }; +- cmocka_run_group_tests(tests, NULL, NULL); ++ return cmocka_run_group_tests(tests, NULL, NULL); + } diff -Nru gnutls28-3.7.9/debian/patches/71_0003-x509-name_constraints-reject-some-malformed-domain-n.patch gnutls28-3.7.9/debian/patches/71_0003-x509-name_constraints-reject-some-malformed-domain-n.patch --- gnutls28-3.7.9/debian/patches/71_0003-x509-name_constraints-reject-some-malformed-domain-n.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0003-x509-name_constraints-reject-some-malformed-domain-n.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,40 @@ +From c28475413f82e1f34295d5c039f0c0a4ca2ee526 Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Mon, 26 Jan 2026 20:14:33 +0100 +Subject: [PATCH 3/9] x509/name_constraints: reject some malformed domain names + +Signed-off-by: Alexander Sosedkin +--- + lib/x509/name_constraints.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -160,10 +160,27 @@ static int validate_name_constraints_nod + int prefix = _gnutls_mask_to_prefix(name->data + name->size/2, name->size/2); + if (prefix < 0) + return gnutls_assert_val(GNUTLS_E_MALFORMED_CIDR); + } + ++ /* Validate DNS names and email addresses for malformed input */ ++ if (type == GNUTLS_SAN_DNSNAME || type == GNUTLS_SAN_RFC822NAME) { ++ unsigned int i; ++ if (name->size == 0) ++ return GNUTLS_E_SUCCESS; ++ ++ /* reject names with consecutive dots... */ ++ for (i = 0; i + 1 < name->size; i++) { ++ if (name->data[i] == '.' && name->data[i + 1] == '.') ++ return gnutls_assert_val( ++ GNUTLS_E_ILLEGAL_PARAMETER); ++ } ++ /* ... or names consisting exclusively of dots */ ++ if (name->size == 1 && name->data[0] == '.') ++ return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); ++ } ++ + return GNUTLS_E_SUCCESS; + } + + static int extract_name_constraints(gnutls_x509_name_constraints_t nc, + asn1_node c2, const char *vstr, diff -Nru gnutls28-3.7.9/debian/patches/71_0004-x509-name_constraints-name_constraints_node_add_-new.patch gnutls28-3.7.9/debian/patches/71_0004-x509-name_constraints-name_constraints_node_add_-new.patch --- gnutls28-3.7.9/debian/patches/71_0004-x509-name_constraints-name_constraints_node_add_-new.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0004-x509-name_constraints-name_constraints_node_add_-new.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,227 @@ +From 6db7da7fcfe230f445b1edbb56e2a8346120c891 Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Thu, 5 Feb 2026 13:22:10 +0100 +Subject: [PATCH 4/9] x509/name_constraints: + name_constraints_node_add_{new,copy} + +Signed-off-by: Alexander Sosedkin +--- + lib/x509/name_constraints.c | 112 ++++++++++++++++-------------------- + 1 file changed, 51 insertions(+), 61 deletions(-) + +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -84,10 +84,42 @@ name_constraints_node_list_add(struct na + } + list->data[list->size++] = node; + return 0; + } + ++static int ++name_constraints_node_add_new(gnutls_x509_name_constraints_t nc, ++ struct name_constraints_node_list_st *list, ++ unsigned type, const unsigned char *data, ++ unsigned int size) ++{ ++ struct name_constraints_node_st *node; ++ int ret; ++ node = name_constraints_node_new(nc, type, data, size); ++ if (node == NULL) { ++ gnutls_assert(); ++ return GNUTLS_E_MEMORY_ERROR; ++ } ++ ret = name_constraints_node_list_add(list, node); ++ if (ret < 0) { ++ gnutls_assert(); ++ return ret; ++ } ++ return GNUTLS_E_SUCCESS; ++} ++ ++static int ++name_constraints_node_add_copy(gnutls_x509_name_constraints_t nc, ++ struct name_constraints_node_list_st *dest, ++ const struct name_constraints_node_st *src) ++{ ++ if (!src) ++ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); ++ return name_constraints_node_add_new(nc, dest, src->type, ++ src->name.data, src->name.size); ++} ++ + // for documentation see the implementation + 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, +@@ -188,11 +220,10 @@ static int extract_name_constraints(gnut + int ret; + char tmpstr[128]; + unsigned indx; + gnutls_datum_t tmp = { NULL, 0 }; + unsigned int type; +- struct name_constraints_node_st *node; + + for (indx=1;;indx++) { + snprintf(tmpstr, sizeof(tmpstr), "%s.?%u.base", vstr, indx); + + ret = +@@ -229,19 +260,13 @@ static int extract_name_constraints(gnut + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + +- node = name_constraints_node_new(nc, type, tmp.data, tmp.size); ++ ret = name_constraints_node_add_new(nc, nodes, type, tmp.data, ++ tmp.size); + _gnutls_free_datum(&tmp); +- if (node == NULL) { +- gnutls_assert(); +- ret = GNUTLS_E_MEMORY_ERROR; +- goto cleanup; +- } +- +- ret = name_constraints_node_list_add(nodes, node); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } +@@ -458,18 +483,11 @@ static int name_constraints_node_list_in + } + // 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( +- nc, t2->type, t2->name.data, t2->name.size); +- if (tmp == NULL) { +- gnutls_assert(); +- ret = GNUTLS_E_MEMORY_ERROR; +- goto cleanup; +- } +- ret = name_constraints_node_list_add(permitted, tmp); ++ ret = name_constraints_node_add_copy(nc, permitted, t2); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } +@@ -487,45 +505,30 @@ static int name_constraints_node_list_in + "Adding universal excluded name constraint for type %d.\n", + type); + switch (type) { + case GNUTLS_SAN_IPADDRESS: + // add universal restricted range for IPv4 +- tmp = name_constraints_node_new( +- nc, GNUTLS_SAN_IPADDRESS, universal_ip, 8); +- if (tmp == NULL) { +- gnutls_assert(); +- ret = GNUTLS_E_MEMORY_ERROR; +- goto cleanup; +- } +- ret = name_constraints_node_list_add(excluded, tmp); ++ ret = name_constraints_node_add_new( ++ nc, excluded, GNUTLS_SAN_IPADDRESS, ++ universal_ip, 8); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + // add universal restricted range for IPv6 +- tmp = name_constraints_node_new( +- nc, GNUTLS_SAN_IPADDRESS, universal_ip, 32); +- if (tmp == NULL) { +- gnutls_assert(); +- ret = GNUTLS_E_MEMORY_ERROR; +- goto cleanup; +- } +- ret = name_constraints_node_list_add(excluded, tmp); ++ ret = name_constraints_node_add_new( ++ nc, excluded, GNUTLS_SAN_IPADDRESS, ++ universal_ip, 32); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + break; + case GNUTLS_SAN_DNSNAME: + case GNUTLS_SAN_RFC822NAME: +- tmp = name_constraints_node_new(nc, type, NULL, 0); +- if (tmp == NULL) { +- gnutls_assert(); +- ret = GNUTLS_E_MEMORY_ERROR; +- goto cleanup; +- } +- ret = name_constraints_node_list_add(excluded, tmp); ++ ret = name_constraints_node_add_new(nc, excluded, type, ++ NULL, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + break; +@@ -543,24 +546,17 @@ cleanup: + 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) + { ++ int ret; ++ + 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); +- } +- ret = name_constraints_node_list_add(nodes, tmp); ++ ret = name_constraints_node_add_copy(nc, nodes, ++ nodes2->data[i]); + if (ret < 0) { +- name_constraints_node_free(tmp); +- return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); ++ return gnutls_assert_val(ret); + } + } + + return 0; + } +@@ -691,29 +687,23 @@ 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; + struct name_constraints_node_list_st *nodes; + int ret; + + ret = validate_name_constraints_node(type, name); + if (ret < 0) + return gnutls_assert_val(ret); + + nodes = permitted ? &nc->permitted : &nc->excluded; + +- tmp = name_constraints_node_new(nc, type, name->data, name->size); +- if (tmp == NULL) +- return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); +- +- ret = name_constraints_node_list_add(nodes, tmp); +- if (ret < 0) { +- name_constraints_node_free(tmp); ++ ret = name_constraints_node_add_new(nc, nodes, type, name->data, ++ name->size); ++ if (ret < 0) + return gnutls_assert_val(ret); +- } + + return 0; + } + + /*- diff -Nru gnutls28-3.7.9/debian/patches/71_0005-x509-name_constraints-introduce-a-rich-comparator.patch gnutls28-3.7.9/debian/patches/71_0005-x509-name_constraints-introduce-a-rich-comparator.patch --- gnutls28-3.7.9/debian/patches/71_0005-x509-name_constraints-introduce-a-rich-comparator.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0005-x509-name_constraints-introduce-a-rich-comparator.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,516 @@ +From 094accd3ebec17ead6c391757eaa18763b72d83f Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Mon, 26 Jan 2026 20:16:36 +0100 +Subject: [PATCH 5/9] x509/name_constraints: introduce a rich comparator + +These are preparatory changes before implementing N * log N intersection +over sorted lists of constraints. + +Signed-off-by: Alexander Sosedkin +--- + lib/x509/name_constraints.c | 411 ++++++++++++++++++++++++++++-------- + 1 file changed, 320 insertions(+), 91 deletions(-) + +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -37,10 +37,13 @@ + #include + + #include "ip.h" + #include "ip-in-cidr.h" + #include "intprops.h" ++#include "minmax.h" ++ ++#include + + #define MAX_NC_CHECKS (1 << 20) + + struct name_constraints_node_st { + unsigned type; +@@ -61,10 +64,286 @@ struct gnutls_name_constraints_st { + + static struct name_constraints_node_st * + name_constraints_node_new(gnutls_x509_name_constraints_t nc, unsigned type, + const unsigned char *data, unsigned int size); + ++/* An enum for "rich" comparisons that not only let us sort name constraints, ++ * children-before-parent, but also subsume them during intersection. */ ++enum name_constraint_relation { ++ NC_SORTS_BEFORE = -2, /* unrelated constraints */ ++ NC_INCLUDED_BY = -1, /* nc1 is included by nc2 / children sort first */ ++ NC_EQUAL = 0, /* exact match */ ++ NC_INCLUDES = 1, /* nc1 includes nc2 / parents sort last */ ++ NC_SORTS_AFTER = 2 /* unrelated constraints */ ++}; ++ ++/* A helper to compare just a pair of strings with this rich comparison */ ++static enum name_constraint_relation ++compare_strings(const void *n1, size_t n1_len, const void *n2, size_t n2_len) ++{ ++ int r = memcmp(n1, n2, MIN(n1_len, n2_len)); ++ if (r < 0) ++ return NC_SORTS_BEFORE; ++ if (r > 0) ++ return NC_SORTS_AFTER; ++ if (n1_len < n2_len) ++ return NC_SORTS_BEFORE; ++ if (n1_len > n2_len) ++ return NC_SORTS_AFTER; ++ return NC_EQUAL; ++} ++ ++/* Rich-compare DNS names. Example order/relationships: ++ * z.x.a INCLUDED_BY x.a BEFORE y.a INCLUDED_BY a BEFORE x.b BEFORE y.b */ ++static enum name_constraint_relation compare_dns_names(const gnutls_datum_t *n1, ++ const gnutls_datum_t *n2) ++{ ++ enum name_constraint_relation rel; ++ unsigned int i, j, i_end, j_end; ++ ++ /* start from the end of each name */ ++ i = i_end = n1->size; ++ j = j_end = n2->size; ++ ++ /* skip the trailing dots for the comparison */ ++ while (i && n1->data[i - 1] == '.') ++ i_end = i = i - 1; ++ while (j && n2->data[j - 1] == '.') ++ j_end = j = j - 1; ++ ++ while (1) { ++ // rewind back to beginning or an after-dot position ++ while (i && n1->data[i - 1] != '.') ++ i--; ++ while (j && n2->data[j - 1] != '.') ++ j--; ++ ++ rel = compare_strings(&n1->data[i], i_end - i, &n2->data[j], ++ j_end - j); ++ if (rel == NC_SORTS_BEFORE) /* x.a BEFORE y.a */ ++ return NC_SORTS_BEFORE; ++ if (rel == NC_SORTS_AFTER) /* y.a AFTER x.a */ ++ return NC_SORTS_AFTER; ++ if (!i && j) /* x.a INCLUDES z.x.a */ ++ return NC_INCLUDES; ++ if (i && !j) /* z.x.a INCLUDED_BY x.a */ ++ return NC_INCLUDED_BY; ++ ++ if (!i && !j) /* r == 0, we ran out of components to compare */ ++ return NC_EQUAL; ++ /* r == 0, i && j: step back past a dot and keep comparing */ ++ i_end = i = i - 1; ++ j_end = j = j - 1; ++ ++ /* support for non-standard ".gr INCLUDES example.gr" [1] */ ++ if (!i && j) /* .a INCLUDES x.a */ ++ return NC_INCLUDES; ++ if (i && !j) /* x.a INCLUDED_BY .a */ ++ return NC_INCLUDED_BY; ++ } ++} ++/* [1] https://mailarchive.ietf.org/arch/msg/saag/Bw6PtreW0G7aEG7SikfzKHES4VA */ ++ ++/* Rich-compare email name constraints. Example order/relationships: ++ * z@x.a INCLUDED_BY x.a BEFORE y.a INCLUDED_BY a BEFORE x@b BEFORE y@b */ ++static enum name_constraint_relation compare_emails(const gnutls_datum_t *n1, ++ const gnutls_datum_t *n2) ++{ ++ enum name_constraint_relation domains_rel; ++ unsigned int i, j, i_end, j_end; ++ gnutls_datum_t d1, d2; /* borrow from n1 and n2 */ ++ ++ /* start from the end of each name */ ++ i = i_end = n1->size; ++ j = j_end = n2->size; ++ ++ /* rewind to @s to look for domains */ ++ while (i && n1->data[i - 1] != '@') ++ i--; ++ d1.size = i_end - i; ++ d1.data = &n1->data[i]; ++ while (j && n2->data[j - 1] != '@') ++ j--; ++ d2.size = j_end - j; ++ d2.data = &n2->data[j]; ++ ++ domains_rel = compare_dns_names(&d1, &d2); ++ ++ /* email constraint semantics differ from DNS ++ * DNS: x.a INCLUDED_BY a ++ * Email: x.a INCLUDED_BY .a BEFORE a */ ++ if (domains_rel == NC_INCLUDED_BY || domains_rel == NC_INCLUDES) { ++ bool d1_has_dot = (d1.size > 0 && d1.data[0] == '.'); ++ bool d2_has_dot = (d2.size > 0 && d2.data[0] == '.'); ++ /* a constraint without a dot is exact, excluding subdomains */ ++ if (!d2_has_dot && domains_rel == NC_INCLUDED_BY) ++ domains_rel = NC_SORTS_BEFORE; /* x.a BEFORE a */ ++ if (!d1_has_dot && domains_rel == NC_INCLUDES) ++ domains_rel = NC_SORTS_AFTER; /* a AFTER x.a */ ++ } ++ ++ if (!i && !j) { /* both are domains-only */ ++ return domains_rel; ++ } else if (i && !j) { /* n1 is email, n2 is domain */ ++ switch (domains_rel) { ++ case NC_SORTS_AFTER: ++ return NC_SORTS_AFTER; ++ case NC_SORTS_BEFORE: ++ return NC_SORTS_BEFORE; ++ case NC_INCLUDES: /* n2 is more specific, a@x.a AFTER z.x.a */ ++ return NC_SORTS_AFTER; ++ case NC_EQUAL: /* subdomains match, z@x.a INCLUDED_BY x.a */ ++ case NC_INCLUDED_BY: /* n1 is more specific */ ++ return NC_INCLUDED_BY; ++ } ++ } else if (!i && j) { /* n1 is domain, n2 is email */ ++ switch (domains_rel) { ++ case NC_SORTS_AFTER: ++ return NC_SORTS_AFTER; ++ case NC_SORTS_BEFORE: ++ return NC_SORTS_BEFORE; ++ case NC_INCLUDES: /* n2 is more specific, a AFTER z@x.a */ ++ return NC_SORTS_AFTER; ++ case NC_EQUAL: /* subdomains match, x.a INCLUDES z@x.a */ ++ return NC_INCLUDES; ++ case NC_INCLUDED_BY: /* n1 is more specific, x.a BEFORE z@a */ ++ return NC_SORTS_BEFORE; ++ } ++ } else if (i && j) { /* both are emails */ ++ switch (domains_rel) { ++ case NC_SORTS_AFTER: ++ return NC_SORTS_AFTER; ++ case NC_SORTS_BEFORE: ++ return NC_SORTS_BEFORE; ++ case NC_INCLUDES: // n2 is more specific ++ return NC_SORTS_AFTER; ++ case NC_INCLUDED_BY: // n1 is more specific ++ return NC_SORTS_BEFORE; ++ case NC_EQUAL: // only case when we need to look before the @ ++ break; // see below for readability ++ } ++ } ++ ++ /* i && j, both are emails, domain names match, compare up to @ */ ++ return compare_strings(n1->data, i - 1, n2->data, j - 1); ++} ++ ++/* Rich-compare IP address constraints. Example order/relationships: ++ * 10.0.0.0/24 INCLUDED_BY 10.0.0.0/16 BEFORE 1::1/128 INCLUDED_BY 1::1/127 */ ++static enum name_constraint_relation compare_ip_ncs(const gnutls_datum_t *n1, ++ const gnutls_datum_t *n2) ++{ ++ unsigned int len, i; ++ int r; ++ const unsigned char *ip1, *ip2, *mask1, *mask2; ++ unsigned char masked11[16], masked22[16], masked12[16], masked21[16]; ++ ++ if (n1->size < n2->size) ++ return NC_SORTS_BEFORE; ++ if (n1->size > n2->size) ++ return NC_SORTS_AFTER; ++ len = n1->size / 2; /* 4 for IPv4, 16 for IPv6 */ ++ ++ /* data is a concatenation of prefix and mask */ ++ ip1 = n1->data; ++ ip2 = n2->data; ++ mask1 = n1->data + len; ++ mask2 = n2->data + len; ++ for (i = 0; i < len; i++) { ++ masked11[i] = ip1[i] & mask1[i]; ++ masked22[i] = ip2[i] & mask2[i]; ++ masked12[i] = ip1[i] & mask2[i]; ++ masked21[i] = ip2[i] & mask1[i]; ++ } ++ ++ r = memcmp(mask1, mask2, len); ++ if (r < 0 && !memcmp(masked11, masked21, len)) /* prefix1 < prefix2 */ ++ return NC_INCLUDES; /* ip1 & mask1 == ip2 & mask1 */ ++ if (r > 0 && !memcmp(masked12, masked22, len)) /* prefix1 > prefix2 */ ++ return NC_INCLUDED_BY; /* ip1 & mask2 == ip2 & mask2 */ ++ ++ r = memcmp(masked11, masked22, len); ++ if (r < 0) ++ return NC_SORTS_BEFORE; ++ else if (r > 0) ++ return NC_SORTS_AFTER; ++ return NC_EQUAL; ++} ++ ++static inline bool is_supported_type(unsigned type) ++{ ++ return type == GNUTLS_SAN_DNSNAME || type == GNUTLS_SAN_RFC822NAME || ++ type == GNUTLS_SAN_IPADDRESS; ++} ++ ++/* Universal comparison for name constraint nodes. ++ * Unsupported types sort before supported types to allow early handling. ++ * NULL represents end-of-list and sorts after everything else. */ ++static enum name_constraint_relation ++compare_name_constraint_nodes(const struct name_constraints_node_st *n1, ++ const struct name_constraints_node_st *n2) ++{ ++ bool n1_supported, n2_supported; ++ ++ if (!n1 && !n2) ++ return NC_EQUAL; ++ if (!n1) ++ return NC_SORTS_AFTER; ++ if (!n2) ++ return NC_SORTS_BEFORE; ++ ++ n1_supported = is_supported_type(n1->type); ++ n2_supported = is_supported_type(n2->type); ++ ++ /* unsupported types bubble up (sort first). intersect relies on this */ ++ if (!n1_supported && n2_supported) ++ return NC_SORTS_BEFORE; ++ if (n1_supported && !n2_supported) ++ return NC_SORTS_AFTER; ++ ++ /* next, sort by type */ ++ if (n1->type < n2->type) ++ return NC_SORTS_BEFORE; ++ if (n1->type > n2->type) ++ return NC_SORTS_AFTER; ++ ++ /* now look deeper */ ++ switch (n1->type) { ++ case GNUTLS_SAN_DNSNAME: ++ return compare_dns_names(&n1->name, &n2->name); ++ case GNUTLS_SAN_RFC822NAME: ++ return compare_emails(&n1->name, &n2->name); ++ case GNUTLS_SAN_IPADDRESS: ++ return compare_ip_ncs(&n1->name, &n2->name); ++ default: ++ /* unsupported types: stable lexicographic order */ ++ return compare_strings(n1->name.data, n1->name.size, ++ n2->name.data, n2->name.size); ++ } ++} ++ ++/* qsort-compatible wrapper */ ++static int compare_name_constraint_nodes_qsort(const void *a, const void *b) ++{ ++ const struct name_constraints_node_st *const *n1 = a; ++ const struct name_constraints_node_st *const *n2 = b; ++ enum name_constraint_relation rel; ++ ++ rel = compare_name_constraint_nodes(*n1, *n2); ++ switch (rel) { ++ case NC_SORTS_BEFORE: ++ case NC_INCLUDED_BY: ++ return -1; ++ case NC_SORTS_AFTER: ++ case NC_INCLUDES: ++ return 1; ++ case NC_EQUAL: ++ default: ++ return 0; ++ } ++} ++ + 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) { +@@ -419,13 +698,11 @@ static int name_constraints_node_list_in + found = t2; + break; + } + } + +- if (found != NULL && (t->type == GNUTLS_SAN_DNSNAME || +- t->type == GNUTLS_SAN_RFC822NAME || +- t->type == GNUTLS_SAN_IPADDRESS)) { ++ if (found != NULL && is_supported_type(t->type)) { + /* move node from PERMITTED to REMOVED */ + ret = name_constraints_node_list_add(&removed, t); + if (ret < 0) { + gnutls_assert(); + goto cleanup; +@@ -829,72 +1106,26 @@ gnutls_datum_t der; + cleanup: + _gnutls_free_datum(&der); + return ret; + } + +-static +-unsigned ends_with(const gnutls_datum_t * str, const gnutls_datum_t * suffix) +-{ +- unsigned char *tree; +- unsigned int treelen; +- +- if (suffix->size >= str->size) +- return 0; +- +- tree = suffix->data; +- treelen = suffix->size; +- if((treelen > 0) && (tree[0] == '.')) { +- tree++; +- treelen--; +- } +- +- if (memcmp(str->data + str->size - treelen, tree, treelen) == 0 && +- str->data[str->size - treelen -1] == '.') +- return 1; /* match */ +- +- return 0; +-} +- +-static +-unsigned email_ends_with(const gnutls_datum_t * str, const gnutls_datum_t * suffix) +-{ +- if (suffix->size >= str->size) +- return 0; +- +- if (suffix->size > 1 && suffix->data[0] == '.') { +- /* .domain.com */ +- if (memcmp(str->data + str->size - suffix->size, suffix->data, suffix->size) == 0) +- return 1; /* match */ +- } else { +- if (memcmp(str->data + str->size - suffix->size, suffix->data, suffix->size) == 0 && +- str->data[str->size - suffix->size -1] == '@') +- return 1; /* match */ +- } +- +- return 0; +-} +- + static unsigned dnsname_matches(const gnutls_datum_t *name, const gnutls_datum_t *suffix) + { + _gnutls_hard_log("matching %.*s with DNS constraint %.*s\n", name->size, name->data, + suffix->size, suffix->data); + +- if (suffix->size == name->size && memcmp(suffix->data, name->data, suffix->size) == 0) +- return 1; /* match */ +- +- return ends_with(name, suffix); ++ enum name_constraint_relation rel = compare_dns_names(name, suffix); ++ return rel == NC_EQUAL || rel == NC_INCLUDED_BY; + } + + static unsigned email_matches(const gnutls_datum_t *name, const gnutls_datum_t *suffix) + { + _gnutls_hard_log("matching %.*s with e-mail constraint %.*s\n", name->size, name->data, + suffix->size, suffix->data); + +- if (suffix->size == name->size && memcmp(suffix->data, name->data, suffix->size) == 0) +- return 1; /* match */ +- +- return email_ends_with(name, suffix); ++ enum name_constraint_relation rel = compare_emails(name, suffix); ++ return rel == NC_EQUAL || rel == NC_INCLUDED_BY; + } + + /*- + * name_constraints_intersect_nodes: + * @nc1: name constraints node 1 +@@ -914,46 +1145,62 @@ static int name_constraints_intersect_no + struct name_constraints_node_st **_intersection) + { + // presume empty intersection + struct name_constraints_node_st *intersection = NULL; + const struct name_constraints_node_st *to_copy = NULL; +- unsigned iplength = 0; +- unsigned byte; ++ enum name_constraint_relation rel; + + *_intersection = NULL; + + if (node1->type != node2->type) { + return GNUTLS_E_SUCCESS; + } + switch (node1->type) { + case GNUTLS_SAN_DNSNAME: +- if (!dnsname_matches(&node2->name, &node1->name)) ++ rel = compare_dns_names(&node1->name, &node2->name); ++ switch (rel) { ++ case NC_EQUAL: // equal means doesn't matter which one ++ case NC_INCLUDES: // node2 is more specific ++ to_copy = node2; ++ break; ++ case NC_INCLUDED_BY: // node1 is more specific ++ to_copy = node1; ++ break; ++ case NC_SORTS_BEFORE: // no intersection ++ case NC_SORTS_AFTER: // no intersection + return GNUTLS_E_SUCCESS; +- to_copy = node2; ++ } + break; + case GNUTLS_SAN_RFC822NAME: +- if (!email_matches(&node2->name, &node1->name)) ++ rel = compare_emails(&node1->name, &node2->name); ++ switch (rel) { ++ case NC_EQUAL: // equal means doesn't matter which one ++ case NC_INCLUDES: // node2 is more specific ++ to_copy = node2; ++ break; ++ case NC_INCLUDED_BY: // node1 is more specific ++ to_copy = node1; ++ break; ++ case NC_SORTS_BEFORE: // no intersection ++ case NC_SORTS_AFTER: // no intersection + return GNUTLS_E_SUCCESS; +- to_copy = node2; ++ } + break; + case GNUTLS_SAN_IPADDRESS: +- if (node1->name.size != node2->name.size) ++ rel = compare_ip_ncs(&node1->name, &node2->name); ++ switch (rel) { ++ case NC_EQUAL: // equal means doesn't matter which one ++ case NC_INCLUDES: // node2 is more specific ++ to_copy = node2; ++ break; ++ case NC_INCLUDED_BY: // node1 is more specific ++ to_copy = node1; ++ break; ++ case NC_SORTS_BEFORE: // no intersection ++ case NC_SORTS_AFTER: // no intersection + return GNUTLS_E_SUCCESS; +- iplength = node1->name.size / 2; +- for (byte = 0; byte < iplength; byte++) { +- 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 = node2; + break; + default: + // for other types, we don't know how to do the intersection, assume empty + return GNUTLS_E_SUCCESS; + } +@@ -966,24 +1213,10 @@ static int name_constraints_intersect_no + if (*_intersection == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + intersection = *_intersection; + + assert(intersection->name.data != NULL); +- +- if (intersection->type == GNUTLS_SAN_IPADDRESS) { +- // make sure both IP addresses are correctly masked +- _gnutls_mask_ip(intersection->name.data, +- intersection->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] |= +- node1->name.data[byte]; +- } +- } + } + + return GNUTLS_E_SUCCESS; + } + diff -Nru gnutls28-3.7.9/debian/patches/71_0006-x509-name_constraints-add-sorted_view-in-preparation.patch gnutls28-3.7.9/debian/patches/71_0006-x509-name_constraints-add-sorted_view-in-preparation.patch --- gnutls28-3.7.9/debian/patches/71_0006-x509-name_constraints-add-sorted_view-in-preparation.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0006-x509-name_constraints-add-sorted_view-in-preparation.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,160 @@ +From bc62fbb946085527b4b1c02f337dd10c68c54690 Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Wed, 4 Feb 2026 09:09:46 +0100 +Subject: [PATCH 6/9] x509/name_constraints: add sorted_view in preparation... + +... for actually using it later for performance gains. + +Signed-off-by: Alexander Sosedkin +--- + lib/x509/name_constraints.c | 62 ++++++++++++++++++++++++++++++------- + 1 file changed, 51 insertions(+), 11 deletions(-) + +diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c +index b5d732d0c5..41f30d13b9 100644 +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -50,14 +50,17 @@ struct name_constraints_node_st { + gnutls_datum_t name; + }; + + struct name_constraints_node_list_st { + struct name_constraints_node_st **data; + size_t size; + size_t capacity; ++ /* sorted-on-demand view, valid only when dirty == false */ ++ bool dirty; ++ struct name_constraints_node_st **sorted_view; + }; + + 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 */ + }; +@@ -338,14 +341,45 @@ static int compare_name_constraint_nodes_qsort(const void *a, const void *b) + return 1; + case NC_EQUAL: + default: + return 0; + } + } + ++/* Bring the sorted view up to date with the list data; clear the dirty flag. */ ++static int ensure_sorted(struct name_constraints_node_list_st *list) ++{ ++ struct name_constraints_node_st **new_data; ++ ++ if (!list->dirty) ++ return GNUTLS_E_SUCCESS; ++ if (!list->size) { ++ list->dirty = false; ++ return GNUTLS_E_SUCCESS; ++ } ++ ++ /* reallocate sorted view to match current size */ ++ new_data = ++ _gnutls_reallocarray(list->sorted_view, list->size, ++ sizeof(struct name_constraints_node_st *)); ++ if (!new_data) ++ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); ++ list->sorted_view = new_data; ++ ++ /* copy pointers and sort in-place */ ++ memcpy(list->sorted_view, list->data, ++ list->size * sizeof(struct name_constraints_node_st *)); ++ qsort(list->sorted_view, list->size, ++ sizeof(struct name_constraints_node_st *), ++ compare_name_constraint_nodes_qsort); ++ ++ list->dirty = false; ++ return GNUTLS_E_SUCCESS; ++} ++ + 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; +@@ -357,18 +391,31 @@ name_constraints_node_list_add(struct name_constraints_node_list_st *list, + 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->dirty = true; + list->data[list->size++] = node; + return 0; + } + ++static void ++name_constraints_node_list_clear(struct name_constraints_node_list_st *list) ++{ ++ gnutls_free(list->data); ++ gnutls_free(list->sorted_view); ++ list->data = NULL; ++ list->sorted_view = NULL; ++ list->capacity = 0; ++ list->size = 0; ++ list->dirty = false; ++} ++ + static int + name_constraints_node_add_new(gnutls_x509_name_constraints_t nc, + struct name_constraints_node_list_st *list, + unsigned type, const unsigned char *data, + unsigned int size) + { + struct name_constraints_node_st *node; +@@ -707,14 +754,15 @@ static int name_constraints_node_list_intersect( + goto cleanup; + } + /* remove node by swapping */ + if (i < permitted->size - 1) + permitted->data[i] = + permitted->data[permitted->size - 1]; + permitted->size--; ++ permitted->dirty = true; + continue; + } + i++; + } + + /* Phase 2 + * iterate through all combinations from PERMITTED2 and PERMITTED +@@ -904,25 +952,17 @@ cleanup: + + 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; ++ name_constraints_node_list_clear(&nc->nodes); ++ name_constraints_node_list_clear(&nc->permitted); ++ name_constraints_node_list_clear(&nc->excluded); + } + + /** + * gnutls_x509_name_constraints_deinit: + * @nc: The nameconstraints + * + * This function will deinitialize a name constraints type. +-- +2.51.0 + diff -Nru gnutls28-3.7.9/debian/patches/71_0007-x509-name_constraints-implement-name_constraints_nod.patch gnutls28-3.7.9/debian/patches/71_0007-x509-name_constraints-implement-name_constraints_nod.patch --- gnutls28-3.7.9/debian/patches/71_0007-x509-name_constraints-implement-name_constraints_nod.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0007-x509-name_constraints-implement-name_constraints_nod.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,183 @@ +From 80db5e90fa18d3e34bb91dd027bdf76d31e93dcd Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Wed, 4 Feb 2026 13:30:08 +0100 +Subject: [PATCH 7/9] x509/name_constraints: implement + name_constraints_node_list_union + +Signed-off-by: Alexander Sosedkin +--- + lib/x509/name_constraints.c | 98 ++++++++++++++++++++++++++++++++----- + 1 file changed, 86 insertions(+), 12 deletions(-) + +diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c +index 41f30d13b9..de20dd8ef4 100644 +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -37,14 +37,15 @@ + #include + + #include "ip.h" + #include "ip-in-cidr.h" + #include "intprops.h" + #include "minmax.h" + ++#include + #include + + #define MAX_NC_CHECKS (1 << 20) + + struct name_constraints_node_st { + unsigned type; + gnutls_datum_t name; +@@ -866,30 +867,103 @@ static int name_constraints_node_list_intersect( + ret = GNUTLS_E_SUCCESS; + + cleanup: + gnutls_free(removed.data); + 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); + } + + /** + * gnutls_x509_crt_get_name_constraints: + * @crt: should contain a #gnutls_x509_crt_t type + * @nc: The nameconstraints intermediate type + * @flags: zero or %GNUTLS_EXT_FLAG_APPEND +@@ -1022,15 +1096,15 @@ static int name_constraints_add(gnutls_x509_name_constraints_t nc, + + /*- + * _gnutls_x509_name_constraints_merge: + * @nc: The nameconstraints + * @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. + * + * The merged constraints will be placed in @nc. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value. + * +@@ -1044,16 +1118,16 @@ int _gnutls_x509_name_constraints_merge(gnutls_x509_name_constraints_t nc, + ret = name_constraints_node_list_intersect( + nc, &nc->permitted, &nc2->permitted, &nc->excluded); + if (ret < 0) { + gnutls_assert(); + 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; + } + + return 0; + } +-- +2.51.0 + diff -Nru gnutls28-3.7.9/debian/patches/71_0008-x509-name_constraints-make-types_with_empty_intersec.patch gnutls28-3.7.9/debian/patches/71_0008-x509-name_constraints-make-types_with_empty_intersec.patch --- gnutls28-3.7.9/debian/patches/71_0008-x509-name_constraints-make-types_with_empty_intersec.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0008-x509-name_constraints-make-types_with_empty_intersec.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,128 @@ +From d0ac999620c8c0aeb6939e1e92d884ca8e40b759 Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Wed, 4 Feb 2026 18:31:37 +0100 +Subject: [PATCH 8/9] x509/name_constraints: make types_with_empty_intersection + a bitmask + +Signed-off-by: Alexander Sosedkin +--- + lib/x509/name_constraints.c | 39 +++++++++++++++++++++++++++---------- + 1 file changed, 29 insertions(+), 10 deletions(-) + +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -273,10 +273,11 @@ static enum name_constraint_relation com + return NC_EQUAL; + } + + static inline bool is_supported_type(unsigned type) + { ++ /* all of these should be under GNUTLS_SAN_MAX (intersect bitmasks) */ + return type == GNUTLS_SAN_DNSNAME || type == GNUTLS_SAN_RFC822NAME || + type == GNUTLS_SAN_IPADDRESS; + } + + /* Universal comparison for name constraint nodes. +@@ -681,10 +682,24 @@ name_constraints_node_new(gnutls_x509_na + } + + return tmp; + } + ++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); ++ ++#define type_bitmask_t uint8_t /* increase if GNUTLS_SAN_MAX grows */ ++#define type_bitmask_set(mask, t) ((mask) |= (1u << (t))) ++#define type_bitmask_clr(mask, t) ((mask) &= ~(1u << (t))) ++#define type_bitmask_in(mask, t) ((mask) & (1u << (t))) ++/* C99-compatible compile-time assertions; gnutls_int.h undefines verify */ ++typedef char assert_san_max[(GNUTLS_SAN_MAX < 8) ? 1 : -1]; ++typedef char assert_dnsname[(GNUTLS_SAN_DNSNAME <= GNUTLS_SAN_MAX) ? 1 : -1]; ++typedef char assert_rfc822[(GNUTLS_SAN_RFC822NAME <= GNUTLS_SAN_MAX) ? 1 : -1]; ++typedef char assert_ipaddr[(GNUTLS_SAN_IPADDRESS <= GNUTLS_SAN_MAX) ? 1 : -1]; + + /*- + * @brief name_constraints_node_list_intersect: + * @nc: %gnutls_x509_name_constraints_t + * @permitted: first name constraints list (permitted) +@@ -709,16 +724,13 @@ static int name_constraints_node_list_in + struct name_constraints_node_list_st removed = { .data = NULL, + .size = 0, + .capacity = 0 }; + static const unsigned char universal_ip[32] = { 0 }; + +- /* temporary array to see, if we need to add universal excluded constraints +- * (see phase 3 for details) +- * indexed directly by (gnutls_x509_subject_alt_name_t enum - 1) */ +- unsigned char types_with_empty_intersection[GNUTLS_SAN_MAX]; +- memset(types_with_empty_intersection, 0, +- sizeof(types_with_empty_intersection)); ++ /* bitmask to see if we need to add universal excluded constraints ++ * (see phase 3 for details) */ ++ type_bitmask_t types_with_empty_intersection = 0; + + if (permitted->size == 0 || permitted2->size == 0) + return 0; + + /* Phase 1 +@@ -740,11 +752,12 @@ static int name_constraints_node_list_in + 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; ++ type_bitmask_set(types_with_empty_intersection, ++ t->type); + found = t2; + break; + } + } + +@@ -794,12 +807,12 @@ static int name_constraints_node_list_in + gnutls_free(tmp); + return gnutls_assert_val( + GNUTLS_E_INTERNAL_ERROR); + } + // we will not add universal excluded constraint for this type +- types_with_empty_intersection[tmp->type - 1] = +- 0; ++ type_bitmask_clr(types_with_empty_intersection, ++ tmp->type); + // add intersection node to PERMITTED + ret = name_constraints_node_list_add(permitted, + tmp); + if (ret < 0) { + gnutls_assert(); +@@ -823,11 +836,11 @@ static int name_constraints_node_list_in + * 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 + * excluded constraint with universal wildcard + * (since the intersection of permitted is now empty). */ + for (type = 1; type <= GNUTLS_SAN_MAX; type++) { +- if (types_with_empty_intersection[type - 1] == 0) ++ if (!type_bitmask_in(types_with_empty_intersection, type)) + continue; + _gnutls_hard_log( + "Adding universal excluded name constraint for type %d.\n", + type); + switch (type) { +@@ -867,10 +880,15 @@ static int name_constraints_node_list_in + cleanup: + gnutls_free(removed.data); + return ret; + } + ++#undef type_bitmask_t ++#undef type_bitmask_set ++#undef type_bitmask_clr ++#undef type_bitmask_in ++ + 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) + { diff -Nru gnutls28-3.7.9/debian/patches/71_0009-x509-name_constraints-name_constraints_node_list_int.patch gnutls28-3.7.9/debian/patches/71_0009-x509-name_constraints-name_constraints_node_list_int.patch --- gnutls28-3.7.9/debian/patches/71_0009-x509-name_constraints-name_constraints_node_list_int.patch 1970-01-01 00:00:00.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/71_0009-x509-name_constraints-name_constraints_node_list_int.patch 2026-02-14 13:56:45.000000000 +0000 @@ -0,0 +1,428 @@ +From d6054f0016db05fb5c82177ddbd0a4e8331059a1 Mon Sep 17 00:00:00 2001 +From: Alexander Sosedkin +Date: Wed, 4 Feb 2026 20:03:49 +0100 +Subject: [PATCH 9/9] x509/name_constraints: + name_constraints_node_list_intersect over sorted + +Fixes: #1773 +Fixes: GNUTLS-SA-2026-02-09-2 +Fixes: CVE-2025-14831 + +Signed-off-by: Alexander Sosedkin +--- + NEWS | 7 + + lib/x509/name_constraints.c | 350 ++++++++++++++---------------------- + 2 files changed, 142 insertions(+), 215 deletions(-) + +--- a/lib/x509/name_constraints.c ++++ b/lib/x509/name_constraints.c +@@ -444,17 +444,10 @@ name_constraints_node_add_copy(gnutls_x5 + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + return name_constraints_node_add_new(nc, dest, src->type, + src->name.data, src->name.size); + } + +-// for documentation see the implementation +-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); +- + /*- + * _gnutls_x509_name_constraints_is_empty: + * @nc: name constraints structure + * @type: type (gnutls_x509_subject_alt_name_t or 0) + * +@@ -714,133 +707,146 @@ typedef char assert_ipaddr[(GNUTLS_SAN_I + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value. + -*/ + 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 *permitted2, + struct name_constraints_node_list_st *excluded) + { +- struct name_constraints_node_st *tmp; +- int ret, type, used; +- struct name_constraints_node_list_st removed = { .data = NULL, +- .size = 0, +- .capacity = 0 }; ++ struct name_constraints_node_st *nc1, *nc2; ++ struct name_constraints_node_list_st result = { 0 }; ++ struct name_constraints_node_list_st unsupp2 = { 0 }; ++ enum name_constraint_relation rel; ++ unsigned type; ++ int ret = GNUTLS_E_SUCCESS; ++ size_t i, j, p1_unsupp = 0, p2_unsupp = 0; ++ type_bitmask_t universal_exclude_needed = 0; ++ type_bitmask_t types_in_p1 = 0, types_in_p2 = 0; + static const unsigned char universal_ip[32] = { 0 }; + +- /* bitmask to see if we need to add universal excluded constraints +- * (see phase 3 for details) */ +- type_bitmask_t types_with_empty_intersection = 0; +- + if (permitted->size == 0 || permitted2->size == 0) +- return 0; ++ return GNUTLS_E_SUCCESS; + +- /* Phase 1 +- * 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) { +- 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 +- type_bitmask_set(types_with_empty_intersection, +- t->type); +- found = t2; +- break; +- } ++ /* make sorted views of the arrays */ ++ ret = ensure_sorted(permitted); ++ if (ret < 0) { ++ gnutls_assert(); ++ goto cleanup; ++ } ++ ret = ensure_sorted(permitted2); ++ if (ret < 0) { ++ gnutls_assert(); ++ goto cleanup; ++ } ++ /* deal with the leading unsupported types first: count, then union */ ++ while (p1_unsupp < permitted->size && ++ !is_supported_type(permitted->sorted_view[p1_unsupp]->type)) ++ p1_unsupp++; ++ while (p2_unsupp < permitted2->size && ++ !is_supported_type(permitted2->sorted_view[p2_unsupp]->type)) ++ p2_unsupp++; ++ if (p1_unsupp) { /* copy p1 unsupported type pointers into result */ ++ result.data = gnutls_calloc( ++ p1_unsupp, sizeof(struct name_constraints_node_st *)); ++ if (!result.data) { ++ ret = GNUTLS_E_MEMORY_ERROR; ++ gnutls_assert(); ++ goto cleanup; + } ++ memcpy(result.data, permitted->sorted_view, ++ p1_unsupp * sizeof(struct name_constraints_node_st *)); ++ result.size = result.capacity = p1_unsupp; ++ result.dirty = true; ++ } ++ if (p2_unsupp) { /* union will make deep copies from p2 */ ++ unsupp2.data = permitted2->sorted_view; /* so, just alias */ ++ unsupp2.size = unsupp2.capacity = p2_unsupp; ++ unsupp2.dirty = false; /* we know it's sorted */ ++ unsupp2.sorted_view = permitted2->sorted_view; ++ ret = name_constraints_node_list_union(nc, &result, &unsupp2); ++ if (ret < 0) { ++ gnutls_assert(); ++ goto cleanup; ++ } ++ } + +- if (found != NULL && is_supported_type(t->type)) { +- /* 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--; +- permitted->dirty = true; +- continue; ++ /* with that out of the way, pre-compute the supported types we have */ ++ for (i = p1_unsupp; i < permitted->size; i++) { ++ type = permitted->sorted_view[i]->type; ++ if (type < 1 || type > GNUTLS_SAN_MAX) { ++ ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); ++ goto cleanup; + } +- i++; ++ type_bitmask_set(types_in_p1, type); + } ++ for (j = p2_unsupp; j < permitted2->size; j++) { ++ type = permitted2->sorted_view[j]->type; ++ if (type < 1 || type > GNUTLS_SAN_MAX) { ++ ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); ++ goto cleanup; ++ } ++ type_bitmask_set(types_in_p2, type); ++ } ++ /* universal excludes might be needed for types intersecting to empty */ ++ universal_exclude_needed = types_in_p1 & types_in_p2; + +- /* Phase 2 +- * iterate through all combinations from PERMITTED2 and PERMITTED +- * and create intersections of nodes with same type */ +- 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; +- 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(nc, t, t2, &tmp); +- if (ret < 0) { +- gnutls_assert(); +- goto cleanup; +- } +- used = 1; +- // if intersection is not empty +- if (tmp != +- NULL) { // intersection for this type is not empty +- // check bounds +- if (tmp->type > GNUTLS_SAN_MAX || +- tmp->type == 0) { +- gnutls_free(tmp); +- return gnutls_assert_val( +- GNUTLS_E_INTERNAL_ERROR); +- } +- // we will not add universal excluded constraint for this type +- type_bitmask_clr(types_with_empty_intersection, +- tmp->type); +- // add intersection node to PERMITTED +- ret = name_constraints_node_list_add(permitted, +- tmp); +- if (ret < 0) { +- gnutls_assert(); +- goto cleanup; +- } +- } ++ /* go through supported type NCs and intersect in a single pass */ ++ i = p1_unsupp; ++ j = p2_unsupp; ++ while (i < permitted->size || j < permitted2->size) { ++ nc1 = (i < permitted->size) ? permitted->sorted_view[i] : NULL; ++ nc2 = (j < permitted2->size) ? permitted2->sorted_view[j] : ++ NULL; ++ rel = compare_name_constraint_nodes(nc1, nc2); ++ ++ switch (rel) { ++ case NC_SORTS_BEFORE: ++ assert(nc1 != NULL); /* comparator-guaranteed */ ++ /* if nothing to intersect with, shallow-copy nc1 */ ++ if (!type_bitmask_in(types_in_p2, nc1->type)) ++ ret = name_constraints_node_list_add(&result, ++ nc1); ++ i++; /* otherwise skip nc1 */ ++ break; ++ case NC_SORTS_AFTER: ++ assert(nc2 != NULL); /* comparator-guaranteed */ ++ /* if nothing to intersect with, deep-copy nc2 */ ++ if (!type_bitmask_in(types_in_p1, nc2->type)) ++ ret = name_constraints_node_add_copy( ++ nc, &result, nc2); ++ j++; /* otherwise skip nc2 */ ++ break; ++ case NC_INCLUDED_BY: /* add nc1, shallow-copy */ ++ assert(nc1 != NULL && nc2 != NULL); /* comparator */ ++ type_bitmask_clr(universal_exclude_needed, nc1->type); ++ ret = name_constraints_node_list_add(&result, nc1); ++ i++; ++ break; ++ case NC_INCLUDES: /* pick nc2, deep-copy */ ++ assert(nc1 != NULL && nc2 != NULL); /* comparator */ ++ type_bitmask_clr(universal_exclude_needed, nc2->type); ++ ret = name_constraints_node_add_copy(nc, &result, nc2); ++ j++; ++ break; ++ case NC_EQUAL: /* pick whichever: nc1, shallow-copy */ ++ assert(nc1 != NULL && nc2 != NULL); /* loop condition */ ++ type_bitmask_clr(universal_exclude_needed, nc1->type); ++ ret = name_constraints_node_list_add(&result, nc1); ++ i++; ++ j++; ++ break; + } +- // 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) { +- ret = name_constraints_node_add_copy(nc, permitted, t2); +- if (ret < 0) { +- gnutls_assert(); +- goto cleanup; +- } ++ if (ret < 0) { ++ gnutls_assert(); ++ goto cleanup; + } + } + +- /* 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 +- * excluded constraint with universal wildcard +- * (since the intersection of permitted is now empty). */ ++ /* finishing touch: add universal excluded constraints for types where ++ * both lists had constraints, but all intersections ended up empty */ + for (type = 1; type <= GNUTLS_SAN_MAX; type++) { +- if (!type_bitmask_in(types_with_empty_intersection, type)) ++ if (!type_bitmask_in(universal_exclude_needed, type)) + continue; + _gnutls_hard_log( + "Adding universal excluded name constraint for type %d.\n", + type); + switch (type) { +@@ -869,18 +875,28 @@ static int name_constraints_node_list_in + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + break; +- default: // do nothing, at least one node was already moved in phase 1 +- break; ++ default: /* unsupported type; should be unreacheable */ ++ ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); ++ goto cleanup; + } + } +- ret = GNUTLS_E_SUCCESS; + ++ gnutls_free(permitted->data); ++ gnutls_free(permitted->sorted_view); ++ permitted->data = result.data; ++ permitted->sorted_view = NULL; ++ permitted->size = result.size; ++ permitted->capacity = result.capacity; ++ permitted->dirty = true; ++ ++ result.data = NULL; ++ ret = GNUTLS_E_SUCCESS; + cleanup: +- gnutls_free(removed.data); ++ name_constraints_node_list_clear(&result); + return ret; + } + + #undef type_bitmask_t + #undef type_bitmask_set +@@ -1256,104 +1272,10 @@ static unsigned email_matches(const gnut + + enum name_constraint_relation rel = compare_emails(name, suffix); + return rel == NC_EQUAL || rel == NC_INCLUDED_BY; + } + +-/*- +- * name_constraints_intersect_nodes: +- * @nc1: name constraints node 1 +- * @nc2: name constraints node 2 +- * @_intersection: newly allocated node with intersected constraints, +- * NULL if the intersection is empty +- * +- * Inspect 2 name constraints nodes (of possibly different types) and allocate +- * a new node with intersection of given constraints. +- * +- * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a negative error value. +- -*/ +-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 +- struct name_constraints_node_st *intersection = NULL; +- const struct name_constraints_node_st *to_copy = NULL; +- enum name_constraint_relation rel; +- +- *_intersection = NULL; +- +- if (node1->type != node2->type) { +- return GNUTLS_E_SUCCESS; +- } +- switch (node1->type) { +- case GNUTLS_SAN_DNSNAME: +- rel = compare_dns_names(&node1->name, &node2->name); +- switch (rel) { +- case NC_EQUAL: // equal means doesn't matter which one +- case NC_INCLUDES: // node2 is more specific +- to_copy = node2; +- break; +- case NC_INCLUDED_BY: // node1 is more specific +- to_copy = node1; +- break; +- case NC_SORTS_BEFORE: // no intersection +- case NC_SORTS_AFTER: // no intersection +- return GNUTLS_E_SUCCESS; +- } +- break; +- case GNUTLS_SAN_RFC822NAME: +- rel = compare_emails(&node1->name, &node2->name); +- switch (rel) { +- case NC_EQUAL: // equal means doesn't matter which one +- case NC_INCLUDES: // node2 is more specific +- to_copy = node2; +- break; +- case NC_INCLUDED_BY: // node1 is more specific +- to_copy = node1; +- break; +- case NC_SORTS_BEFORE: // no intersection +- case NC_SORTS_AFTER: // no intersection +- return GNUTLS_E_SUCCESS; +- } +- break; +- case GNUTLS_SAN_IPADDRESS: +- rel = compare_ip_ncs(&node1->name, &node2->name); +- switch (rel) { +- case NC_EQUAL: // equal means doesn't matter which one +- case NC_INCLUDES: // node2 is more specific +- to_copy = node2; +- break; +- case NC_INCLUDED_BY: // node1 is more specific +- to_copy = node1; +- break; +- case NC_SORTS_BEFORE: // no intersection +- case NC_SORTS_AFTER: // no intersection +- return GNUTLS_E_SUCCESS; +- } +- break; +- default: +- // for other types, we don't know how to do the intersection, assume empty +- return GNUTLS_E_SUCCESS; +- } +- +- // copy existing node if applicable +- if (to_copy != NULL) { +- *_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; +- +- assert(intersection->name.data != NULL); +- } +- +- return GNUTLS_E_SUCCESS; +-} +- + /* + * Returns: true if the certification is acceptable, and false otherwise. + */ + static + unsigned check_unsupported_constraint(gnutls_x509_name_constraints_t nc, diff -Nru gnutls28-3.7.9/debian/patches/series gnutls28-3.7.9/debian/patches/series --- gnutls28-3.7.9/debian/patches/series 2025-07-12 11:38:17.000000000 +0000 +++ gnutls28-3.7.9/debian/patches/series 2026-02-14 13:08:08.000000000 +0000 @@ -22,3 +22,14 @@ 69_0004-x509-avoid-double-free-when-exporting-othernames-in-.patch 69_0005-certtool-avoid-1-byte-write-buffer-overrun-when-pars.patch 69_0006-handshake-clear-HSK_PSK_SELECTED-is-when-resetting-b.patch +70_0001-pkcs11-try-to-initialize-modules-in-thread-safe-mode.patch +70_0002-pkcs11-avoid-stack-overwrite-when-initializing-a-tok.patch +71_0001-x509-name_constraints-use-actual-zeroes-in-universal.patch +71_0002-tests-name-constraints-ip-stop-swallowing-errors.patch +71_0003-x509-name_constraints-reject-some-malformed-domain-n.patch +71_0004-x509-name_constraints-name_constraints_node_add_-new.patch +71_0005-x509-name_constraints-introduce-a-rich-comparator.patch +71_0006-x509-name_constraints-add-sorted_view-in-preparation.patch +71_0007-x509-name_constraints-implement-name_constraints_nod.patch +71_0008-x509-name_constraints-make-types_with_empty_intersec.patch +71_0009-x509-name_constraints-name_constraints_node_list_int.patch