Compare commits

...

No commits in common. "c8" and "c8-beta" have entirely different histories.
c8 ... c8-beta

27 changed files with 2 additions and 11853 deletions

View File

@ -1,226 +0,0 @@
From 35dbb0e4ebcc07acecfd060ffc6ca076cf397920 Mon Sep 17 00:00:00 2001
From: Joshua Rogers <joshua@joshua.hu>
Date: Wed, 18 Mar 2026 17:08:03 +0100
Subject: [PATCH 1/3] handshake-checks: fix username comparison during
rehandshake
This is definitely a security issue
subverting the GNUTLS_ALLOW_ID_CHANGE protection,
but its real-life exploitability is under question.
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1808
Signed-off-by: Joshua Rogers <joshua@joshua.hu>
---
lib/handshake-checks.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/handshake-checks.c b/lib/handshake-checks.c
index b07b9680c..e02210531 100644
--- a/lib/handshake-checks.c
+++ b/lib/handshake-checks.c
@@ -75,11 +75,16 @@ int _gnutls_check_id_for_change(gnutls_session_t session)
if (username == NULL)
return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
- if (session->internals.saved_username_size != -1) {
- if (session->internals.saved_username_size == username_length &&
- strncmp(session->internals.saved_username, username, username_length) != 0) {
- _gnutls_debug_log("Session's PSK username changed during rehandshake; aborting!\n");
- return gnutls_assert_val(GNUTLS_E_SESSION_USER_ID_CHANGED);
+ if (session->internals.saved_username &&
+ session->internals.saved_username_size != -1) {
+ if (session->internals.saved_username_size !=
+ username_length ||
+ memcmp(session->internals.saved_username, username,
+ username_length)) {
+ _gnutls_debug_log(
+ "Session's PSK username changed during rehandshake; aborting!\n");
+ return gnutls_assert_val(
+ GNUTLS_E_SESSION_USER_ID_CHANGED);
}
} else {
memcpy(session->internals.saved_username, username, username_length);
--
2.53.0
From 7153b4668234f2687cab354c418b40624a650567 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 18 Mar 2026 16:08:51 +0100
Subject: [PATCH 2/3] tests/rehandshake-switch-psk-id: refactor a bit
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/rehandshake-switch-psk-id.c | 50 ++++++++++++++++++-------------
1 file changed, 29 insertions(+), 21 deletions(-)
diff --git a/tests/rehandshake-switch-psk-id.c b/tests/rehandshake-switch-psk-id.c
index c8beec13f..62593060a 100644
--- a/tests/rehandshake-switch-psk-id.c
+++ b/tests/rehandshake-switch-psk-id.c
@@ -24,10 +24,10 @@
#include <config.h>
#endif
+#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <errno.h>
#include <gnutls/gnutls.h>
#include "utils.h"
#include "eagain-common.h"
@@ -35,6 +35,8 @@
/* This test checks whether the server switching certificates is detected
* by the client */
+#define SIZEOF(array) (sizeof(array) / sizeof(array[0]))
+
const char *side;
static void tls_log_func(int level, const char *str)
@@ -42,11 +44,8 @@ static void tls_log_func(int level, const char *str)
fprintf(stderr, "%s|<%d>| %s", side, level, str);
}
-#include "cert-common.h"
-
-static int
-pskfunc(gnutls_session_t session, const char *username,
- gnutls_datum_t * key)
+static int pskfunc(gnutls_session_t session, const char *username,
+ gnutls_datum_t *key)
{
if (debug)
printf("psk: username %s\n", username);
@@ -76,6 +75,9 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx, unsigned allow_chang
const gnutls_datum_t key = { (void *) "DEADBEEF", 8 };
int cret = GNUTLS_E_AGAIN;
+ success("testing: prio=%s kx=%s allow_change=%d\n", prio,
+ gnutls_kx_get_name(kx), allow_change);
+
/* General init. */
gnutls_global_set_log_function(tls_log_func);
if (debug)
@@ -170,22 +172,28 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx, unsigned allow_chang
void doit(void)
{
+ const char *prio_list[] = {
+ "NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK",
+ "NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK",
+ "NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK",
+ };
+ const gnutls_kx_algorithm_t kx_list[] = {
+ GNUTLS_KX_PSK,
+ GNUTLS_KX_DHE_PSK,
+ GNUTLS_KX_ECDHE_PSK,
+ };
+ assert(SIZEOF(prio_list) == SIZEOF(kx_list));
+
global_init();
- /* Allow change of ID */
- try("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", GNUTLS_KX_PSK, 0);
- reset_buffers();
- try("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", GNUTLS_KX_DHE_PSK, 0);
- reset_buffers();
- try("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", GNUTLS_KX_ECDHE_PSK, 0);
- reset_buffers();
-
- /* Prohibit (default) change of ID */
- try("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", GNUTLS_KX_PSK, 1);
- reset_buffers();
- try("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", GNUTLS_KX_DHE_PSK, 1);
- reset_buffers();
- try("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", GNUTLS_KX_ECDHE_PSK, 1);
- reset_buffers();
+ /* loop over allowed (0) and disallowed (1) ID change */
+ for (unsigned allow = 0; allow <= 1; allow++) {
+ /* loop over priority strings/key exchange algorithms */
+ for (unsigned i = 0; i < SIZEOF(prio_list); i++) {
+ try(prio_list[i], kx_list[i], allow);
+ reset_buffers();
+ }
+ }
+
gnutls_global_deinit();
}
--
2.53.0
From cb3b9c873555d3e458205cb554153553c7baaaae Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 18 Mar 2026 16:47:43 +0100
Subject: [PATCH 3/3] tests/rehandshake-switch-psk-id: test usernames of varied
length
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/rehandshake-switch-psk-id.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/tests/rehandshake-switch-psk-id.c b/tests/rehandshake-switch-psk-id.c
index 62593060a..5e06abe05 100644
--- a/tests/rehandshake-switch-psk-id.c
+++ b/tests/rehandshake-switch-psk-id.c
@@ -58,7 +58,8 @@ static int pskfunc(gnutls_session_t session, const char *username,
return 0;
}
-static void try(const char *prio, gnutls_kx_algorithm_t kx, unsigned allow_change)
+static void try(const char *prio, gnutls_kx_algorithm_t kx,
+ unsigned allow_change, const char *username)
{
int ret;
/* Server stuff. */
@@ -75,8 +76,8 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx, unsigned allow_chang
const gnutls_datum_t key = { (void *) "DEADBEEF", 8 };
int cret = GNUTLS_E_AGAIN;
- success("testing: prio=%s kx=%s allow_change=%d\n", prio,
- gnutls_kx_get_name(kx), allow_change);
+ success("testing: prio=%s kx=%s allow_change=%d username=%s\n", prio,
+ gnutls_kx_get_name(kx), allow_change, username);
/* General init. */
gnutls_global_set_log_function(tls_log_func);
@@ -120,7 +121,7 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx, unsigned allow_chang
if (ret < 0)
exit(1);
- gnutls_psk_set_client_credentials(clientpskcred2, "test2", &key,
+ gnutls_psk_set_client_credentials(clientpskcred2, username, &key,
GNUTLS_PSK_KEY_HEX);
ret = gnutls_init(&client, GNUTLS_CLIENT);
@@ -184,14 +185,21 @@ void doit(void)
};
assert(SIZEOF(prio_list) == SIZEOF(kx_list));
+ /* same length, different length */
+ const char *usernames[] = { "test2", "test3-but-longer" };
+
global_init();
/* loop over allowed (0) and disallowed (1) ID change */
for (unsigned allow = 0; allow <= 1; allow++) {
/* loop over priority strings/key exchange algorithms */
for (unsigned i = 0; i < SIZEOF(prio_list); i++) {
- try(prio_list[i], kx_list[i], allow);
- reset_buffers();
+ /* loop over usernames to rehandshake to */
+ for (unsigned j = 0; j < SIZEOF(usernames); j++) {
+ try(prio_list[i], kx_list[i], allow,
+ usernames[j]);
+ reset_buffers();
+ }
}
}
--
2.53.0

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
From 4e8d3ba9160dfd3155c2fab12e9d5ab973013c2d Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 7 Apr 2026 10:16:03 +0200
Subject: [PATCH] session_pack: validate session_id_size on unpacking
A check for session_id_size not exceeding GNUTLS_MAX_SESSION_ID_SIZE
on loading persisted TLS session data was overlooked,
leading to a heap overflow
were the data corrupted in a malicious manner.
Reported-by: Haruto Kimura (Stella)
Fixes: #1817
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/session_pack.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/lib/session_pack.c b/lib/session_pack.c
index a6d11c4cf..823824e4c 100644
--- a/lib/session_pack.c
+++ b/lib/session_pack.c
@@ -1006,6 +1006,10 @@ unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps)
&session->internals.resumed_security_parameters.
session_id_size, 1);
+ if (session->internals.resumed_security_parameters.session_id_size >
+ GNUTLS_MAX_SESSION_ID_SIZE)
+ return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+
BUFFER_POP(ps,
session->internals.resumed_security_parameters.
session_id,
--
2.53.0

View File

@ -1,116 +0,0 @@
From 09f3ab24fba0d2558db29c0e47b011f9b9b56c09 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 7 Apr 2026 20:23:29 +0200
Subject: [PATCH 1/2] lib/x509/privkey_openssl: mind header size more carefully
When parsing private keys in OpenSSL PEM format, GnuTLS did not perform
sufficient bounds checking for the length of the PEM header being parsed.
For specially crafted inputs, this could lead to heap overreads.
There was no confidentiality risk and
the crash potential was limited to instrumented builds in practice.
This change instates the overlooked bounds checking.
Reported-by: Kamil Frankowicz <kamil.frankowicz@cert.pl>
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Related: #1818
Fixes: #1854
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/privkey_openssl.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lib/x509/privkey_openssl.c b/lib/x509/privkey_openssl.c
index 9fc70e032..b81671e7f 100644
--- a/lib/x509/privkey_openssl.c
+++ b/lib/x509/privkey_openssl.c
@@ -174,7 +174,8 @@ gnutls_x509_privkey_import_openssl(gnutls_x509_privkey_t key,
for (i = 0; i < sizeof(pem_ciphers) / sizeof(pem_ciphers[0]); i++) {
l = strlen(pem_ciphers[i].name);
- if (!strncmp(pem_header, pem_ciphers[i].name, l) &&
+ if (pem_header_size > l &&
+ !strncmp(pem_header, pem_ciphers[i].name, l) &&
pem_header[l] == ',') {
pem_header += l + 1;
cipher = pem_ciphers[i].cipher;
@@ -225,6 +226,8 @@ gnutls_x509_privkey_import_openssl(gnutls_x509_privkey_t key,
while (*pem_header == '\n' || *pem_header == '\r')
pem_header++;
+ pem_header_size =
+ data->size - (ptrdiff_t)(pem_header - pem_header_start);
ret =
_gnutls_base64_decode((const void *) pem_header,
pem_header_size, &b64_data);
--
2.53.0
From 3023e4d155affad82a42e1f076b73c5bf17c5931 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 7 Apr 2026 20:31:49 +0200
Subject: [PATCH 2/2] tests/key-openssl: add a test for #1818.4 OpenSSL PEM
parsing
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/key-openssl.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/tests/key-openssl.c b/tests/key-openssl.c
index d2c8a724b..0e1e74804 100644
--- a/tests/key-openssl.c
+++ b/tests/key-openssl.c
@@ -95,6 +95,21 @@ const char key2[] =
"F3bDyqlxSOm7uxF/K3YzI44v8/D8GGnLBTpN+ANBdiY=\n"
"-----END RSA PRIVATE KEY-----\n";
+const char key_newlines_head[] = /* key2... */
+ "-----BEGIN RSA PRIVATE KEY-----\n"
+ "Proc-Type: 4,ENCRYPTED\n"
+ "DEK-Info: AES-128-CBC,2A57FF97B701B3F760145D7446929481\n";
+/* ... with many newlines inserted in here */
+const char key_newlines_tail[] =
+ "mGAPhSw48wZBnkHOhfMDg8yL2IBgMuTmeKE4xoHi7T6isHBNfkqMd0iJ+DJP/OKb\n"
+ "t+7lkKjj/xQ7w/bOBvBxlfRe4MW6+ejCdAFD9XSolW6WN6CEJPMI4UtmOK5inqcC\n"
+ "8l2l54f/VGrVN9uavU3KlXCjrd3Jp9B0Mu4Zh/UU4+EWs9rJAZfLIn+vHZ3OHetx\n"
+ "g74LdV7nC7lt/fjxc1caNIfgHs40dUt9FVrnJvAtkcNMtcjX/D+L8ZrLgQzIWFcs\n"
+ "WAbUZj7Me22mCli3RPET7Je37K59IzfWgbWFCGaNu3X02g5xtCfdcn/Uqy9eofH0\n"
+ "YjKRhpgXPeGJCkoRqDeUHQNPpVP5HrzDZMVK3E4DC03C8qvgsYvuwYt3KkbG2fuA\n"
+ "F3bDyqlxSOm7uxF/K3YzI44v8/D8GGnLBTpN+ANBdiY=\n";
+/* "-----END RSA PRIVATE KEY-----\n"; intentionally omitted */
+
void doit(void)
{
gnutls_x509_privkey_t pkey;
@@ -167,5 +182,27 @@ void doit(void)
}
gnutls_x509_privkey_deinit(pkey);
+ /* import_openssl with a key having too many newlines in the header,
+ * (fails with #1818.4 unfixed under ASAN) */
+ ret = gnutls_x509_privkey_init(&pkey);
+ if (ret < 0)
+ fail("gnutls_x509_privkey_init: %d\n", ret);
+
+ //gnutls_x509_privkey_set_pin_function(pkey, good_pwd_cb, NULL);
+ key.size = 1024 * 1024 * 1024;
+ key.data = gnutls_malloc(key.size);
+ memset(key.data, '\n', key.size);
+ memcpy(key.data, key_newlines_head, sizeof(key_newlines_head) - 1);
+ memcpy(key.data + key.size - sizeof(key_newlines_tail),
+ key_newlines_tail, sizeof(key_newlines_tail));
+
+ ret = gnutls_x509_privkey_import_openssl(pkey, &key, "a123456");
+ if (ret != GNUTLS_E_BASE64_DECODING_ERROR) {
+ fail("gnutls_x509_import_openssl (full of newlines): %s\n",
+ gnutls_strerror(ret));
+ }
+ gnutls_free(key.data);
+ gnutls_x509_privkey_deinit(pkey);
+
gnutls_global_deinit();
}
--
2.53.0

View File

@ -1,41 +0,0 @@
From f66b9933252ef90f9765124361b47951a34d456d Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 7 Apr 2026 20:18:40 +0200
Subject: [PATCH] nettle/pk: check RSA key coprimality in verify_params
Previously, gnutls_privkey_verify_params has overlooked
the scenario of p and q not being co-prime,
and proceeded with undefined behaviour that was extremely likely
to error out in practice anyway.
Now it returns GNUTLS_E_PK_INVALID_PRIVKEY in this case.
Reported-by: Kamil Frankowicz <kamil.frankowicz@cert.pl>
Related: #1818
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/nettle/pk.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/lib/nettle/pk.c b/lib/nettle/pk.c
index b19fe3804..9d1c97b86 100644
--- a/lib/nettle/pk.c
+++ b/lib/nettle/pk.c
@@ -2911,9 +2911,12 @@ wrap_nettle_pk_verify_priv_params(gnutls_pk_algorithm_t algo,
goto rsa_cleanup;
}
- mpz_invert(TOMPZ(t1),
- TOMPZ(params->params[RSA_PRIME2]),
- TOMPZ(params->params[RSA_PRIME1]));
+ if (!mpz_invert(TOMPZ(t1),
+ TOMPZ(params->params[RSA_PRIME2]),
+ TOMPZ(params->params[RSA_PRIME1]))) {
+ ret = gnutls_assert_val(GNUTLS_E_PK_INVALID_PRIVKEY);
+ goto rsa_cleanup;
+ }
if (_gnutls_mpi_cmp(t1, params->params[RSA_COEF])
!= 0) {
ret =
--
2.53.0

View File

@ -1,58 +0,0 @@
From 2799775899d630d211647d5c916b987746dd7fbf Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 10 Apr 2026 20:30:27 +0200
Subject: [PATCH] lib/x509: fix cleanup when gnutls_x509_crt_list_import_pkcs11
fails
Previously gnutls_x509_trust_list_remove_trust_file tried to free
the entire xcrt_list, even though one source of failures is
gnutls_pkcs11_obj_list_import_url2 that deinits it up to the correct
position.
With this change, both functions zero unused entries.
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1819
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/pkcs11.c | 1 +
lib/x509/verify-high2.c | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/pkcs11.c b/lib/pkcs11.c
index d8d4a6511..6a4915f3f 100644
--- a/lib/pkcs11.c
+++ b/lib/pkcs11.c
@@ -3661,6 +3661,7 @@ gnutls_x509_crt_list_import_pkcs11(gnutls_x509_crt_t * certs,
cleanup:
for (j = 0; j < i; j++) {
gnutls_x509_crt_deinit(certs[j]);
+ certs[j] = NULL;
}
return ret;
diff --git a/lib/x509/verify-high2.c b/lib/x509/verify-high2.c
index 9820595e9..9f30a219d 100644
--- a/lib/x509/verify-high2.c
+++ b/lib/x509/verify-high2.c
@@ -216,7 +216,7 @@ int add_trust_list_pkcs11_object_url(gnutls_x509_trust_list_t list, const char *
goto cleanup;
}
- xcrt_list = gnutls_malloc(sizeof(gnutls_x509_crt_t) * pcrt_list_size);
+ xcrt_list = gnutls_calloc(pcrt_list_size, sizeof(gnutls_x509_crt_t));
if (xcrt_list == NULL) {
ret = GNUTLS_E_MEMORY_ERROR;
goto cleanup;
@@ -264,7 +264,7 @@ int remove_pkcs11_object_url(gnutls_x509_trust_list_t list, const char *url)
goto cleanup;
}
- xcrt_list = gnutls_malloc(sizeof(gnutls_x509_crt_t) * pcrt_list_size);
+ xcrt_list = gnutls_calloc(pcrt_list_size, sizeof(gnutls_x509_crt_t));
if (xcrt_list == NULL) {
ret = GNUTLS_E_MEMORY_ERROR;
goto cleanup;
--
2.53.0

File diff suppressed because it is too large Load Diff

View File

@ -1,409 +0,0 @@
From 08f979a318f8c553b4b781e0a586ba54f4e7b165 Mon Sep 17 00:00:00 2001
From: rpm-build <rpm-build>
Date: Fri, 6 Feb 2026 15:43:54 +0100
Subject: [PATCH 1/2] tests/pkcs11/pkcs11-mock4: add, modified for 3.8.10
---
tests/Makefile.am | 6 ++
tests/pkcs11/pkcs11-mock4.c | 125 ++++++++++++++++++++++++++++++++++++
2 files changed, 131 insertions(+)
create mode 100644 tests/pkcs11/pkcs11-mock4.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1019f6c1d8..467284925a 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -338,6 +338,11 @@ 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
@@ -586,6 +591,7 @@ TESTS_ENVIRONMENT += \
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 \
diff --git a/tests/pkcs11/pkcs11-mock4.c b/tests/pkcs11/pkcs11-mock4.c
new file mode 100644
index 0000000000..a6dd21cddd
--- /dev/null
+++ b/tests/pkcs11/pkcs11-mock4.c
@@ -0,0 +1,125 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <dlfcn.h>
+#include <p11-kit/pkcs11.h>
+#include <p11-kit/pkcs11x.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+
+#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
+
+#define LOCK_FLAGS (CKF_LIBRARY_CANT_CREATE_OS_THREADS | CKF_OS_LOCKING_OK)
+
+static CK_RV override_C_Initialize(void *args)
+{
+ CK_C_INITIALIZE_ARGS *init_args = args;
+ static bool first = true;
+
+ // we don't have threadsafe initialization/fallback in 3.8.10...
+ /*
+ if (first) {
+ assert(init_args &&
+ (init_args->flags & LOCK_FLAGS) == LOCK_FLAGS);
+ first = false;
+ return CKR_CANT_LOCK;
+ } else {
+ assert(!init_args ||
+ (init_args->flags & LOCK_FLAGS) != LOCK_FLAGS);
+ }
+ */
+ // ... so we expect 3.8.10 behaviour
+ assert(first);
+ assert(init_args);
+ assert(!(init_args->flags & LOCK_FLAGS) != LOCK_FLAGS);
+ first = false;
+
+ 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);
+}
--
2.52.0
From ab8ad3b005c1937ed52993cdd6a0c5e4eec98cfc Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Tue, 18 Nov 2025 13:17:55 +0900
Subject: [PATCH 2/2] 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 <ueno@gnu.org>
---
lib/pkcs11_write.c | 5 +-
tests/Makefile.am | 4 +-
tests/pkcs11/long-label.c | 164 ++++++++++++++++++++++++++++++++++++++
3 files changed, 170 insertions(+), 3 deletions(-)
create mode 100644 tests/pkcs11/long-label.c
diff --git a/lib/pkcs11_write.c b/lib/pkcs11_write.c
index 3ce794b076..5685411ee1 100644
--- a/lib/pkcs11_write.c
+++ b/lib/pkcs11_write.c
@@ -28,6 +28,7 @@
#include "pkcs11x.h"
#include <x509/common.h>
#include "pk.h"
+#include "minmax.h"
static const ck_bool_t tval = 1;
static const ck_bool_t fval = 0;
@@ -1199,7 +1200,7 @@ int gnutls_pkcs11_delete_url(const char *object_url, unsigned int flags)
* 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
@@ -1238,7 +1239,7 @@ gnutls_pkcs11_token_init(const char *token_url,
/* 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);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 467284925a..ed8b7e19c3 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -488,11 +488,13 @@ buffer_CPPFLAGS = $(AM_CPPFLAGS) \
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/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_long_label_DEPENDENCIES = libpkcs11mock4.la libutils.la
+pkcs11_long_label_LDADD = $(LDADD) $(LIBDL)
endif
endif
diff --git a/tests/pkcs11/long-label.c b/tests/pkcs11/long-label.c
new file mode 100644
index 0000000000..a70bc97284
--- /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 <https://www.gnu.org/licenses/>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_WIN32)
+
+int main(void)
+{
+ exit(77);
+}
+
+#else
+
+#include <string.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+
+#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 */
--
2.52.0

View File

@ -1,488 +0,0 @@
From bd70e112d4d1f063223f0f0886aaaf33699390d0 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 22 Apr 2026 14:19:57 +0200
Subject: [PATCH 1/5] buffers: rename a variable in parse_handshake_header
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index be51f3aac..d9ef1d907 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -876,7 +876,7 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
{
uint8_t *dataptr = NULL; /* for realloc */
size_t handshake_header_size =
- HANDSHAKE_HEADER_SIZE(session), data_size, frag_size;
+ HANDSHAKE_HEADER_SIZE(session), data_size, frag_length;
/* Note: SSL2_HEADERS == 1 */
if (_mbuffer_get_udata_size(bufel) < handshake_header_size)
@@ -892,7 +892,7 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
&& bufel->htype == GNUTLS_HANDSHAKE_CLIENT_HELLO_V2)) {
handshake_header_size = SSL2_HEADERS; /* we've already read one byte */
- frag_size = _mbuffer_get_udata_size(bufel) - handshake_header_size; /* we've read the first byte */
+ frag_length = _mbuffer_get_udata_size(bufel) - handshake_header_size; /* we've read the first byte */
if (dataptr[0] != GNUTLS_HANDSHAKE_CLIENT_HELLO)
return
@@ -902,7 +902,7 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
hsk->sequence = 0;
hsk->start_offset = 0;
- hsk->length = frag_size;
+ hsk->length = frag_length;
} else
#endif
{ /* TLS or DTLS handshake headers */
@@ -919,12 +919,12 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
hsk->sequence = _gnutls_read_uint16(&dataptr[4]);
hsk->start_offset =
_gnutls_read_uint24(&dataptr[6]);
- frag_size =
+ frag_length =
_gnutls_read_uint24(&dataptr[9]);
} else {
hsk->sequence = 0;
hsk->start_offset = 0;
- frag_size =
+ frag_length =
MIN((_mbuffer_get_udata_size(bufel) -
handshake_header_size), hsk->length);
}
@@ -940,8 +940,8 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
}
data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size;
- if (frag_size > 0)
- hsk->end_offset = hsk->start_offset + frag_size - 1;
+ if (frag_length > 0)
+ hsk->end_offset = hsk->start_offset + frag_length - 1;
else
hsk->end_offset = 0;
@@ -949,15 +949,15 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
("HSK[%p]: %s (%u) was received. Length %d[%d], frag offset %d, frag length: %d, sequence: %d\n",
session, _gnutls_handshake2str(hsk->htype),
(unsigned) hsk->htype, (int) hsk->length, (int) data_size,
- hsk->start_offset, (int) frag_size,
+ hsk->start_offset, (int) frag_length,
(int) hsk->sequence);
hsk->header_size = handshake_header_size;
memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel),
handshake_header_size);
- if (hsk->length > 0 && (frag_size > data_size ||
- (frag_size > 0 &&
+ if (hsk->length > 0 && (frag_length > data_size ||
+ (frag_length > 0 &&
hsk->end_offset >= hsk->length))) {
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
--
2.53.0
From e5b72c53c7d789d19d1d1cd10b275e87d0415413 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 23 Mar 2026 15:09:43 +0100
Subject: [PATCH 2/5] buffers: switch from end_offset over to frag_length
Instead of maintaining an inclusive [start_offset, end_offset] range
when reassembling DTLS handshake,
track start_offset and a relative frag_length instead.
You'd think it'd be a no-op, but it fixes:
* 0-length fragments triggering completion if message was 1 byte long
* a remotely triggerable underflow and an ensuing heap overrun
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1811
Fixes: CVE-2026-33845
Fixes: GNUTLS-SA-2026-04-29-3
CVSS: 7.5 High CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 64 +++++++++++++++++++++++++-----------------------
lib/gnutls_int.h | 4 +--
2 files changed, 36 insertions(+), 32 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index d9ef1d907..134d680f4 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -940,10 +940,7 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
}
data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size;
- if (frag_length > 0)
- hsk->end_offset = hsk->start_offset + frag_length - 1;
- else
- hsk->end_offset = 0;
+ hsk->frag_length = frag_length;
_gnutls_handshake_log
("HSK[%p]: %s (%u) was received. Length %d[%d], frag offset %d, frag length: %d, sequence: %d\n",
@@ -956,14 +953,16 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel),
handshake_header_size);
- if (hsk->length > 0 && (frag_length > data_size ||
- (frag_length > 0 &&
- hsk->end_offset >= hsk->length))) {
+ if (hsk->length > 0 &&
+ (frag_length > data_size ||
+ (frag_length > 0 &&
+ hsk->start_offset + frag_length > hsk->length))) {
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
}
- else if (hsk->length == 0 && hsk->end_offset != 0
- && hsk->start_offset != 0)
+ else if (hsk->length == 0 &&
+ hsk->start_offset + frag_length != hsk->start_offset &&
+ hsk->start_offset != 0)
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
@@ -1020,19 +1019,19 @@ static int merge_handshake_packet(gnutls_session_t session,
gnutls_assert_val(GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS);
if (!exists) {
- if (hsk->length > 0 && hsk->end_offset > 0
- && hsk->end_offset - hsk->start_offset + 1 !=
- hsk->length) {
+ if (hsk->length > 0) {
ret =
_gnutls_buffer_resize(&hsk->data, hsk->length);
if (ret < 0)
return gnutls_assert_val(ret);
hsk->data.length = hsk->length;
+ }
+ if (hsk->length > 0 && hsk->frag_length > 0 &&
+ hsk->frag_length != hsk->length) {
memmove(&hsk->data.data[hsk->start_offset],
- hsk->data.data,
- hsk->end_offset - hsk->start_offset + 1);
+ hsk->data.data, hsk->frag_length);
}
session->internals.handshake_recv_buffer_size++;
@@ -1066,20 +1065,27 @@ static int merge_handshake_packet(gnutls_session_t session,
}
if (hsk->start_offset < recv_buf[pos].start_offset &&
- hsk->end_offset + 1 >= recv_buf[pos].start_offset) {
+ hsk->start_offset + hsk->frag_length >=
+ recv_buf[pos].start_offset) {
memcpy(&recv_buf[pos].data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
recv_buf[pos].start_offset = hsk->start_offset;
- recv_buf[pos].end_offset =
- MIN(hsk->end_offset, recv_buf[pos].end_offset);
- } else if (hsk->end_offset > recv_buf[pos].end_offset &&
- hsk->start_offset <= recv_buf[pos].end_offset + 1) {
+ recv_buf[pos].frag_length = MIN(
+ hsk->frag_length, recv_buf[pos].frag_length);
+ } else if (hsk->start_offset + hsk->frag_length >
+ recv_buf[pos].start_offset +
+ recv_buf[pos].frag_length &&
+ hsk->start_offset <=
+ recv_buf[pos].start_offset +
+ recv_buf[pos].frag_length) {
memcpy(&recv_buf[pos].data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
- recv_buf[pos].end_offset = hsk->end_offset;
recv_buf[pos].start_offset = MIN(
hsk->start_offset, recv_buf[pos].start_offset);
+ recv_buf[pos].frag_length = hsk->start_offset +
+ hsk->frag_length -
+ recv_buf[pos].start_offset;
}
_gnutls_handshake_buffer_clear(hsk);
}
@@ -1140,8 +1146,8 @@ static int get_last_packet(gnutls_session_t session,
}
else if ((recv_buf[LAST_ELEMENT].start_offset == 0 &&
- recv_buf[LAST_ELEMENT].end_offset ==
- recv_buf[LAST_ELEMENT].length - 1)
+ recv_buf[LAST_ELEMENT].frag_length ==
+ recv_buf[LAST_ELEMENT].length)
|| recv_buf[LAST_ELEMENT].length == 0) {
session->internals.dtls.hsk_read_seq++;
_gnutls_handshake_buffer_move(hsk,
@@ -1153,7 +1159,9 @@ static int get_last_packet(gnutls_session_t session,
/* if we don't have a complete handshake message, but we
* have queued data waiting, try again to reconstruct the
* handshake packet, using the queued */
- if (recv_buf[LAST_ELEMENT].end_offset != recv_buf[LAST_ELEMENT].length - 1 &&
+ if ((recv_buf[LAST_ELEMENT].start_offset +
+ recv_buf[LAST_ELEMENT].frag_length) !=
+ recv_buf[LAST_ELEMENT].length &&
record_check_unprocessed(session) > 0)
return gnutls_assert_val(GNUTLS_E_INT_CHECK_AGAIN);
else
@@ -1341,9 +1349,7 @@ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session)
ret);
data_size =
- MIN(tmp.length,
- tmp.end_offset - tmp.start_offset +
- 1);
+ MIN(tmp.length, tmp.frag_length);
ret =
_gnutls_buffer_append_data(&tmp.data,
@@ -1361,9 +1367,7 @@ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session)
merge_handshake_packet(session, &tmp);
if (ret < 0)
return gnutls_assert_val(ret);
-
- }
- while (_mbuffer_get_udata_size(bufel) > 0);
+ } while (_mbuffer_get_udata_size(bufel) > 0);
prev = bufel;
bufel =
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index 815f69b10..0e753a739 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -396,10 +396,10 @@ typedef struct {
uint16_t sequence;
/* indicate whether that message is complete.
- * complete means start_offset == 0 and end_offset == length
+ * complete means start_offset == 0 and frag_length == length
*/
uint32_t start_offset;
- uint32_t end_offset;
+ uint32_t frag_length; /* used exclusively in DTLS reassembly */
uint8_t header[MAX_HANDSHAKE_HEADER_SIZE];
int header_size;
--
2.53.0
From 21563d8778dfec2d87d415d7e7f7ba3b66f1d283 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 23 Mar 2026 15:57:39 +0100
Subject: [PATCH 3/5] buffers: simplify and tighten parse_handshake_header
checks
* frag_size > data_size is now rejected even when length == 0
* length == 0 && frag_size > 0 is now rejected even when start_offset == 0
* start_offset > length is now rejected even when frag_size == 0
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index 134d680f4..7aa22811f 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -953,16 +953,10 @@ parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel,
memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel),
handshake_header_size);
- if (hsk->length > 0 &&
- (frag_length > data_size ||
- (frag_length > 0 &&
- hsk->start_offset + frag_length > hsk->length))) {
+ if (frag_length > data_size) /* fragment straight up lying to us */
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
- }
- else if (hsk->length == 0 &&
- hsk->start_offset + frag_length != hsk->start_offset &&
- hsk->start_offset != 0)
+ if (frag_length + hsk->start_offset > hsk->length) /* reassembly OOB */
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
--
2.53.0
From 1057846cd5c611037113327f5ae38af2c5cd7c87 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 20 Mar 2026 16:55:10 +0100
Subject: [PATCH 4/5] tests/mini-dtls-fragments: test injecting 0-length ones
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/mini-dtls-fragments.c | 47 +++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
index 4f4c8f623..5d226a56a 100644
--- a/tests/mini-dtls-fragments.c
+++ b/tests/mini-dtls-fragments.c
@@ -165,6 +165,50 @@ static uint64_t read_u48(const uint8_t *p)
return seq;
}
+static void make_0frag(uint8_t *dst, const uint8_t *src)
+{
+ memcpy(dst, src, 13 + 12);
+ dst[13 + 6] = dst[13 + 7] = dst[13 + 8] = 0; /* frag offset = 0 */
+ dst[13 + 9] = dst[13 + 10] = dst[13 + 11] = 0; /* frag length = 0 */
+ /* record payload length: just the 12-byte handshake header, no data */
+ dst[11] = 0;
+ dst[12] = 12;
+}
+
+ATTRIBUTE_NONNULL((2))
+static ssize_t client_push_inj0(gnutls_transport_ptr_t tr, const void *d_,
+ size_t l)
+{
+ static uint32_t seq = 0;
+ const uint8_t *d = (const uint8_t *)d_;
+ uint8_t frag[13 + 12];
+ uint8_t *b;
+
+ if (l < 13) /* too short for a DTLS record header */
+ return queue_put(&c2s, d, l);
+ if (!(d[3] == 0 && d[4] == 0)) /* not epoch 0: encrypted, don't touch */
+ return queue_put(&c2s, d, l);
+
+ b = malloc(l);
+ assert(b);
+ memcpy(b, d, l);
+
+ if (l >= 13 + 12 && d[0] == 22) { /* handshake record: inject 0-frag */
+ make_0frag(frag, d);
+ write_u48(frag + 5, seq++); /* 0-frag first */
+ queue_put(&c2s, frag, sizeof(frag));
+
+ write_u48(b + 5, seq++); /* real second */
+ queue_put(&c2s, b, l);
+ } else { /* other (e.g. CCS): just renumber */
+ write_u48(b + 5, seq++);
+ queue_put(&c2s, b, l);
+ }
+
+ free(b);
+ return l;
+}
+
static void test(gnutls_push_func client_push, bool expect_success)
{
gnutls_session_t client, server;
@@ -459,7 +503,10 @@ static ssize_t client_push_split_hello_bad_seq(gnutls_transport_ptr_t tr,
void doit(void)
{
global_init();
+ success("normal:\n");
test(client_push_normal, true);
+ success("valid 0-len fragments injected every 2nd push in epoch0:\n");
+ test(client_push_inj0, true);
success("malicious reassembly bug exploitation (#1816):\n");
test_malicious1816();
success("split client hello smoke-test\n");
--
2.53.0
From d59d9cc7943568ee06e5ba35fd7ae2a6e433059f Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 23 Mar 2026 20:24:26 +0100
Subject: [PATCH 5/5] tests/mini-dtls-fragments: test #1811 crashing datagram
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/mini-dtls-fragments.c | 58 +++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
index 5d226a56a..03881058a 100644
--- a/tests/mini-dtls-fragments.c
+++ b/tests/mini-dtls-fragments.c
@@ -500,6 +500,62 @@ static ssize_t client_push_split_hello_bad_seq(gnutls_transport_ptr_t tr,
return l;
}
+static void test_malicious1811(void)
+{
+ static const uint8_t dgram[] = {
+ 22, /* type = handshake */
+ 0xfe, 0xfd, /* version = DTLS 1.2 */
+ 0x00, 0x00, /* epoch = 0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* seq = 0 */
+ 0x00, 0x0c, /* record length = 12 */
+
+ 0x01, /* type = ClientHello */
+ 0xff, 0xff, 0xff, /* length = 0xffffff (!) */
+ 0x00, 0x00, /* msg seq = 0 */
+ 0x00, 0x00, 0x02, /* frag_offset = 2 (!) */
+ 0x00, 0x00, 0x00, /* frag_length = 0 (!) */
+ };
+ gnutls_session_t server;
+ gnutls_certificate_credentials_t scred;
+ int sr;
+
+ if (debug)
+ gnutls_global_set_log_level(4711);
+
+ gnutls_certificate_allocate_credentials(&scred);
+ gnutls_certificate_set_x509_key_mem(scred, &server_cert, &server_key,
+ GNUTLS_X509_FMT_PEM);
+
+ gnutls_init(&server, GNUTLS_SERVER | GNUTLS_DATAGRAM);
+ gnutls_priority_set_direct(server, "NORMAL:+VERS-DTLS1.2", NULL);
+ gnutls_credentials_set(server, GNUTLS_CRD_CERTIFICATE, scred);
+
+ gnutls_dtls_set_timeouts(server, 80 * 1000, 8 * 1000);
+
+ gnutls_transport_set_ptr(server, server);
+ gnutls_transport_set_push_function(server, server_push);
+ gnutls_transport_set_pull_function(server, server_pull);
+ gnutls_transport_set_pull_timeout_function(server,
+ c2s_pull_timeout_once);
+
+ queue_put(&c2s, dgram, sizeof(dgram));
+
+ gnutls_global_set_log_function(server_log_func);
+ do {
+ sr = gnutls_handshake(server); /* crashes if vulnerable */
+ } while (c2s.head != c2s.tail && !gnutls_error_is_fatal(sr));
+ if (gnutls_error_is_fatal(sr))
+ fail("server: %s\n", gnutls_strerror(sr));
+
+ success("OK\n");
+
+ queue_reset(&c2s);
+ queue_reset(&s2c);
+
+ gnutls_deinit(server);
+ gnutls_certificate_free_credentials(scred);
+}
+
void doit(void)
{
global_init();
@@ -513,6 +569,8 @@ void doit(void)
test(client_push_split_hello, true);
success("split client hello smoke-test and mangle sequence number\n");
test(client_push_split_hello_bad_seq, false);
+ success("malicious injection aiming for an underflow (#1811):\n");
+ test_malicious1811();
gnutls_global_deinit();
}
--
2.53.0

View File

@ -1,944 +0,0 @@
From 4f94e5cfe1f252a431e41642b0752e7e0daf43b9 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 20 Mar 2026 16:09:40 +0100
Subject: [PATCH 1/8] tests/mini-dtls-fragments: implement a basic DTLS test
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/Makefile.am | 6 +-
tests/mini-dtls-fragments.c | 206 ++++++++++++++++++++++++++++++++++++
2 files changed, 211 insertions(+), 1 deletion(-)
create mode 100644 tests/mini-dtls-fragments.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index afb665597..f85a6947d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -224,7 +224,7 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei
set_x509_ocsp_multi_cli kdf-api keylog-func \
dtls_hello_random_value tls_hello_random_value x509cert-dntypes \
pkcs7-verify-double-free \
- tls12-rehandshake-ticket
+ tls12-rehandshake-ticket mini-dtls-fragments
if HAVE_SECCOMP_TESTS
ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp
@@ -487,6 +487,10 @@ buffer_CPPFLAGS = $(AM_CPPFLAGS) \
-I$(top_srcdir)/gl \
-I$(top_builddir)/gl
+mini_dtls_fragments_CPPFLAGS = $(AM_CPPFLAGS) \
+ -I$(top_srcdir)/gl \
+ -I$(top_builddir)/gl
+
if ENABLE_PKCS11
if !WINDOWS
ctests += tls13/post-handshake-with-cert-pkcs11 pkcs11/tls-neg-pkcs11-no-key \
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
new file mode 100644
index 000000000..0abe557f7
--- /dev/null
+++ b/tests/mini-dtls-fragments.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ *
+ * Author: Alexander Sosedkin
+ *
+ * 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 <https://www.gnu.org/licenses/>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_WIN32)
+
+int main(void)
+{
+ exit(77);
+}
+
+#else
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/dtls.h>
+#include "cert-common.h"
+#include "utils.h"
+
+#include "attribute.h"
+
+static void server_log_func(int level, const char *str)
+{
+ fprintf(stderr, "server|<%d>| %s", level, str);
+}
+
+static void client_log_func(int level, const char *str)
+{
+ fprintf(stderr, "client|<%d>| %s", level, str);
+}
+
+#define QUEUE_SIZE 1024
+#define PACKET_SIZE 2048
+
+typedef struct {
+ uint8_t buf[PACKET_SIZE];
+ size_t len;
+} packet_t;
+typedef struct {
+ packet_t packets[QUEUE_SIZE];
+ size_t head;
+ size_t tail;
+} queue_t;
+
+static queue_t c2s, s2c;
+
+static int queue_put(queue_t *q, const void *buf, size_t len)
+{
+ assert(len <= PACKET_SIZE);
+ memcpy(q->packets[q->tail].buf, buf, len);
+ q->packets[q->tail].len = len;
+ q->tail++;
+ q->tail %= QUEUE_SIZE;
+ assert(q->tail != q->head);
+ return len;
+}
+
+static ssize_t queue_get(queue_t *q, gnutls_session_t s, void *buf, size_t len)
+{
+ if (q->head == q->tail) {
+ gnutls_transport_set_errno(s, EAGAIN);
+ return -1;
+ }
+ size_t n = q->packets[q->head].len;
+ memcpy(buf, q->packets[q->head].buf, n);
+ q->head++;
+ q->head %= QUEUE_SIZE;
+ return n;
+}
+
+static void queue_reset(queue_t *q)
+{
+ q->head = q->tail = 0;
+}
+
+static int pull_timeout(gnutls_transport_ptr_t tr, unsigned ms)
+{
+ return 1;
+}
+
+static ssize_t server_pull(gnutls_transport_ptr_t tr, void *b, size_t l)
+{
+ return queue_get(&c2s, (gnutls_session_t)tr, b, l);
+}
+
+static ssize_t client_pull(gnutls_transport_ptr_t tr, void *b, size_t l)
+{
+ return queue_get(&s2c, (gnutls_session_t)tr, b, l);
+}
+
+static ssize_t server_push(gnutls_transport_ptr_t tr, const void *b, size_t l)
+{
+ return queue_put(&s2c, b, l);
+}
+
+static ssize_t client_push_normal(gnutls_transport_ptr_t tr, const void *b,
+ size_t l)
+{
+ return queue_put(&c2s, b, l);
+}
+
+static void test(gnutls_push_func client_push)
+{
+ gnutls_session_t client, server;
+ gnutls_certificate_credentials_t ccred, scred;
+ int cr = 0, sr = 0;
+ bool cdone = false, sdone = false;
+
+ if (debug)
+ gnutls_global_set_log_level(4711);
+
+ gnutls_certificate_allocate_credentials(&scred);
+ gnutls_certificate_set_x509_key_mem(scred, &server_cert, &server_key,
+ GNUTLS_X509_FMT_PEM);
+ gnutls_certificate_allocate_credentials(&ccred);
+
+ gnutls_init(&server, GNUTLS_SERVER | GNUTLS_DATAGRAM);
+ gnutls_init(&client, GNUTLS_CLIENT | GNUTLS_DATAGRAM);
+
+ gnutls_priority_set_direct(server, "NORMAL:-VERS-ALL:+VERS-DTLS1.2",
+ NULL);
+ gnutls_priority_set_direct(client, "NORMAL:-VERS-ALL:+VERS-DTLS1.2",
+ NULL);
+
+ gnutls_credentials_set(server, GNUTLS_CRD_CERTIFICATE, scred);
+ gnutls_credentials_set(client, GNUTLS_CRD_CERTIFICATE, ccred);
+
+ gnutls_dtls_set_timeouts(client, 80 * 1000, 8 * 1000);
+ gnutls_dtls_set_timeouts(server, 80 * 1000, 8 * 1000);
+
+ gnutls_transport_set_ptr(client, client);
+ gnutls_transport_set_push_function(client, client_push);
+ gnutls_transport_set_pull_function(client, client_pull);
+ gnutls_transport_set_pull_timeout_function(client, pull_timeout);
+
+ gnutls_transport_set_ptr(server, server);
+ gnutls_transport_set_push_function(server, server_push);
+ gnutls_transport_set_pull_function(server, server_pull);
+ gnutls_transport_set_pull_timeout_function(server, pull_timeout);
+
+ while (!cdone || !sdone) {
+ gnutls_global_set_log_function(client_log_func);
+ if (!cdone)
+ cr = gnutls_handshake(client);
+ if (!cr || gnutls_error_is_fatal(cr))
+ cdone = true;
+
+ gnutls_global_set_log_function(server_log_func);
+ if (!sdone)
+ sr = gnutls_handshake(server);
+ if (!sr || gnutls_error_is_fatal(sr))
+ sdone = true;
+ }
+
+ if (cr)
+ fail("client: %s\n", gnutls_strerror(cr));
+ if (sr)
+ fail("server: %s\n", gnutls_strerror(sr));
+
+ success("OK\n");
+
+ queue_reset(&c2s);
+ queue_reset(&s2c);
+
+ gnutls_deinit(client);
+ gnutls_deinit(server);
+ gnutls_certificate_free_credentials(ccred);
+ gnutls_certificate_free_credentials(scred);
+}
+
+void doit(void)
+{
+ global_init();
+ test(client_push_normal);
+ gnutls_global_deinit();
+}
+
+#endif /* _WIN32 */
--
2.53.0
From 87b63fec37a9bae87ec34f6a55c57cb64fe4c7aa Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 4 May 2026 09:16:13 +0000
Subject: [PATCH 2/8] buffers: reformat ssmerge_handshake_packet
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 64 +++++++++++++++++++++++++--------------------------
1 file changed, 31 insertions(+), 33 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index 2d0e3d8af..827e97fe5 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -1031,46 +1031,44 @@ static int merge_handshake_packet(gnutls_session_t session,
_gnutls_write_uint24(0, &hsk->header[6]);
_gnutls_write_uint24(hsk->length, &hsk->header[9]);
- _gnutls_handshake_buffer_move(&session->internals.
- handshake_recv_buffer[pos],
- hsk);
+ _gnutls_handshake_buffer_move(
+ &session->internals.handshake_recv_buffer[pos], hsk);
} else {
if (hsk->start_offset <
- session->internals.handshake_recv_buffer[pos].
- start_offset
- && hsk->end_offset + 1 >=
- session->internals.handshake_recv_buffer[pos].
- start_offset) {
- memcpy(&session->internals.
- handshake_recv_buffer[pos].data.data[hsk->
- start_offset],
+ session->internals.handshake_recv_buffer[pos]
+ .start_offset &&
+ hsk->end_offset + 1 >=
+ session->internals.handshake_recv_buffer[pos]
+ .start_offset) {
+ memcpy(&session->internals.handshake_recv_buffer[pos]
+ .data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
- session->internals.handshake_recv_buffer[pos].
- start_offset = hsk->start_offset;
- session->internals.handshake_recv_buffer[pos].
- end_offset =
- MIN(hsk->end_offset,
- session->internals.
- handshake_recv_buffer[pos].end_offset);
+ session->internals.handshake_recv_buffer[pos]
+ .start_offset = hsk->start_offset;
+ session->internals.handshake_recv_buffer[pos]
+ .end_offset = MIN(
+ hsk->end_offset,
+ session->internals.handshake_recv_buffer[pos]
+ .end_offset);
} else if (hsk->end_offset >
- session->internals.handshake_recv_buffer[pos].
- end_offset
- && hsk->start_offset <=
- session->internals.handshake_recv_buffer[pos].
- end_offset + 1) {
- memcpy(&session->internals.
- handshake_recv_buffer[pos].data.data[hsk->
- start_offset],
+ session->internals.handshake_recv_buffer[pos]
+ .end_offset &&
+ hsk->start_offset <=
+ session->internals.handshake_recv_buffer[pos]
+ .end_offset +
+ 1) {
+ memcpy(&session->internals.handshake_recv_buffer[pos]
+ .data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
- session->internals.handshake_recv_buffer[pos].
- end_offset = hsk->end_offset;
- session->internals.handshake_recv_buffer[pos].
- start_offset =
- MIN(hsk->start_offset,
- session->internals.
- handshake_recv_buffer[pos].start_offset);
+ session->internals.handshake_recv_buffer[pos]
+ .end_offset = hsk->end_offset;
+ session->internals.handshake_recv_buffer[pos]
+ .start_offset = MIN(
+ hsk->start_offset,
+ session->internals.handshake_recv_buffer[pos]
+ .start_offset);
}
_gnutls_handshake_buffer_clear(hsk);
}
--
2.53.0
From 9deffca528c23bbb218f5ec3bd4bb1bf4cbd1fc0 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 17 Apr 2026 17:49:31 +0200
Subject: [PATCH 3/8] buffers: shorten merge_handshake_packet using recv_buf
I had vague concerns about thread-safety of this,
but then this pattern already exists within the file.
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 52 +++++++++++++++++----------------------------------
1 file changed, 17 insertions(+), 35 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index 827e97fe5..9ff606501 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -992,9 +992,11 @@ static int merge_handshake_packet(gnutls_session_t session,
int exists = 0, i, pos = 0;
int ret;
+ handshake_buffer_st *recv_buf =
+ session->internals.handshake_recv_buffer;
+
for (i = 0; i < session->internals.handshake_recv_buffer_size; i++) {
- if (session->internals.handshake_recv_buffer[i].htype ==
- hsk->htype) {
+ if (recv_buf[i].htype == hsk->htype) {
exists = 1;
pos = i;
break;
@@ -1031,44 +1033,24 @@ static int merge_handshake_packet(gnutls_session_t session,
_gnutls_write_uint24(0, &hsk->header[6]);
_gnutls_write_uint24(hsk->length, &hsk->header[9]);
- _gnutls_handshake_buffer_move(
- &session->internals.handshake_recv_buffer[pos], hsk);
+ _gnutls_handshake_buffer_move(&recv_buf[pos], hsk);
} else {
- if (hsk->start_offset <
- session->internals.handshake_recv_buffer[pos]
- .start_offset &&
- hsk->end_offset + 1 >=
- session->internals.handshake_recv_buffer[pos]
- .start_offset) {
- memcpy(&session->internals.handshake_recv_buffer[pos]
- .data.data[hsk->start_offset],
+ if (hsk->start_offset < recv_buf[pos].start_offset &&
+ hsk->end_offset + 1 >= recv_buf[pos].start_offset) {
+ memcpy(&recv_buf[pos].data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
- session->internals.handshake_recv_buffer[pos]
- .start_offset = hsk->start_offset;
- session->internals.handshake_recv_buffer[pos]
- .end_offset = MIN(
- hsk->end_offset,
- session->internals.handshake_recv_buffer[pos]
- .end_offset);
- } else if (hsk->end_offset >
- session->internals.handshake_recv_buffer[pos]
- .end_offset &&
- hsk->start_offset <=
- session->internals.handshake_recv_buffer[pos]
- .end_offset +
- 1) {
- memcpy(&session->internals.handshake_recv_buffer[pos]
- .data.data[hsk->start_offset],
+ recv_buf[pos].start_offset = hsk->start_offset;
+ recv_buf[pos].end_offset =
+ MIN(hsk->end_offset, recv_buf[pos].end_offset);
+ } else if (hsk->end_offset > recv_buf[pos].end_offset &&
+ hsk->start_offset <= recv_buf[pos].end_offset + 1) {
+ memcpy(&recv_buf[pos].data.data[hsk->start_offset],
hsk->data.data, hsk->data.length);
- session->internals.handshake_recv_buffer[pos]
- .end_offset = hsk->end_offset;
- session->internals.handshake_recv_buffer[pos]
- .start_offset = MIN(
- hsk->start_offset,
- session->internals.handshake_recv_buffer[pos]
- .start_offset);
+ recv_buf[pos].end_offset = hsk->end_offset;
+ recv_buf[pos].start_offset = MIN(
+ hsk->start_offset, recv_buf[pos].start_offset);
}
_gnutls_handshake_buffer_clear(hsk);
}
--
2.53.0
From 65ab33fa54e34fba69d793735b7df3d383d1ff78 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 17 Apr 2026 18:21:36 +0200
Subject: [PATCH 4/8] buffers: add more checks to DTLS reassembly
Previously, gnutls didn't check that DTLS fragments claimed
a consistent message_length value.
Additionally, a crucial array size check was missing,
enabling an attacker to cause a heap overwrite.
The updated version rejects fragments with mismatching length
and adds a missing boundary check.
Reported-by: Haruto Kimura (Stella)
Reported-by: Oscar Reparaz
Reported-by: Zou Dikai
Fixes: #1816
Fixes: #1838
Fixes: #1839
Fixes: CVE-2026-33846
Fixes: GNUTLS-SA-2026-04-29-1
CVSS: 7.4 High CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
CVSS: 7.5 High CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/lib/buffers.c b/lib/buffers.c
index 9ff606501..20ff909bd 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -1036,6 +1036,26 @@ static int merge_handshake_packet(gnutls_session_t session,
_gnutls_handshake_buffer_move(&recv_buf[pos], hsk);
} else {
+ if (hsk->length != recv_buf[pos].length) {
+ /* inconsistent across fragments */
+ _gnutls_handshake_buffer_clear(hsk);
+ return gnutls_assert_val(
+ GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+ /* start_offset + data.length <= hsk->length <= max_length */
+ if (hsk->length < hsk->start_offset + hsk->data.length) {
+ /* impossible claims, overflow requested */
+ _gnutls_handshake_buffer_clear(hsk);
+ return gnutls_assert_val(
+ GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+ if (hsk->length > recv_buf[pos].data.max_length) {
+ /* we don't have this much allocated, overflow guard */
+ _gnutls_handshake_buffer_clear(hsk);
+ return gnutls_assert_val(
+ GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+
if (hsk->start_offset < recv_buf[pos].start_offset &&
hsk->end_offset + 1 >= recv_buf[pos].start_offset) {
memcpy(&recv_buf[pos].data.data[hsk->start_offset],
--
2.53.0
From cf3f1955e58cbcc10373b841bb101fb058565d87 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 1 Apr 2026 19:51:45 +0200
Subject: [PATCH 5/8] tests/mini-dtls-fragments: extend with a #1816 reproducer
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/mini-dtls-fragments.c | 119 ++++++++++++++++++++++++++++++++++++
1 file changed, 119 insertions(+)
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
index 0abe557f7..d997ad64f 100644
--- a/tests/mini-dtls-fragments.c
+++ b/tests/mini-dtls-fragments.c
@@ -106,6 +106,11 @@ static int pull_timeout(gnutls_transport_ptr_t tr, unsigned ms)
return 1;
}
+static int c2s_pull_timeout_once(gnutls_transport_ptr_t tr, unsigned ms)
+{
+ return c2s.head != c2s.tail ? 1 : 0;
+}
+
static ssize_t server_pull(gnutls_transport_ptr_t tr, void *b, size_t l)
{
return queue_get(&c2s, (gnutls_session_t)tr, b, l);
@@ -196,10 +201,124 @@ static void test(gnutls_push_func client_push)
gnutls_certificate_free_credentials(scred);
}
+static void test_malicious1816(void)
+{
+ /* dgram1: msg_len=50, frag_offset=25, frag_len=25 */
+ static const uint8_t dgram1_hdr[] = {
+ 0x16, /* type: handshake */
+ 0xfe, 0xfd, /* version: DTLS 1.2 */
+ 0x00, 0x00, /* epoch: 0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* seq: 0 */
+ 0x00, 0x25, /* record_length: 37 */
+ 0x01, /* msg_type: ClientHello */
+ 0x00, 0x00, 0x32, /* msg_length: 50 */
+ 0x00, 0x00, /* msg_seq: 0 */
+ 0x00, 0x00, 0x19, /* frag_offset: 25 */
+ 0x00, 0x00, 0x19, /* frag_length: 25 */
+ };
+ /* dgram2: msg_len=3000, frag_offset=0, frag_len=48 */
+ static const uint8_t dgram2_hdr[] = {
+ 0x16, /* type: handshake */
+ 0xfe, 0xfd, /* version: DTLS 1.2 */
+ 0x00, 0x00, /* epoch: 0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, /* seq: 1 */
+ 0x00, 0x3c, /* record_length: 60 */
+ 0x01, /* msg_type: ClientHello */
+ 0x00, 0x0b, 0xb8, /* msg_length: 3000 */
+ 0x00, 0x00, /* msg_seq: 0 */
+ 0x00, 0x00, 0x00, /* frag_offset: 0 */
+ 0x00, 0x00, 0x30, /* frag_length: 48 */
+ };
+ /* dgram3: msg_len=3000, frag_offset=40, frag_len=1475 */
+ static const uint8_t dgram3_hdr[] = {
+ 0x16, /* type: handshake */
+ 0xfe, 0xfd, /* version: DTLS 1.2 */
+ 0x00, 0x00, /* epoch: 0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, /* seq: 2 */
+ 0x05, 0xcf, /* record_length: 1487 */
+ 0x01, /* msg_type: ClientHello */
+ 0x00, 0x0b, 0xb8, /* msg_length: 3000 */
+ 0x00, 0x00, /* msg_seq: 0 */
+ 0x00, 0x00, 0x28, /* frag_offset: 40 */
+ 0x00, 0x05, 0xc3, /* frag_length: 1475 */
+ };
+ /* dgram4: msg_len=3000, frag_offset=1500, frag_len=1475 */
+ static const uint8_t dgram4_hdr[] = {
+ 0x16, /* type: handshake */
+ 0xfe, 0xfd, /* version: DTLS 1.2 */
+ 0x00, 0x00, /* epoch: 0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, /* seq: 3 */
+ 0x05, 0xcf, /* record_length: 1487 */
+ 0x01, /* msg_type: ClientHello */
+ 0x00, 0x0b, 0xb8, /* msg_length: 3000 */
+ 0x00, 0x00, /* msg_seq: 0 */
+ 0x00, 0x05, 0xdc, /* frag_offset: 1500 */
+ 0x00, 0x05, 0xc3, /* frag_length: 1475 */
+ };
+ gnutls_session_t server;
+ gnutls_certificate_credentials_t scred;
+ uint8_t dgram[1500];
+ int sr;
+
+ if (debug)
+ gnutls_global_set_log_level(4711);
+
+ gnutls_certificate_allocate_credentials(&scred);
+ gnutls_certificate_set_x509_key_mem(scred, &server_cert, &server_key,
+ GNUTLS_X509_FMT_PEM);
+
+ gnutls_init(&server, GNUTLS_SERVER | GNUTLS_DATAGRAM);
+ gnutls_priority_set_direct(server, "NORMAL:+VERS-DTLS1.2", NULL);
+ gnutls_credentials_set(server, GNUTLS_CRD_CERTIFICATE, scred);
+
+ gnutls_dtls_set_timeouts(server, 80 * 1000, 8 * 1000);
+
+ gnutls_transport_set_ptr(server, server);
+ gnutls_transport_set_push_function(server, server_push);
+ gnutls_transport_set_pull_function(server, server_pull);
+ gnutls_transport_set_pull_timeout_function(server,
+ c2s_pull_timeout_once);
+
+ memset(dgram, 0, sizeof(dgram));
+ memcpy(dgram, dgram1_hdr, 25);
+ queue_put(&c2s, dgram, 25 + 25);
+
+ memset(dgram, 0, sizeof(dgram));
+ memcpy(dgram, dgram2_hdr, 25);
+ queue_put(&c2s, dgram, 25 + 48);
+
+ memset(dgram, 0, sizeof(dgram));
+ memcpy(dgram, dgram3_hdr, 25);
+ queue_put(&c2s, dgram, 25 + 1475);
+
+ memset(dgram, 0, sizeof(dgram));
+ memcpy(dgram, dgram4_hdr, 25);
+ queue_put(&c2s, dgram, 25 + 1475);
+
+ gnutls_global_set_log_function(server_log_func);
+ do {
+ sr = gnutls_handshake(server); /* invalid write if vulnerable */
+ } while (c2s.head != c2s.tail && !gnutls_error_is_fatal(sr));
+ if (sr != GNUTLS_E_UNEXPECTED_PACKET_LENGTH)
+ fail("server: expected GNUTLS_E_UNEXPECTED_PACKET_LENGTH, "
+ "got: %s\n",
+ gnutls_strerror(sr));
+
+ success("OK\n");
+
+ queue_reset(&c2s);
+ queue_reset(&s2c);
+
+ gnutls_deinit(server);
+ gnutls_certificate_free_credentials(scred);
+}
+
void doit(void)
{
global_init();
test(client_push_normal);
+ success("malicious reassembly bug exploitation (#1816):\n");
+ test_malicious1816();
gnutls_global_deinit();
}
--
2.53.0
From bb427ff74dba849d40753ed9c8511e873f762743 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 20 Apr 2026 16:08:11 +0200
Subject: [PATCH 6/8] tests/mini-dtls-fragments: extend with fragmenting
ClientHello
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/mini-dtls-fragments.c | 107 ++++++++++++++++++++++++++++++++++++
1 file changed, 107 insertions(+)
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
index d997ad64f..a1fa7f8dd 100644
--- a/tests/mini-dtls-fragments.c
+++ b/tests/mini-dtls-fragments.c
@@ -132,6 +132,39 @@ static ssize_t client_push_normal(gnutls_transport_ptr_t tr, const void *b,
return queue_put(&c2s, b, l);
}
+static void write_u16(uint8_t *p, uint16_t val)
+{
+ p[0] = val >> 8;
+ p[1] = val & 0xff;
+}
+
+static void write_u24(uint8_t *p, uint32_t val)
+{
+ p[0] = (val >> 16) & 0xff;
+ p[1] = (val >> 8) & 0xff;
+ p[2] = val & 0xff;
+}
+
+static void write_u48(uint8_t *p, uint64_t seq)
+{
+ int i;
+ for (i = 5; i >= 0; i--) {
+ p[i] = seq & 0xff;
+ seq >>= 8;
+ }
+}
+
+static uint64_t read_u48(const uint8_t *p)
+{
+ uint64_t seq = 0;
+ int i;
+ for (i = 5; i >= 0; i--) {
+ seq <<= 8;
+ seq |= p[i];
+ }
+ return seq;
+}
+
static void test(gnutls_push_func client_push)
{
gnutls_session_t client, server;
@@ -313,12 +346,86 @@ static void test_malicious1816(void)
gnutls_certificate_free_credentials(scred);
}
+static ssize_t queue_put_renumbered(queue_t *q, const uint8_t *data, size_t l,
+ int delta_n)
+{
+ if (delta_n == 0 || l < 13 || data[3] != 0 || data[4] != 0)
+ return queue_put(&c2s, data, l);
+
+ uint8_t *p = malloc(l);
+ assert(p);
+ memcpy(p, data, l);
+ write_u48(p + 5, read_u48(p + 5) + delta_n);
+ ssize_t ret = queue_put(q, p, l);
+ free(p);
+ return ret;
+}
+
+static void split_client_hello(const uint8_t *data, size_t len, uint8_t **frag1,
+ size_t *frag1_len, uint8_t **frag2,
+ size_t *frag2_len)
+{
+ size_t body_size = len - 25;
+ *frag1_len = 13 + 12 + 1;
+ *frag2_len = 13 + 12 + (body_size - 1);
+
+ *frag1 = malloc(13 + 12 + 1);
+ assert(*frag1);
+ *frag2 = malloc(13 + 12 + body_size - 1);
+ assert(*frag2);
+
+ /* first fragment: record header + handshake header + first body byte */
+ memcpy(*frag1, data, 13); /* record header */
+ write_u16(*frag1 + 11, 12 + 1); /* record length */
+ memcpy(*frag1 + 13, data + 13, 12); /* handshake header */
+ write_u24(*frag1 + 19, 0); /* fragment_offset = 0 */
+ write_u24(*frag1 + 22, 1); /* fragment_length = 1 */
+ (*frag1)[25] = data[25]; /* first body byte */
+
+ /* second fragment: record header + handshake header + remaining body */
+ memcpy(*frag2, data, 13); /* record header */
+ write_u16(*frag2 + 11, *frag2_len - 13); /* record length */
+ write_u48(*frag2 + 5, read_u48(*frag2 + 5) + 1); /* sequence number */
+ memcpy(*frag2 + 13, data + 13, 12); /* handshake header */
+ write_u24(*frag2 + 19, 1); /* fragment_offset = 1 */
+ write_u24(*frag2 + 22, body_size - 1); /* shortened fragment_length */
+ memcpy(*frag2 + 25, data + 26, body_size - 1); /* remaining body */
+}
+
+static ssize_t client_push_split_hello(gnutls_transport_ptr_t tr, const void *b,
+ size_t l)
+{
+ static int seq_offset = 0; /* for renumbering follow-up epoch0 ones */
+
+ const uint8_t *data = (const uint8_t *)b;
+ uint8_t *frag1, *frag2;
+ size_t frag1_len, frag2_len;
+
+ /* Pass through anything that isn't an epoch0 ClientHello with body */
+ if (l < 13 + 12 + 1 || /* too short for DTLS record header */
+ data[0] != 22 || /* not a handshake record */
+ data[3] != 0 || data[4] != 0 || /* not epoch 0 */
+ data[13] != 1) /* not ClientHello */
+ return queue_put_renumbered(&c2s, b, l, seq_offset);
+
+ /* epoch0 Client Hello: special treatment of splitting into fragments */
+ split_client_hello(data, l, &frag1, &frag1_len, &frag2, &frag2_len);
+ queue_put(&c2s, frag1, frag1_len);
+ queue_put(&c2s, frag2, frag2_len);
+ free(frag1);
+ free(frag2);
+ seq_offset++;
+ return l;
+}
+
void doit(void)
{
global_init();
test(client_push_normal);
success("malicious reassembly bug exploitation (#1816):\n");
test_malicious1816();
+ success("split client hello smoke-test\n");
+ test(client_push_split_hello);
gnutls_global_deinit();
}
--
2.53.0
From 092c65d004e2f125f2fea3db84d801ac49a09f78 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 20 Apr 2026 16:32:02 +0200
Subject: [PATCH 7/8] buffers: match DTLS datagrams by sequence number
DTLS handshake fragment reassembly previously matched incoming fragments
by handshake type only, without checking the sequence number.
This allowed fragments from different handshake messages
to be merged into the same reassembly buffer.
Now sequence number is accounted for during reassembly,
ensuring fragments are only merged when they belong
to the same handshake message.
Reported-by: Zou Dikai
Fixes: #1839
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index 20ff909bd..48715adcc 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -996,7 +996,8 @@ static int merge_handshake_packet(gnutls_session_t session,
session->internals.handshake_recv_buffer;
for (i = 0; i < session->internals.handshake_recv_buffer_size; i++) {
- if (recv_buf[i].htype == hsk->htype) {
+ if (recv_buf[i].htype == hsk->htype &&
+ recv_buf[i].sequence == hsk->sequence) {
exists = 1;
pos = i;
break;
--
2.53.0
From a2b41be83a1a3529c551ccf54958da91a656550e Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 20 Apr 2026 16:36:08 +0200
Subject: [PATCH 8/8] tests/mini-dtls-fragments: #1839 mismatching message_seq
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/mini-dtls-fragments.c | 54 ++++++++++++++++++++++++++++++++-----
1 file changed, 47 insertions(+), 7 deletions(-)
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
index a1fa7f8dd..77daa6225 100644
--- a/tests/mini-dtls-fragments.c
+++ b/tests/mini-dtls-fragments.c
@@ -165,7 +165,7 @@ static uint64_t read_u48(const uint8_t *p)
return seq;
}
-static void test(gnutls_push_func client_push)
+static void test(gnutls_push_func client_push, bool expect_success)
{
gnutls_session_t client, server;
gnutls_certificate_credentials_t ccred, scred;
@@ -216,12 +216,22 @@ static void test(gnutls_push_func client_push)
sr = gnutls_handshake(server);
if (!sr || gnutls_error_is_fatal(sr))
sdone = true;
+
+ if (c2s.head == c2s.tail && s2c.head == s2c.tail)
+ break; /* speed the test up */
}
- if (cr)
- fail("client: %s\n", gnutls_strerror(cr));
- if (sr)
- fail("server: %s\n", gnutls_strerror(sr));
+ if (expect_success) {
+ if (cr)
+ fail("client: %s\n", gnutls_strerror(cr));
+ if (sr)
+ fail("server: %s\n", gnutls_strerror(sr));
+
+ } else {
+ if (cr == 0 && sr == 0)
+ fail("handshake unexpectedly succeeded: %s / %s\n",
+ gnutls_strerror(cr), gnutls_strerror(sr));
+ }
success("OK\n");
@@ -418,14 +428,44 @@ static ssize_t client_push_split_hello(gnutls_transport_ptr_t tr, const void *b,
return l;
}
+static ssize_t client_push_split_hello_bad_seq(gnutls_transport_ptr_t tr,
+ const void *b, size_t l)
+{
+ /* gnutls wasn't matching on message_seq on merging, see #1839 */
+ static int seq_offset = 0; /* for renumbering follow-up epoch0 ones */
+
+ const uint8_t *data = (const uint8_t *)b;
+ uint8_t *frag1, *frag2;
+ size_t frag1_len, frag2_len;
+
+ /* Pass through anything that isn't an epoch0 ClientHello with body */
+ if (l < 13 + 12 + 1 || /* too short for DTLS record header */
+ data[0] != 22 || /* not a handshake record */
+ data[3] != 0 || data[4] != 0 || /* not epoch 0 */
+ data[13] != 1) /* not ClientHello */
+ return queue_put_renumbered(&c2s, b, l, seq_offset);
+
+ /* epoch0 Client Hello: special treatment of splitting into fragments */
+ split_client_hello(data, l, &frag1, &frag1_len, &frag2, &frag2_len);
+ queue_put(&c2s, frag1, frag1_len);
+ frag2[18]++; /* WRONG, message_seq mismatch must be rejected, #1839 */
+ queue_put(&c2s, frag2, frag2_len);
+ free(frag1);
+ free(frag2);
+ seq_offset++;
+ return l;
+}
+
void doit(void)
{
global_init();
- test(client_push_normal);
+ test(client_push_normal, true);
success("malicious reassembly bug exploitation (#1816):\n");
test_malicious1816();
success("split client hello smoke-test\n");
- test(client_push_split_hello);
+ test(client_push_split_hello, true);
+ success("split client hello smoke-test and mangle sequence number\n");
+ test(client_push_split_hello_bad_seq, false);
gnutls_global_deinit();
}
--
2.53.0

View File

@ -1,166 +0,0 @@
From 19f6508647bdcd3ce21130201e484d7ca6d962c5 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 16 Mar 2026 15:29:40 +0100
Subject: [PATCH 1/2] x509/name-constraints: compare domain names
case-insensitive
RFC 5280 7.2:
> When comparing DNS names for equality, conforming implementations
> MUST perform a case-insensitive exact match on the entire DNS name.
> When evaluating name constraints, conforming implementations MUST
> perform a case-insensitive exact match on a label-by-label basis.
Domain name comparison during name constraints processing
was case-sensitive. For excluded name constraints, this could lead to
incorrectly accepting domain names that should've been rejected.
The code for comparing domain names and domain name parts of emails
has been modified to perform case-insensitive comparison instead.
Reported-by: Oleh Konko <security@1seal.org>
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1223
Fixes: #1803
Fixes: #1852
Fixes: CVE-2026-3833
Fixes: GNUTLS-SA-2026-04-29-5
CVSS: 7.4 High CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/name_constraints.c | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c
index 197fc47ac..a126123b1 100644
--- a/lib/x509/name_constraints.c
+++ b/lib/x509/name_constraints.c
@@ -35,6 +35,7 @@
#include <x509_int.h>
#include <x509_ext_int.h>
#include <libtasn1.h>
+#include "c-strcase.h"
#include "ip.h"
#include "ip-in-cidr.h"
@@ -80,7 +81,7 @@ enum name_constraint_relation {
NC_SORTS_AFTER = 2 /* unrelated constraints */
};
-/* A helper to compare just a pair of strings with this rich comparison */
+/* Helpers 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)
{
@@ -96,6 +97,22 @@ compare_strings(const void *n1, size_t n1_len, const void *n2, size_t n2_len)
return NC_EQUAL;
}
+static enum name_constraint_relation
+compare_strings_case_insensitive(const void *n1, size_t n1_len, const void *n2,
+ size_t n2_len)
+{
+ int r = c_strncasecmp(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,
@@ -121,8 +138,8 @@ static enum name_constraint_relation compare_dns_names(const gnutls_datum_t *n1,
while (j && n2->data[j - 1] != '.')
j--;
- rel = compare_strings(&n1->data[i], i_end - i, &n2->data[j],
- j_end - j);
+ rel = compare_strings_case_insensitive(&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 */
--
2.53.0
From 0fd5bfe0c777a4be30fb481c78c99d1052297f01 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 16 Mar 2026 15:48:57 +0100
Subject: [PATCH 2/2] tests/name-constraints: add case-sensitivity check
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/name-constraints.c | 52 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/tests/name-constraints.c b/tests/name-constraints.c
index 64e82ad35..7683da433 100644
--- a/tests/name-constraints.c
+++ b/tests/name-constraints.c
@@ -324,6 +324,58 @@ void doit(void)
gnutls_x509_name_constraints_deinit(nc);
+ /* 4b: case insensitivity */
+
+ ret = gnutls_x509_name_constraints_init(&nc);
+ check_for_error(ret);
+
+ set_name("example.net", &name);
+ ret = gnutls_x509_name_constraints_add_excluded(nc, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_for_error(ret);
+ set_name("email@example.net", &name);
+ ret = gnutls_x509_name_constraints_add_excluded(
+ nc, GNUTLS_SAN_RFC822NAME, &name);
+ check_for_error(ret);
+
+ set_name("example.org", &name); /* unrelated: accepted */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+ check_test_result(ret, NAME_ACCEPTED, &name);
+
+ set_name("example.net", &name); /* exact match: rejected */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+ check_test_result(ret, NAME_REJECTED, &name);
+
+ set_name("eXample.net", &name); /* case *insensitive*: rejected */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+ check_test_result(ret, NAME_REJECTED, &name);
+
+ set_name("mail@example.net", &name); /* unrelated: accepted */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+ &name);
+ check_test_result(ret, NAME_ACCEPTED, &name);
+
+ set_name("email@example.net", &name); /* exact match: rejected */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+ &name);
+ check_test_result(ret, NAME_REJECTED, &name);
+
+ set_name("eMail@example.net", &name); /* case *sensitive*: accepted */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+ &name);
+ check_test_result(ret, NAME_ACCEPTED, &name);
+
+ set_name("email@EXAMPLE.NET", &name); /* case *insensitive*: rejected */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_RFC822NAME,
+ &name);
+ check_test_result(ret, NAME_REJECTED, &name);
+
+ set_name("www.ExAmPlE.NeT", &name); /* case *insensitive*: inexact */
+ ret = gnutls_x509_name_constraints_check(nc, GNUTLS_SAN_DNSNAME, &name);
+ check_test_result(ret, NAME_REJECTED, &name);
+
+ gnutls_x509_name_constraints_deinit(nc);
+
// Test suite end.
if (debug)
--
2.53.0

View File

@ -1,95 +0,0 @@
From f01e21441e29052a6f0963840794c41d3b3ee66d Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 16:52:48 +0200
Subject: [PATCH 1/2] lib/buffers: ensure packets have differing sequence
numbers
There should normally be no packets with same sequence number and
differing handshake type, unless an adversary crafts them.
Discarding them allows to get rid of packets
with duplicate sequence ID in the buffer,
relieving us from the question of how to sort them later.
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1848
Fixes: CVE-2026-42009
Fixes: GNUTLS-SA-2026-04-29-2
CVSS: 7.5 High CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/buffers.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index 48715adcc..9f33821dd 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -996,8 +996,20 @@ static int merge_handshake_packet(gnutls_session_t session,
session->internals.handshake_recv_buffer;
for (i = 0; i < session->internals.handshake_recv_buffer_size; i++) {
- if (recv_buf[i].htype == hsk->htype &&
- recv_buf[i].sequence == hsk->sequence) {
+ if (recv_buf[i].sequence == hsk->sequence) {
+ if (recv_buf[i].htype != hsk->htype) {
+ _gnutls_audit_log(
+ session,
+ "Discarded unexpected handshake packet "
+ "with duplicate sequence %d, but "
+ "mismatched type %s (previously %s)\n",
+ hsk->sequence,
+ _gnutls_handshake2str(hsk->htype),
+ _gnutls_handshake2str(
+ recv_buf[i].htype));
+ _gnutls_handshake_buffer_clear(hsk);
+ return 0;
+ }
exists = 1;
pos = i;
break;
--
2.53.0
From f341441fad91142897d83b44a175ffc8f925b76f Mon Sep 17 00:00:00 2001
From: Joshua Rogers <joshua@joshua.hu>
Date: Tue, 21 Apr 2026 18:11:39 +0200
Subject: [PATCH 2/2] buffers: fix handshake_compare when sequence numbers
match
The comparator function used for ordering DTLS packets
by sequence numbers did not follow qsort comparator contracts
in case of packets with duplicate sequence numbers,
which could lead to unstable ordering or undefined behaviour.
Returning 0 in such cases makes the sorting stable.
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1848
Fixes: CVE-2026-42009
Fixes: GNUTLS-SA-2026-04-29-2
CVSS: 7.5 High CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Signed-off-by: Joshua Rogers <joshua@joshua.hu>
---
lib/buffers.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index 9f33821dd..be51f3aac 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -866,11 +866,7 @@ static int handshake_compare(const void *_e1, const void *_e2)
{
const handshake_buffer_st *e1 = _e1;
const handshake_buffer_st *e2 = _e2;
-
- if (e1->sequence <= e2->sequence)
- return 1;
- else
- return -1;
+ return (e1->sequence < e2->sequence) - (e1->sequence > e2->sequence);
}
#define SSL2_HEADERS 1
--
2.53.0

View File

@ -1,498 +0,0 @@
From 382afcb74f8cdabd2234374c730c33332f06c7b2 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 4 May 2026 10:08:34 +0000
Subject: [PATCH 1/6] tests/pskself2: reformat
---
tests/pskself2.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/tests/pskself2.c b/tests/pskself2.c
index 81286a035..c587df060 100644
--- a/tests/pskself2.c
+++ b/tests/pskself2.c
@@ -287,8 +287,7 @@ static void server(int sd, const char *prio)
success("server: finished\n");
}
-static
-void run_test(const char *prio, unsigned exp_hint)
+static void run_test(const char *prio, unsigned exp_hint)
{
pid_t child;
int err;
@@ -331,11 +330,19 @@ void doit(void)
run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", 1);
run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:+PSK", 0);
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK", 0);
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK", 0);
+ run_test(
+ "NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
+ 0);
+ run_test(
+ "NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
+ 0);
run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK", 0);
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK", 0);
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK", 0);
+ run_test(
+ "NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
+ 0);
+ run_test(
+ "NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
+ 0);
/* the following should work once we support PSK without DH */
run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+PSK", 0);
--
2.53.0
From e3ffd31846d1e6624338a26ca7fce7d1685b17cd Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:02:43 +0200
Subject: [PATCH 2/6] tests/pskself2: extend with RSA-PSK support
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/pskself2.c | 81 ++++++++++++++++++++++++++++++++----------------
1 file changed, 54 insertions(+), 27 deletions(-)
diff --git a/tests/pskself2.c b/tests/pskself2.c
index c587df060..974d48334 100644
--- a/tests/pskself2.c
+++ b/tests/pskself2.c
@@ -28,6 +28,7 @@
#include <config.h>
#endif
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -52,6 +53,7 @@ int main(int argc, char **argv)
#include "utils.h"
#include "extras/hex.h"
+#include "cert-common.h"
/* A very basic TLS client, with PSK authentication.
*/
@@ -66,12 +68,13 @@ static void tls_log_func(int level, const char *str)
#define MAX_BUF 1024
#define MSG "Hello TLS"
-static void client(int sd, const char *prio, unsigned exp_hint)
+static void client(int sd, const char *prio, bool exp_hint, bool rsa)
{
int ret, ii;
gnutls_session_t session;
char buffer[MAX_BUF + 1];
gnutls_psk_client_credentials_t pskcred;
+ gnutls_certificate_credentials_t xcred = NULL;
/* Need to enable anonymous KX specifically. */
const gnutls_datum_t key = { (void *) "DEADBEEF", 8 };
gnutls_datum_t user;
@@ -111,6 +114,11 @@ static void client(int sd, const char *prio, unsigned exp_hint)
*/
gnutls_credentials_set(session, GNUTLS_CRD_PSK, pskcred);
+ if (rsa) {
+ gnutls_certificate_allocate_credentials(&xcred);
+ gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
+ }
+
gnutls_transport_set_int(session, sd);
/* Perform the TLS handshake
@@ -166,6 +174,8 @@ static void client(int sd, const char *prio, unsigned exp_hint)
gnutls_free(user.data);
gnutls_psk_free_client_credentials(pskcred);
+ if (xcred)
+ gnutls_certificate_free_credentials(xcred);
gnutls_global_deinit();
}
@@ -195,9 +205,10 @@ pskfunc(gnutls_session_t session, const gnutls_datum_t *username,
}
-static void server(int sd, const char *prio)
+static void server(int sd, const char *prio, bool rsa)
{
gnutls_psk_server_credentials_t server_pskcred;
+ gnutls_certificate_credentials_t serverx509cred = NULL;
int ret;
gnutls_session_t session;
gnutls_datum_t psk_username;
@@ -217,6 +228,13 @@ static void server(int sd, const char *prio)
gnutls_psk_set_server_credentials_hint(server_pskcred, "hint");
gnutls_psk_set_server_credentials_function2(server_pskcred, pskfunc);
+ if (rsa) {
+ gnutls_certificate_allocate_credentials(&serverx509cred);
+ gnutls_certificate_set_x509_key_mem(serverx509cred,
+ &server_cert, &server_key,
+ GNUTLS_X509_FMT_PEM);
+ }
+
gnutls_init(&session, GNUTLS_SERVER);
/* avoid calling all the priority functions, since the defaults
@@ -225,6 +243,9 @@ static void server(int sd, const char *prio)
gnutls_priority_set_direct(session, prio, NULL);
gnutls_credentials_set(session, GNUTLS_CRD_PSK, server_pskcred);
+ if (serverx509cred)
+ gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
+ serverx509cred);
gnutls_transport_set_int(session, sd);
ret = gnutls_handshake(session);
@@ -280,6 +301,8 @@ static void server(int sd, const char *prio)
gnutls_deinit(session);
gnutls_psk_free_server_credentials(server_pskcred);
+ if (serverx509cred)
+ gnutls_certificate_free_credentials(serverx509cred);
gnutls_global_deinit();
@@ -287,7 +310,7 @@ static void server(int sd, const char *prio)
success("server: finished\n");
}
-static void run_test(const char *prio, unsigned exp_hint)
+static void run_test(const char *prio, bool exp_hint, bool rsa)
{
pid_t child;
int err;
@@ -313,42 +336,46 @@ static void run_test(const char *prio, unsigned exp_hint)
int status;
/* parent */
close(sockets[1]);
- server(sockets[0], prio);
+ server(sockets[0], prio, rsa);
wait(&status);
check_wait_status(status);
} else {
close(sockets[0]);
- client(sockets[1], prio, exp_hint);
+ client(sockets[1], prio, exp_hint, rsa);
exit(0);
}
}
void doit(void)
{
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", 1);
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", 1);
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", 1);
-
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:+PSK", 0);
- run_test(
- "NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
- 0);
- run_test(
- "NORMAL:-VERS-ALL:+VERS-TLS1.2:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
- 0);
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK", 0);
- run_test(
- "NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
- 0);
- run_test(
- "NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
- 0);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", true, false);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", true,
+ false);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", true, false);
+
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:+PSK", false, false);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:"
+ "-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
+ false, false);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:"
+ "-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
+ false, false);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK", false, false);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:"
+ "-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK",
+ false, false);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:"
+ "-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK",
+ false, false);
/* the following should work once we support PSK without DH */
- run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+PSK", 0);
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+PSK", false, false);
+
+ run_test("NORMAL:-KX-ALL:+PSK", false, false);
+ run_test("NORMAL:-KX-ALL:+ECDHE-PSK", false, false);
+ run_test("NORMAL:-KX-ALL:+DHE-PSK", false, false);
- run_test("NORMAL:-KX-ALL:+PSK", 0);
- run_test("NORMAL:-KX-ALL:+ECDHE-PSK", 0);
- run_test("NORMAL:-KX-ALL:+DHE-PSK", 0);
+ /* RSA-PSK */
+ run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+RSA-PSK", false, true);
}
#endif /* _WIN32 */
--
2.53.0
From cb1833afd9b6309563211b1c0a7c291f52ca98d5 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:26:10 +0200
Subject: [PATCH 3/6] lib/auth/rsa_psk: fix binary PSK identity lookup
A server looking up PSK username with a NUL-character in it
was wrongfully matching username truncated at a NUL-character.
Fix the check to compare up to the full username length.
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1850
Fixes: CVE-2026-42010
Fixes: GNUTLS-SA-2026-04-29-4
CVSS: 7.1 High CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/auth/rsa_psk.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/auth/rsa_psk.c b/lib/auth/rsa_psk.c
index 8f3fe5a4b..8813eeeec 100644
--- a/lib/auth/rsa_psk.c
+++ b/lib/auth/rsa_psk.c
@@ -332,7 +332,7 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
* filled in if the key is not found.
*/
ret =
- _gnutls_psk_pwd_find_entry(session, info->username, strlen(info->username), &pwd_psk);
+ _gnutls_psk_pwd_find_entry(session, info->username, info->username_len, &pwd_psk);
if (ret < 0)
return gnutls_assert_val(ret);
--
2.53.0
From cf20434d5cb8f3508e6ed2abdcb3e07bf28b9b6f Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:19:42 +0200
Subject: [PATCH 4/6] tests/pskself2: test username with NUL in the middle
(#1850)
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/pskself2.c | 31 +++++++++++++++++++++----------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/tests/pskself2.c b/tests/pskself2.c
index 974d48334..508711aa9 100644
--- a/tests/pskself2.c
+++ b/tests/pskself2.c
@@ -28,6 +28,7 @@
#include <config.h>
#endif
+#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -87,12 +87,15 @@ static void client(int sd, const char *prio, bool exp_hint, bool rsa)
side = "client";
- user.data = gnutls_malloc(4);
+ user.data = gnutls_malloc(5);
+ assert(user.data != NULL);
+
user.data[0] = 0xCA;
user.data[1] = 0xFE;
- user.data[2] = 0xCA;
- user.data[3] = 0xFE;
- user.size = 4;
+ user.data[2] = 0x00;
+ user.data[3] = 0xCA;
+ user.data[4] = 0xFE;
+ user.size = 5;
gnutls_psk_allocate_client_credentials(&pskcred);
ret = gnutls_psk_set_client_credentials2(pskcred, &user, &key,
@@ -191,14 +194,20 @@ static int
pskfunc(gnutls_session_t session, const gnutls_datum_t *username,
gnutls_datum_t * key)
{
+ const unsigned char expected_user[] = { 0xCA, 0xFE, 0x00, 0xCA, 0xFE };
+ const unsigned char expected_key[] = { 0xDE, 0xAD, 0xBE, 0xEF };
+
if (debug)
printf("psk: Got username with length %d\n", username->size);
+ /* verify callback received full 5-byte username (#1850) */
+ if (username->size != 5 ||
+ memcmp(username->data, expected_user, 5) != 0)
+ fail("pskfunc: username mismatch: got %u bytes, expected 5\n",
+ username->size);
+
key->data = gnutls_malloc(4);
- key->data[0] = 0xDE;
- key->data[1] = 0xAD;
- key->data[2] = 0xBE;
- key->data[3] = 0xEF;
+ memcpy(key->data, expected_key, 4);
key->size = 4;
return 0;
@@ -212,7 +221,8 @@ static void server(int sd, const char *prio, bool rsa)
int ret;
gnutls_session_t session;
gnutls_datum_t psk_username;
- char buffer[MAX_BUF + 1], expected_psk_username[] = { 0xDE, 0xAD, 0xBE, 0xEF };
+ char buffer[MAX_BUF + 1];
+ const char expected_psk_username[] = { 0xCA, 0xFE, 0x00, 0xCA, 0xFE };
/* this must be called once in the program
*/
@@ -265,7 +275,8 @@ static void server(int sd, const char *prio, bool rsa)
if (gnutls_psk_server_get_username2(session, &psk_username) < 0)
fail("server: Could not get PSK username\n");
- if (psk_username.size != 4 || memcmp(psk_username.data, expected_psk_username, 4))
+ if (psk_username.size != 5 ||
+ memcmp(psk_username.data, expected_psk_username, 5))
fail("server: Unexpected PSK username\n");
success("server: PSK username length: %d\n", psk_username.size);
--
2.53.0
From 0f8539fac736a2cdcc79ee4ea5a2f2590a6bea6b Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:49:47 +0200
Subject: [PATCH 5/6] tests/pskself2: sprinkle NUL into key for good measure
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/pskself2.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/tests/pskself2.c b/tests/pskself2.c
index 508711aa9..71c94cc1d 100644
--- a/tests/pskself2.c
+++ b/tests/pskself2.c
@@ -76,7 +76,7 @@ static void client(int sd, const char *prio, bool exp_hint, bool rsa)
gnutls_psk_client_credentials_t pskcred;
gnutls_certificate_credentials_t xcred = NULL;
/* Need to enable anonymous KX specifically. */
- const gnutls_datum_t key = { (void *) "DEADBEEF", 8 };
+ const gnutls_datum_t key = { (void *) "DEAD00BEEF", 10 };
gnutls_datum_t user;
const char *hint;
@@ -195,7 +195,7 @@ pskfunc(gnutls_session_t session, const gnutls_datum_t *username,
gnutls_datum_t * key)
{
const unsigned char expected_user[] = { 0xCA, 0xFE, 0x00, 0xCA, 0xFE };
- const unsigned char expected_key[] = { 0xDE, 0xAD, 0xBE, 0xEF };
+ const unsigned char expected_key[] = { 0xDE, 0xAD, 0x00, 0xBE, 0xEF };
if (debug)
printf("psk: Got username with length %d\n", username->size);
@@ -206,9 +206,9 @@ pskfunc(gnutls_session_t session, const gnutls_datum_t *username,
fail("pskfunc: username mismatch: got %u bytes, expected 5\n",
username->size);
- key->data = gnutls_malloc(4);
- memcpy(key->data, expected_key, 4);
- key->size = 4;
+ key->data = gnutls_malloc(5);
+ memcpy(key->data, expected_key, 5);
+ key->size = 5;
return 0;
}
--
2.53.0
From b10ac69270cd5ab4353efa62b92d9e04a5fec464 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 27 Apr 2026 17:16:25 +0200
Subject: [PATCH 6/6] lib/auth/psk_passwd: limit the length of the comparison
Comparing a long username from a password file
to a short username from the wire
could lead to a heap overread up to the difference in their lengths.
Fixes: #1864
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/auth/psk_passwd.c | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/lib/auth/psk_passwd.c b/lib/auth/psk_passwd.c
index 9a9d68c48..c87f2d68e 100644
--- a/lib/auth/psk_passwd.c
+++ b/lib/auth/psk_passwd.c
@@ -78,7 +78,7 @@ static int pwd_put_values(gnutls_datum_t * psk, char *str)
static bool username_matches(const gnutls_datum_t *username,
const char *line, size_t line_size)
{
- int retval;
+ bool retval;
unsigned i;
gnutls_datum_t hexline, hex_username = { NULL, 0 };
@@ -91,7 +91,7 @@ static bool username_matches(const gnutls_datum_t *username,
return false;
if (line_size == 0)
- return (username->size == 0);
+ return false;
/* move to first ':' */
i = 0;
@@ -100,6 +100,9 @@ static bool username_matches(const gnutls_datum_t *username,
i++;
}
+ if (line[i] != ':')
+ return false;
+
/* if format is in hex, e.g. #FAFAFA */
if (line[0] == '#' && line_size > 1) {
hexline.data = (void *) &line[1];
@@ -108,17 +111,17 @@ static bool username_matches(const gnutls_datum_t *username,
if ((retval = gnutls_hex_decode2(&hexline, &hex_username)) < 0)
return gnutls_assert_val(0);
- if (hex_username.size == username->size)
- retval = memcmp(username->data, hex_username.data, username->size);
- else
- retval = -1;
+ retval = hex_username.size == username->size &&
+ memcmp(username->data, hex_username.data,
+ username->size) == 0;
_gnutls_free_datum(&hex_username);
} else {
- retval = strncmp((const char *) username->data, line, MAX(i, username->size));
+ retval = i == username->size &&
+ strncmp((const char *) username->data, line, i) == 0;
}
- return (retval == 0);
+ return retval;
}
--
2.53.0

View File

@ -1,176 +0,0 @@
From 1dead2faec6320aaba321eb56f20d442df192b83 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 14 Apr 2026 17:41:30 +0200
Subject: [PATCH 1/2] x509/name_constraints: fix intersecting empty constraints
Permitted name constraints were wrongfully ignored
when prior CAs only had excluded name constraints,
resulting in a name constraint bypass.
With this change, they are taken into account and propagate.
Reported-by: Haruto Kimura (Stella)
Fixes: #1824
Fixes: CVE-2026-42011
Fixes: GNUTLS-SA-2026-04-29-6
CVSS: 4.8 Medium CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/name_constraints.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c
index a126123b1..5161f9926 100644
--- a/lib/x509/name_constraints.c
+++ b/lib/x509/name_constraints.c
@@ -717,9 +717,6 @@ static int name_constraints_node_list_intersect(
type_bitmask_t types_in_p1 = 0, types_in_p2 = 0;
static const unsigned char universal_ip[32] = { 0 };
- if (permitted->size == 0 || permitted2->size == 0)
- return GNUTLS_E_SUCCESS;
-
/* make sorted views of the arrays */
ret = ensure_sorted(permitted);
if (ret < 0) {
--
2.53.0
From 24713b8c63137ce0665b495d22ccce4f5ce05c84 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 14 Apr 2026 17:49:50 +0200
Subject: [PATCH 2/2] tests/name-constraints-merge: extend to cover #1824
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/name-constraints-merge.c | 113 +++++++++++++++++++++++++++++++++
1 file changed, 113 insertions(+)
diff --git a/tests/name-constraints-merge.c b/tests/name-constraints-merge.c
index 76430fb80..387395c6c 100644
--- a/tests/name-constraints-merge.c
+++ b/tests/name-constraints-merge.c
@@ -369,6 +369,119 @@ void doit(void)
gnutls_x509_name_constraints_deinit(nc1);
gnutls_x509_name_constraints_deinit(nc2);
+ /* 6: test intersecting empty permitted with non-empty permitted
+ * NC1: excluded DNS excluded.example.org (empty permitted)
+ * NC2: permitted DNS permitted.example.org
+ * Expected result:
+ * permitted=[permitted.example.org], excluded=[excluded.example.org]
+ * unrelated.example.com is rejected
+ */
+ suite = 6;
+
+ ret = gnutls_x509_name_constraints_init(&nc1);
+ check_for_error(ret);
+
+ ret = gnutls_x509_name_constraints_init(&nc2);
+ check_for_error(ret);
+
+ set_name("excluded.example.org", &name);
+ ret = gnutls_x509_name_constraints_add_excluded(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_for_error(ret);
+
+ set_name("permitted.example.org", &name);
+ ret = gnutls_x509_name_constraints_add_permitted(
+ nc2, GNUTLS_SAN_DNSNAME, &name);
+ check_for_error(ret);
+
+ ret = _gnutls_x509_name_constraints_merge(nc1, nc2);
+ check_for_error(ret);
+
+ set_name("unrelated.example.com", &name); /* entirely unrelated */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_REJECTED, &name); /* #1814 */
+
+ set_name("permitted.example.org", &name); /* permitted, direct */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_ACCEPTED, &name); /* sanity */
+
+ set_name("sub.permitted.example.org", &name); /* permitted, subdomain */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_ACCEPTED, &name); /* sanity */
+
+ set_name("excluded.example.org", &name); /* excluded, direct */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_REJECTED, &name); /* sanity */
+
+ set_name("sub.excluded.example.org", &name); /* excluded, subdomain */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_REJECTED, &name); /* sanity */
+
+ gnutls_x509_name_constraints_deinit(nc1);
+ gnutls_x509_name_constraints_deinit(nc2);
+
+ /* 7: test intersecting non-empty permitted with empty permitted
+ * (same as 6, but swapped to ensure order doesn't matter)
+ * NC1: permitted DNS permitted.example.org
+ * NC2: excluded DNS excluded.example.org (empty permitted)
+ * Expected result:
+ * permitted=[permitted.example.org], excluded=[excluded.example.org]
+ * unrelated.example.com is rejected
+ */
+ suite = 7;
+
+ ret = gnutls_x509_name_constraints_init(&nc1);
+ check_for_error(ret);
+
+ ret = gnutls_x509_name_constraints_init(&nc2);
+ check_for_error(ret);
+
+ set_name("permitted.example.org", &name);
+ ret = gnutls_x509_name_constraints_add_permitted(
+ nc1, GNUTLS_SAN_DNSNAME, &name);
+ check_for_error(ret);
+
+ set_name("excluded.example.org", &name);
+ ret = gnutls_x509_name_constraints_add_excluded(nc2, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_for_error(ret);
+
+ ret = _gnutls_x509_name_constraints_merge(nc1, nc2);
+ check_for_error(ret);
+
+ set_name("unrelated.example.com", &name); /* entirely unrelated */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_REJECTED, &name); /* #1814 */
+
+ set_name("permitted.example.org", &name); /* permitted, direct */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_ACCEPTED, &name); /* sanity */
+
+ set_name("sub.permitted.example.org", &name); /* permitted, subdomain */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_ACCEPTED, &name); /* sanity */
+
+ set_name("excluded.example.org", &name); /* excluded, direct */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_REJECTED, &name); /* sanity */
+
+ set_name("sub.excluded.example.org", &name); /* excluded, subdomain */
+ ret = gnutls_x509_name_constraints_check(nc1, GNUTLS_SAN_DNSNAME,
+ &name);
+ check_test_result(suite, ret, NAME_REJECTED, &name); /* sanity */
+
+ gnutls_x509_name_constraints_deinit(nc1);
+ gnutls_x509_name_constraints_deinit(nc2);
+
/* Test footer */
if (debug)
--
2.53.0

View File

@ -1,552 +0,0 @@
From fc909c3abddcc2955bebf0de403136ed9ec689c2 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 28 Apr 2026 15:26:32 +0200
Subject: [PATCH 1/6] x509/virt-san: a small OOM-correctness fix
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/virt-san.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/lib/x509/virt-san.c b/lib/x509/virt-san.c
index d2adc4e25..4bbfa1e0e 100644
--- a/lib/x509/virt-san.c
+++ b/lib/x509/virt-san.c
@@ -98,26 +98,27 @@ int _gnutls_alt_name_assign_virt_type(struct name_st *name, unsigned type, gnutl
if (ret < 0)
return gnutls_assert_val(ret);
- name->type = GNUTLS_SAN_OTHERNAME;
name->san.data = encoded.data;
name->san.size = encoded.size;
- name->othername_oid.data = (void*)gnutls_strdup(oid);
- name->othername_oid.size = strlen(oid);
break;
case GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL:
ret = _gnutls_krb5_principal_to_der((char*)san->data, &name->san);
if (ret < 0)
return gnutls_assert_val(ret);
-
- name->othername_oid.data = (void*)gnutls_strdup(oid);
- name->othername_oid.size = strlen(oid);
- name->type = GNUTLS_SAN_OTHERNAME;
break;
default:
return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
}
+ ret = _gnutls_set_strdatum(&name->othername_oid, oid,
+ strlen(oid));
+ if (ret < 0) {
+ gnutls_assert();
+ _gnutls_free_datum(&name->san);
+ return ret;
+ }
+ name->type = GNUTLS_SAN_OTHERNAME;
gnutls_free(san->data);
}
--
2.53.0
From 5cc003b9688378f6c7934b1df0aa147e80006be4 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 13 Mar 2026 17:41:33 +0100
Subject: [PATCH 2/6] x509: add bare-bones awareness of SRV virtual SAN
There's no support for constraints, no certtool support, no nothing.
Just added what's easy to add because I needed a virtual SAN for them.
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/includes/gnutls/gnutls.h.in | 4 +++-
lib/x509/common.h | 1 +
lib/x509/name_constraints.c | 3 ++-
lib/x509/output.c | 9 ++++++++-
lib/x509/virt-san.c | 25 +++++++++++++++++++++++++
lib/x509/x509.c | 6 ++++--
6 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index 264da238a..e5906617a 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -2692,6 +2692,7 @@ gnutls_psk_set_server_params_function(gnutls_psk_server_credentials_t
* @GNUTLS_SAN_REGISTERED_ID: RegisteredID.
* @GNUTLS_SAN_OTHERNAME_XMPP: Virtual SAN, used by certain functions for convenience.
* @GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL: Virtual SAN, used by certain functions for convenience.
+ * @GNUTLS_SAN_OTHERNAME_SRV: Virtual SAN, used by certain functions for convenience.
*
* Enumeration of different subject alternative names types.
*/
@@ -2708,7 +2709,8 @@ typedef enum gnutls_x509_subject_alt_name_t {
that they are represented by an otherName value and an OID.
Used by gnutls_x509_crt_get_subject_alt_othername_oid. */
GNUTLS_SAN_OTHERNAME_XMPP = 1000,
- GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL
+ GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL,
+ GNUTLS_SAN_OTHERNAME_SRV
} gnutls_x509_subject_alt_name_t;
struct gnutls_openpgp_crt_int;
diff --git a/lib/x509/common.h b/lib/x509/common.h
index 483bd1de6..37ed0b160 100644
--- a/lib/x509/common.h
+++ b/lib/x509/common.h
@@ -102,6 +102,7 @@
#define XMPP_OID "1.3.6.1.5.5.7.8.5"
#define KRB5_PRINCIPAL_OID "1.3.6.1.5.2.2"
+#define SRV_OID "1.3.6.1.5.5.7.8.7"
#define PKIX1_RSA_PSS_MGF1_OID "1.2.840.113549.1.1.8"
#define GOST28147_89_OID "1.2.643.2.2.21"
diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c
index 5161f9926..37e1c098e 100644
--- a/lib/x509/name_constraints.c
+++ b/lib/x509/name_constraints.c
@@ -520,7 +520,8 @@ static int validate_name_constraints_node(gnutls_x509_subject_alt_name_t type,
{
if (type != GNUTLS_SAN_DNSNAME && type != GNUTLS_SAN_RFC822NAME &&
type != GNUTLS_SAN_DN && type != GNUTLS_SAN_URI &&
- type != GNUTLS_SAN_IPADDRESS) {
+ type != GNUTLS_SAN_IPADDRESS &&
+ type != GNUTLS_SAN_OTHERNAME_SRV) {
return gnutls_assert_val(GNUTLS_E_X509_UNKNOWN_SAN);
}
diff --git a/lib/x509/output.c b/lib/x509/output.c
index 705e8babf..3c996186b 100644
--- a/lib/x509/output.c
+++ b/lib/x509/output.c
@@ -108,8 +108,10 @@ print_name(gnutls_buffer_st *str, const char *prefix, unsigned type, gnutls_datu
if ((type == GNUTLS_SAN_DNSNAME || type == GNUTLS_SAN_OTHERNAME_XMPP
|| type == GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL
+ || type == GNUTLS_SAN_OTHERNAME_SRV
|| type == GNUTLS_SAN_RFC822NAME
- || type == GNUTLS_SAN_URI) && sname != NULL && strlen(sname) != name->size) {
+ || type == GNUTLS_SAN_URI) && sname != NULL
+ && strlen(sname) != name->size) {
adds(str,
_("warning: SAN contains an embedded NUL, "
"replacing with '!'\n"));
@@ -156,6 +158,11 @@ print_name(gnutls_buffer_st *str, const char *prefix, unsigned type, gnutls_datu
addf(str, _("%sKRB5Principal: %.*s\n"), prefix, name->size, NON_NULL(name->data));
break;
+ case GNUTLS_SAN_OTHERNAME_SRV:
+ addf(str, _("%sSRVName: %.*s\n"), prefix, name->size,
+ NON_NULL(name->data));
+ break;
+
default:
addf(str, _("%sUnknown name: "), prefix);
_gnutls_buffer_hexprint(str, name->data, name->size);
diff --git a/lib/x509/virt-san.c b/lib/x509/virt-san.c
index 4bbfa1e0e..a59da4299 100644
--- a/lib/x509/virt-san.c
+++ b/lib/x509/virt-san.c
@@ -40,6 +40,9 @@ int san_othername_to_virtual(const char *oid, size_t size)
else if ((unsigned) size == (sizeof(KRB5_PRINCIPAL_OID)-1)
&& memcmp(oid, KRB5_PRINCIPAL_OID, sizeof(KRB5_PRINCIPAL_OID)-1) == 0)
return GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL;
+ else if ((unsigned)size == (sizeof(SRV_OID) - 1) &&
+ memcmp(oid, SRV_OID, sizeof(SRV_OID) - 1) == 0)
+ return GNUTLS_SAN_OTHERNAME_SRV;
}
return GNUTLS_SAN_OTHERNAME;
@@ -53,6 +56,8 @@ const char * virtual_to_othername_oid(unsigned type)
return XMPP_OID;
case GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL:
return KRB5_PRINCIPAL_OID;
+ case GNUTLS_SAN_OTHERNAME_SRV:
+ return SRV_OID;
default:
return NULL;
}
@@ -108,6 +113,17 @@ int _gnutls_alt_name_assign_virt_type(struct name_st *name, unsigned type, gnutl
return gnutls_assert_val(ret);
break;
+ case GNUTLS_SAN_OTHERNAME_SRV:
+ ret = _gnutls_x509_encode_string(
+ ASN1_ETYPE_IA5_STRING,
+ san->data, san->size,
+ &encoded);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+ name->san.data = encoded.data;
+ name->san.size = encoded.size;
+ break;
+
default:
return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
}
@@ -172,6 +188,15 @@ int gnutls_x509_othername_to_virtual(const char *oid,
return ret;
}
return 0;
+ case GNUTLS_SAN_OTHERNAME_SRV:
+ ret = _gnutls_x509_decode_string
+ (ASN1_ETYPE_IA5_STRING, othername->data,
+ othername->size, virt, 0);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+ return 0;
default:
return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
}
diff --git a/lib/x509/x509.c b/lib/x509/x509.c
index c713f857a..877b88b26 100644
--- a/lib/x509/x509.c
+++ b/lib/x509/x509.c
@@ -1382,7 +1382,8 @@ inline static int is_type_printable(int type)
{
if (type == GNUTLS_SAN_DNSNAME || type == GNUTLS_SAN_RFC822NAME ||
type == GNUTLS_SAN_URI || type == GNUTLS_SAN_OTHERNAME_XMPP ||
- type == GNUTLS_SAN_OTHERNAME || type == GNUTLS_SAN_REGISTERED_ID)
+ type == GNUTLS_SAN_OTHERNAME_SRV || type == GNUTLS_SAN_OTHERNAME ||
+ type == GNUTLS_SAN_REGISTERED_ID)
return 1;
else
return 0;
@@ -1855,7 +1856,8 @@ get_alt_name(gnutls_subject_alt_names_t san,
goto cleanup;
}
- if (othername_oid && type == GNUTLS_SAN_OTHERNAME && ooid.data) {
+ /* API uses othername_oid=0; map to virtual types regardless */
+ if (type == GNUTLS_SAN_OTHERNAME && ooid.data) {
unsigned vtype;
ret = gnutls_x509_othername_to_virtual((char*)ooid.data, &oname, &vtype, &virt);
if (ret >= 0) {
--
2.53.0
From 5a21e1e175f6c853ab3ee39a4d2d9adfb80e3731 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 4 May 2026 10:53:26 +0000
Subject: [PATCH 3/6] x509/hostname-verify: use memchr for embedded-null check
_gnutls_has_embedded_null uses strlen, which reads past the buffer if
there is no NUL within the first size bytes. memchr(p, '\0', size) is
the bounded equivalent.
---
lib/x509/hostname-verify.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/x509/hostname-verify.c b/lib/x509/hostname-verify.c
index 6ef8ba030..15d548661 100644
--- a/lib/x509/hostname-verify.c
+++ b/lib/x509/hostname-verify.c
@@ -220,7 +220,7 @@ gnutls_x509_crt_check_hostname2(gnutls_x509_crt_t cert,
if (ret == GNUTLS_SAN_DNSNAME) {
found_dnsname = 1;
- if (_gnutls_has_embedded_null(dnsname, dnsnamesize)) {
+ if (memchr(dnsname, '\0', dnsnamesize)) {
_gnutls_debug_log("certificate has %s with embedded null in name\n", dnsname);
continue;
}
--
2.53.0
From 6133fb459b74a9dcfa2d0ff010a4e03c56822d39 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 13 Mar 2026 17:00:03 +0100
Subject: [PATCH 3/6] x509/hostname-verify: refactor and simplify CN fallback
logic
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/hostname-verify.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/lib/x509/hostname-verify.c b/lib/x509/hostname-verify.c
index 6ef8ba030..698356f32 100644
--- a/lib/x509/hostname-verify.c
+++ b/lib/x509/hostname-verify.c
@@ -112,7 +112,8 @@ gnutls_x509_crt_check_ip(gnutls_x509_crt_t cert,
* that we do not fallback to CN-ID if we encounter a supported name
* type.
*/
-#define IS_SAN_SUPPORTED(san) (san==GNUTLS_SAN_DNSNAME||san==GNUTLS_SAN_IPADDRESS)
+#define PRECLUDES_CN_FALLBACK(san) \
+ (san == GNUTLS_SAN_DNSNAME || san == GNUTLS_SAN_IPADDRESS)
/**
* gnutls_x509_crt_check_hostname2:
@@ -154,13 +155,12 @@ gnutls_x509_crt_check_hostname2(gnutls_x509_crt_t cert,
{
char dnsname[MAX_CN];
size_t dnsnamesize;
- int found_dnsname = 0;
int ret = 0;
int i = 0;
struct in_addr ipv4;
char *p = NULL;
char *a_hostname;
- unsigned have_other_addresses = 0;
+ bool cn_fallback_allowed = true;
gnutls_datum_t out;
/* check whether @hostname is an ip address */
@@ -217,9 +217,10 @@ gnutls_x509_crt_check_hostname2(gnutls_x509_crt_t cert,
&dnsnamesize,
NULL);
- if (ret == GNUTLS_SAN_DNSNAME) {
- found_dnsname = 1;
+ if (PRECLUDES_CN_FALLBACK(ret))
+ cn_fallback_allowed = false;
+ if (ret == GNUTLS_SAN_DNSNAME) {
if (memchr(dnsname, '\0', dnsnamesize)) {
_gnutls_debug_log("certificate has %s with embedded null in name\n", dnsname);
continue;
@@ -235,13 +236,11 @@ gnutls_x509_crt_check_hostname2(gnutls_x509_crt_t cert,
ret = 1;
goto cleanup;
}
- } else {
- if (IS_SAN_SUPPORTED(ret))
- have_other_addresses = 1;
}
}
- if (!have_other_addresses && !found_dnsname && _gnutls_check_key_purpose(cert, GNUTLS_KP_TLS_WWW_SERVER, 0) != 0) {
+ if (cn_fallback_allowed &&
+ _gnutls_check_key_purpose(cert, GNUTLS_KP_TLS_WWW_SERVER, 0) != 0) {
/* did not get the necessary extension, use CN instead, if the
* certificate would have been acceptable for a TLS WWW server purpose.
* That is because only for that purpose the CN is a valid field to
--
2.53.0
From 8dcc6a1f48945997666ac9f10896819edd01a03b Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 13 Mar 2026 17:02:07 +0100
Subject: [PATCH 5/6] x509/hostname-verify: make URI/SRV SAN preclude CN
fallback
URI/SRV SAN did not suppress CN fallback as required by RFC 6125 6.4.4:
> a client MUST NOT seek a match for a reference identifier of CN-ID
> if the presented identifiers include a DNS-ID, *SRV-ID*, *URI-ID*,
> or any application-specific identifier types supported by the client.
With this change, certificates containing URI or SRV SAN
no longer pass DNS hostname checks via CN fallback
to avoid potential misuse of such certificates
beyond their original purpose.
Reported-by: Oleh Konko <security@1seal.org>
Fixes: #1802
Fixes: CVE-2026-42012
Fixes: GNUTLS-SA-2026-04-29-7
CVSS: 6.5 Medium CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/hostname-verify.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/x509/hostname-verify.c b/lib/x509/hostname-verify.c
index eb0fddaa8..d46fd965f 100644
--- a/lib/x509/hostname-verify.c
+++ b/lib/x509/hostname-verify.c
@@ -112,8 +112,9 @@ gnutls_x509_crt_check_ip(gnutls_x509_crt_t cert,
* that we do not fallback to CN-ID if we encounter a supported name
* type.
*/
-#define PRECLUDES_CN_FALLBACK(san) \
- (san == GNUTLS_SAN_DNSNAME || san == GNUTLS_SAN_IPADDRESS)
+#define PRECLUDES_CN_FALLBACK(san) \
+ (san == GNUTLS_SAN_DNSNAME || san == GNUTLS_SAN_IPADDRESS || \
+ san == GNUTLS_SAN_URI || san == GNUTLS_SAN_OTHERNAME_SRV)
/**
* gnutls_x509_crt_check_hostname2:
--
2.53.0
From b39429d77d4ba022f8597c99b84bbd0a073c815b Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Fri, 13 Mar 2026 17:54:56 +0100
Subject: [PATCH 6/6] tests/hostname-check: extend to exercise no-CN-fallback
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/hostname-check.c | 141 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 141 insertions(+)
diff --git a/tests/hostname-check.c b/tests/hostname-check.c
index 47f8d355d..71481cafb 100644
--- a/tests/hostname-check.c
+++ b/tests/hostname-check.c
@@ -831,6 +831,99 @@ char txt_ip_in_cn[] =
"f0+Un2eHAxFcRZPWdPy1/mn83NUMnjquuA/HHcju+pcoZrEwAI3PPQHgsGQ=\n"
"-----END CERTIFICATE-----\n";
+char dns_uri_and_cn[] =
+ "organization = GnuTLS test\n"
+ "cn = example.org\n"
+ "expiration_days = 365\n"
+ "tls_www_server\n"
+ "dns_name = alt.example.org\n"
+ "uri = http://example.org/\n"
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIEWzCCAsOgAwIBAgIUZ2rP89A5RbTdVMCQ86E9qWLSZz4wDQYJKoZIhvcNAQEL\n"
+ "BQAwLDEUMBIGA1UEChMLR251VExTIHRlc3QxFDASBgNVBAMTC2V4YW1wbGUub3Jn\n"
+ "MB4XDTI2MDMxMzE1NDIwNVoXDTI3MDMxMzE1NDIwNVowLDEUMBIGA1UEChMLR251\n"
+ "VExTIHRlc3QxFDASBgNVBAMTC2V4YW1wbGUub3JnMIIBojANBgkqhkiG9w0BAQEF\n"
+ "AAOCAY8AMIIBigKCAYEArEgcC7WDqNJCpO0J3UfCraRWzmwk7soTg7pMmCv1FHWP\n"
+ "ywxfXxbl0jvRK/Owyv7q5QRdfcxSYhxydrloCOdWXhGQejgzcpgLu/Y3Ij0CjIzr\n"
+ "C38vMSS7yUjAuzB4IYtZsgyuB+bCfGH2Y227ntuniIjEnuekA18gfFemRUQ8PDao\n"
+ "EzNAPn8Q3cFlHh5Kr3gkDUduQlRhZdK0ryU/XaAUxz9G9TminIKCl4h1bKNT2pXN\n"
+ "08Cg3eOEi3Bl8lCCA7ufODl5frfrDcjNkxqRKN99zMa9A9hcRzYNF9/asX+BXFyh\n"
+ "7qbypKXAHBi6xi055+CRIBTxFC960qAluv2cOlo1tzZ0Nta75GhWCqLaVZgI0D0n\n"
+ "6SPZPEknVjGPQx1vSnq3ZNdNDdG83yw4QjFtujXdKrflcJafCg3D6LCvEyDPBdYC\n"
+ "reTJ9xWNCdsxdzlTdDk11CbxdIz4jA7qA/forbY9Dv7l0iC9d2zWmn4DOv23QIki\n"
+ "Uw46/ymnpeFaUTyNyyNHAgMBAAGjdTBzMAwGA1UdEwEB/wQCMAAwLwYDVR0RBCgw\n"
+ "JoIPYWx0LmV4YW1wbGUub3JnhhNodHRwOi8vZXhhbXBsZS5vcmcvMBMGA1UdJQQM\n"
+ "MAoGCCsGAQUFBwMBMB0GA1UdDgQWBBSXBONihYHHlbM9mrezDF7o607tFTANBgkq\n"
+ "hkiG9w0BAQsFAAOCAYEAHk3bGPAl8YvQE84KZnCnVWBfayFeHKXlN/o/MvpYtPb2\n"
+ "y9cnD8IMmruW3A/UL+md2xx24V+pQWmugB2e879N/Q3QVsSbHFlzPei4tieK5VVd\n"
+ "gLC2iG7N8YjQ0SNRDF22A1QQDcVzdCXOggivs4MelF1zaGfY3ywHOhiHXt0jDj1o\n"
+ "2bP5OflElDFVF7m38RDwdeGokb+raW/2lOJZe4oKpdmllyUtLvrQhdwpogwnbpvH\n"
+ "7ln5Tq4wDNIcxM+Y4MQwe6m0AEELdFZjBmfsZthmaGrbppLTbp14rzC6kKqZ9ay/\n"
+ "zG06DhPalDCR+Bqvmh2Qp25xgqThv9AX8JQU6W8avnkfyxFZwBEJZ0lNoOyLZV2Z\n"
+ "4vmflZyOih9ccGUaYXWXzyc+vxNZjFQwjWNss2vynVvp4+5DUeMWeAj/unvjrxy2\n"
+ "HOsI4FrD94g5PchhXyKXRmeJk4mcr0jtE5ycbmiDU3sXz6xM7hsdBeNFyr47L2nj\n"
+ "OfWEK8ArBc3uTEgp2tIA\n"
+ "-----END CERTIFICATE-----\n";
+
+char uri_and_cn[] =
+ "organization = GnuTLS test\n"
+ "expiration_days = 365\n"
+ "tls_www_server\n"
+ "uri = http://example.org/\n"
+ "cn = example.org\n"
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIESjCCArKgAwIBAgIUNC/WOkhQZc6stg6RSrVPaUHRmLwwDQYJKoZIhvcNAQEL\n"
+ "BQAwLDEUMBIGA1UEChMLR251VExTIHRlc3QxFDASBgNVBAMTC2V4YW1wbGUub3Jn\n"
+ "MB4XDTI2MDMxMzE1NTAwNVoXDTI3MDMxMzE1NTAwNVowLDEUMBIGA1UEChMLR251\n"
+ "VExTIHRlc3QxFDASBgNVBAMTC2V4YW1wbGUub3JnMIIBojANBgkqhkiG9w0BAQEF\n"
+ "AAOCAY8AMIIBigKCAYEArEgcC7WDqNJCpO0J3UfCraRWzmwk7soTg7pMmCv1FHWP\n"
+ "ywxfXxbl0jvRK/Owyv7q5QRdfcxSYhxydrloCOdWXhGQejgzcpgLu/Y3Ij0CjIzr\n"
+ "C38vMSS7yUjAuzB4IYtZsgyuB+bCfGH2Y227ntuniIjEnuekA18gfFemRUQ8PDao\n"
+ "EzNAPn8Q3cFlHh5Kr3gkDUduQlRhZdK0ryU/XaAUxz9G9TminIKCl4h1bKNT2pXN\n"
+ "08Cg3eOEi3Bl8lCCA7ufODl5frfrDcjNkxqRKN99zMa9A9hcRzYNF9/asX+BXFyh\n"
+ "7qbypKXAHBi6xi055+CRIBTxFC960qAluv2cOlo1tzZ0Nta75GhWCqLaVZgI0D0n\n"
+ "6SPZPEknVjGPQx1vSnq3ZNdNDdG83yw4QjFtujXdKrflcJafCg3D6LCvEyDPBdYC\n"
+ "reTJ9xWNCdsxdzlTdDk11CbxdIz4jA7qA/forbY9Dv7l0iC9d2zWmn4DOv23QIki\n"
+ "Uw46/ymnpeFaUTyNyyNHAgMBAAGjZDBiMAwGA1UdEwEB/wQCMAAwHgYDVR0RBBcw\n"
+ "FYYTaHR0cDovL2V4YW1wbGUub3JnLzATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNV\n"
+ "HQ4EFgQUlwTjYoWBx5WzPZq3swxe6OtO7RUwDQYJKoZIhvcNAQELBQADggGBAE6w\n"
+ "uGfQy1pi+VbvHFc64QZJhf6r0FQl8Y5kWPu7OI2o+M5/FmY9hXmXxJzAfGH3ecE8\n"
+ "PL/bnR9zRCHTi1ONogukPKPmm/x3AQehn54hvgjZXGFOMxGBB5wSbsEzjCQxgKOO\n"
+ "uzUKZ0zgJin5YEi9g3DGKYi1qDNceNB9LjsWq372FKze0y2zZT7U2xiQcXlKgIZ6\n"
+ "KEcRBQVDygKNeU8ux0Q+lSaymsT9dhs6uahmGUTbbLcsKxsPhJjfC3IWTH+vK3tV\n"
+ "yjjnHcfcITAYSYHOM8+2+5EMOCZmGxCqv3unDkJRYY2xrp3+kXyGXXKRw+yNs4MT\n"
+ "Zc9zymseS+rB+9SDYO4DHDIV+jMJPMcqjJSlglMhs53Z4HFuWcuYJ6FzbRyM7hky\n"
+ "X4El+DWVaajh10QZApiWnRTTafJzJTbYljbpdZVgDX6chAyQRTKj6Di7YrrXmlYZ\n"
+ "iPKiHBqRUnCnxe8HYoBeK5Dw1lzCmgqXp5wjRqo5UHaemgZQTjdlPiWeovEiVg==\n"
+ "-----END CERTIFICATE-----\n";
+
+char srv_and_cn[] =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIESjCCArKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAsMRQwEgYDVQQKEwtHbnVU\n"
+ "TFMgdGVzdDEUMBIGA1UEAxMLZXhhbXBsZS5vcmcwHhcNMjYwMzEzMTY0OTU5WhcN\n"
+ "MjcwMzEzMTY0OTU5WjAsMRQwEgYDVQQKEwtHbnVUTFMgdGVzdDEUMBIGA1UEAxML\n"
+ "ZXhhbXBsZS5vcmcwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCsSBwL\n"
+ "tYOo0kKk7QndR8KtpFbObCTuyhODukyYK/UUdY/LDF9fFuXSO9Er87DK/urlBF19\n"
+ "zFJiHHJ2uWgI51ZeEZB6ODNymAu79jciPQKMjOsLfy8xJLvJSMC7MHghi1myDK4H\n"
+ "5sJ8YfZjbbue26eIiMSe56QDXyB8V6ZFRDw8NqgTM0A+fxDdwWUeHkqveCQNR25C\n"
+ "VGFl0rSvJT9doBTHP0b1OaKcgoKXiHVso1Palc3TwKDd44SLcGXyUIIDu584OXl+\n"
+ "t+sNyM2TGpEo333Mxr0D2FxHNg0X39qxf4FcXKHupvKkpcAcGLrGLTnn4JEgFPEU\n"
+ "L3rSoCW6/Zw6WjW3NnQ21rvkaFYKotpVmAjQPSfpI9k8SSdWMY9DHW9Kerdk100N\n"
+ "0bzfLDhCMW26Nd0qt+Vwlp8KDcPosK8TIM8F1gKt5Mn3FY0J2zF3OVN0OTXUJvF0\n"
+ "jPiMDuoD9+ittj0O/uXSIL13bNaafgM6/bdAiSJTDjr/Kael4VpRPI3LI0cCAwEA\n"
+ "AaN3MHUwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNVHQ4E\n"
+ "FgQUlwTjYoWBx5WzPZq3swxe6OtO7RUwMQYDVR0RBCowKKAmBggrBgEFBQcIB6Aa\n"
+ "FhhfeG1wcC1jbGllbnQuZXhhbXBsZS5vcmcwDQYJKoZIhvcNAQELBQADggGBAI0B\n"
+ "WAylO7fWCLtWJqfMrHa3JFH2rkQRf5WV+Z5JqcxlP47m6220+xBiV/iIZT0V6Un/\n"
+ "Z/4Je+jrx6vFIAxtTR1gmyfLo8TfzkEmwPT+uHb16RFkJbi5ik47Mm+31VOrq2G/\n"
+ "s1FP0oNCG6LAnVN1a3Np5uorbUJlTkrd9MxymIvbZMQ56pZvI8TeNgescxD3xY96\n"
+ "/yQXFBy3wu8PIJblG/7r5vPSY8BrEqpRZ0Dch4EoU2RVybE0vXUUTL2moripnHzF\n"
+ "4+mm4Fa8mNb6nII8RjmFDJJzKCQlpmm4R8iNaCvULv8jVO899XUwDSL8+hHt6jZK\n"
+ "aU0pWVCorxpUmgQiQmBAIYvreSM4nKtljSwT2+SBaMna+MaZk2vfBDO15tZH0LWW\n"
+ "OYpnqQQkihP9my4jESvn8FE4NtF5x44XuJVKTVSas1o49XLXq/94fT4DZGa6rdSx\n"
+ "p9Nnj64WFIqbTLoqM3nt7+zqFZDvwh+8ZEVcE1MazHOYhDQj1uU3jqIq/sZE8w==\n"
+ "-----END CERTIFICATE-----\n";
+
void doit(void)
{
@@ -1214,6 +1307,54 @@ void doit(void)
gnutls_openpgp_crt_deinit(pgp);
#endif
+
+ if (debug)
+ success("Testing not falling back to CN with DNS+URI SAN...\n");
+ data.data = (unsigned char *)dns_uri_and_cn;
+ data.size = strlen(dns_uri_and_cn);
+
+ ret = gnutls_x509_crt_import(x509, &data, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ fail("%d: gnutls_x509_crt_import: %d\n", __LINE__, ret);
+
+ ret = gnutls_x509_crt_check_hostname(x509, "example.org");
+ if (ret)
+ fail("%d: Hostname incorrectly falls back to CN (%d)\n",
+ __LINE__, ret);
+
+ ret = gnutls_x509_crt_check_hostname(x509, "alt.example.org");
+ if (!ret)
+ fail("%d: Hostname does not match a valid DNS SAN (%d)\n",
+ __LINE__, ret);
+
+ if (debug)
+ success("Testing not falling back to CN with URI SAN...\n");
+ data.data = (unsigned char *)uri_and_cn;
+ data.size = strlen(uri_and_cn);
+
+ ret = gnutls_x509_crt_import(x509, &data, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ fail("%d: gnutls_x509_crt_import: %d\n", __LINE__, ret);
+
+ ret = gnutls_x509_crt_check_hostname(x509, "example.org");
+ if (ret)
+ fail("%d: Hostname incorrectly falls back to CN (%d)\n",
+ __LINE__, ret);
+
+ if (debug)
+ success("Testing not falling back to CN with SRV SAN...\n");
+ data.data = (unsigned char *)srv_and_cn;
+ data.size = strlen(srv_and_cn);
+
+ ret = gnutls_x509_crt_import(x509, &data, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ fail("%d: gnutls_x509_crt_import: %d\n", __LINE__, ret);
+
+ ret = gnutls_x509_crt_check_hostname(x509, "example.org");
+ if (ret)
+ fail("%d: Hostname incorrectly falls back to CN (%d)\n",
+ __LINE__, ret);
+
gnutls_x509_crt_deinit(x509);
gnutls_global_deinit();
--
2.53.0

View File

@ -1,244 +0,0 @@
From 3ee2cb707002f755e4bda3f75285caa0cb36c214 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 15 Apr 2026 15:35:59 +0200
Subject: [PATCH 1/3] x509/email-verify: call fallback DN fallback
A comment was inaccurately referring to DN email field fallback
as CN fallback.
Rename a few things as well to match x509/hostname-verify more closely.
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/email-verify.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/lib/x509/email-verify.c b/lib/x509/email-verify.c
index 053e51287..0d55e5524 100644
--- a/lib/x509/email-verify.c
+++ b/lib/x509/email-verify.c
@@ -43,7 +43,7 @@ gnutls_x509_crt_check_email(gnutls_x509_crt_t cert,
{
char rfc822name[MAX_CN];
size_t rfc822namesize;
- int found_rfc822name = 0;
+ bool dn_fallback_allowed = true;
int ret = 0;
int i = 0;
char *a_email;
@@ -79,7 +79,7 @@ gnutls_x509_crt_check_email(gnutls_x509_crt_t cert,
NULL);
if (ret == GNUTLS_SAN_RFC822NAME) {
- found_rfc822name = 1;
+ dn_fallback_allowed = false;
if (_gnutls_has_embedded_null(rfc822name, rfc822namesize)) {
_gnutls_debug_log("certificate has %s with embedded null in rfc822name\n", rfc822name);
@@ -99,12 +99,10 @@ gnutls_x509_crt_check_email(gnutls_x509_crt_t cert,
}
}
- if (!found_rfc822name) {
- /* did not get the necessary extension, use CN instead
- */
+ if (dn_fallback_allowed) {
+ /* did not get the necessary extension, use DN email instead */
- /* enforce the RFC6125 (§1.8) requirement that only
- * a single CN must be present */
+ /* only a single one must be present */
rfc822namesize = sizeof(rfc822name);
ret = gnutls_x509_crt_get_dn_by_oid
(cert, GNUTLS_OID_PKCS9_EMAIL, 1, 0, rfc822name,
--
2.53.0
From 29801bef00ecc0f23c0bac4cd333b269cd2c1af4 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 15 Apr 2026 16:02:19 +0200
Subject: [PATCH 2/3] x509: prevent fallback on oversized SAN
Passing oversized SAN did not preclude CN (or DN email) fallback
during verification, which is an RFC 6125 6.4.4 violation.
Now oversized SAN are skipped over,
but prevent the fallback from happening.
Reported-by: Haruto Kimura (Stella)
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1825
Fixes: #1849
Fixes: CVE-2026-42013
Fixes: GNUTLS-SA-2026-04-27-8
CVSS: 6.5 Moderate CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/email-verify.c | 14 ++++++++++++++
lib/x509/hostname-verify.c | 14 ++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/lib/x509/email-verify.c b/lib/x509/email-verify.c
index 0d55e5524..f755d766e 100644
--- a/lib/x509/email-verify.c
+++ b/lib/x509/email-verify.c
@@ -78,6 +78,20 @@ gnutls_x509_crt_check_email(gnutls_x509_crt_t cert,
&rfc822namesize,
NULL);
+ if (ret < 0) {
+ if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ /* oversized SAN; proceed without DN fallback */
+ _gnutls_debug_log("oversized SAN ignored, "
+ "disabling DN fallback\n");
+ dn_fallback_allowed = false;
+ ret = 0;
+ continue;
+ }
+ if (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ gnutls_assert();
+ break;
+ }
+
if (ret == GNUTLS_SAN_RFC822NAME) {
dn_fallback_allowed = false;
diff --git a/lib/x509/hostname-verify.c b/lib/x509/hostname-verify.c
index d46fd965f..6d6de00ca 100644
--- a/lib/x509/hostname-verify.c
+++ b/lib/x509/hostname-verify.c
@@ -218,6 +218,20 @@ gnutls_x509_crt_check_hostname2(gnutls_x509_crt_t cert,
&dnsnamesize,
NULL);
+ if (ret < 0) {
+ if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ /* oversized SAN; proceed without CN fallback */
+ _gnutls_debug_log("oversized SAN ignored, "
+ "disabling CN fallback\n");
+ cn_fallback_allowed = false;
+ ret = 0;
+ continue;
+ }
+ if (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ gnutls_assert();
+ break;
+ }
+
if (PRECLUDES_CN_FALLBACK(ret))
cn_fallback_allowed = false;
--
2.53.0
From 01a4fd98b5b85f6736333aa0381bb56c9aa8dbb9 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 15 Apr 2026 18:02:31 +0200
Subject: [PATCH 3/3] tests/cert-tests: add tests for #1825
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
.../cert-tests/email-certs/oversized-san.pem | 16 +++++++++
tests/cert-tests/email | 11 +++++++
tests/hostname-check.c | 33 +++++++++++++++++++
3 files changed, 60 insertions(+)
create mode 100644 tests/cert-tests/email-certs/oversized-san.pem
diff --git a/tests/cert-tests/email-certs/oversized-san.pem b/tests/cert-tests/email-certs/oversized-san.pem
new file mode 100644
index 000000000..44c0f6997
--- /dev/null
+++ b/tests/cert-tests/email-certs/oversized-san.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICezCCAi2gAwIBAgIUWECpllJihTypDAKZQgEJeM02fG8wBQYDK2VwMDcxFDAS
+BgNVBAMTC2V4YW1wbGUuY29tMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUu
+Y29tMB4XDTI2MDQxNTE1NTE1MloXDTI3MDQxNTE1NTE1MlowNzEUMBIGA1UEAxML
+ZXhhbXBsZS5jb20xHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wKjAF
+BgMrZXADIQCn95fhASNNgr5I/qAX+kiY8SiwPJcTVy1ugWJdX3d4uqOCAUkwggFF
+MA8GA1UdEwEB/wQFMAMBAf8wggERBgNVHREEggEIMIIBBIGCAQBhYWFhYWFhYWFh
+YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh
+YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh
+YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh
+YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh
+YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh
+YWFhYWFhMB0GA1UdDgQWBBTeT2MAM29EwVLvTom8wGN05B7QhDAFBgMrZXADQQAQ
+zTZqdt4LXX21VFce7S99k6XX+N+xPAUo4beursVrlaesdVsfvDtEk2t+0b5WLbtW
+7UI9PxB9CN4hULrxrI8N
+-----END CERTIFICATE-----
diff --git a/tests/cert-tests/email b/tests/cert-tests/email
index a00281028..d2bd48ee3 100755
--- a/tests/cert-tests/email
+++ b/tests/cert-tests/email
@@ -96,5 +96,16 @@ if test "${rc}" != "1"; then
exit 1
fi
+# #1825: oversized SAN does not preclude fallback to DN email
+${VALGRIND} "${CERTTOOL}" \
+ --infile "${srcdir}/email-certs/oversized-san.pem" \
+ --load-ca-certificate "${srcdir}/email-certs/oversized-san.pem" \
+ --verify --verify-email test@example.com
+rc=$?
+
+if test "${rc}" != "1"; then
+ echo "email test 9 failed"
+ exit 1
+fi
exit 0
diff --git a/tests/hostname-check.c b/tests/hostname-check.c
index 71481cafb..068bf7831 100644
--- a/tests/hostname-check.c
+++ b/tests/hostname-check.c
@@ -924,6 +924,24 @@ char srv_and_cn[] =
"p9Nnj64WFIqbTLoqM3nt7+zqFZDvwh+8ZEVcE1MazHOYhDQj1uU3jqIq/sZE8w==\n"
"-----END CERTIFICATE-----\n";
+char pem_1825_oversized_san[] =
+ "ca\n"
+ "cn = example.com\n"
+ "dns_name = <'a' * 256>\n"
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIICOTCCAeugAwIBAgIURFygaiK3EBmc5AMZToFitMMikhcwBQYDK2VwMBYxFDAS\n"
+ "BgNVBAMTC2V4YW1wbGUuY29tMB4XDTI2MDQxNTE2MDYwMFoXDTI3MDQxNTE2MDYw\n"
+ "MFowFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wKjAFBgMrZXADIQBHqgbjhT1zZ3h9\n"
+ "okSrhd2+0Lr0Uj1q81sqHrcCEdqVpaOCAUkwggFFMA8GA1UdEwEB/wQFMAMBAf8w\n"
+ "ggERBgNVHREEggEIMIIBBIKCAQBhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n"
+ "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n"
+ "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n"
+ "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n"
+ "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n"
+ "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhMB0GA1UdDgQWBBT+\n"
+ "/oWt1Lrfz7Awk9h8yDoz1TKyHjAFBgMrZXADQQBfR5ByQyxpLEsVM5+ihYjSbmYF\n"
+ "1pOFndq0UIKPkWsRqBpitzDIVrVTLlIcY0fQpsxITNgdoIU68WynLGVrRHIF\n"
+ "-----END CERTIFICATE-----\n";
void doit(void)
{
@@ -1355,6 +1373,21 @@ void doit(void)
fail("%d: Hostname incorrectly falls back to CN (%d)\n",
__LINE__, ret);
+ if (debug)
+ success("Testing oversized SAN (#1825)...\n");
+ data.data = (unsigned char *)pem_1825_oversized_san;
+ data.size = strlen(pem_1825_oversized_san);
+
+ ret = gnutls_x509_crt_import(x509, &data, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ fail("%d: gnutls_x509_crt_import: %d\n", __LINE__, ret);
+
+ ret = gnutls_x509_crt_check_hostname(x509, "example.com");
+ if (ret)
+ fail("%d: Hostname incorrectly falls back to CN "
+ "with oversized SAN (%d)\n",
+ __LINE__, ret);
+
gnutls_x509_crt_deinit(x509);
gnutls_global_deinit();
--
2.53.0

View File

@ -1,60 +0,0 @@
From 3957f136e2ed23caf176a594b54b3827f5cef701 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Wed, 18 Mar 2026 18:19:06 +0100
Subject: [PATCH] pkcs11_write: fix UAF and leak in gnutls_pkcs11_token_set_pin
Changing Security Officer PIN with gnutls_pkcs11_token_set_pin() with
oldpin == NULL for a token that lacks a protected authentication path
led to a use-after-free.
Reported-by: Luigino Camastra and Joshua Rogers of AISLE Research Team
Fixes: #1766
Fixes: #1809
Fixes: CVE-2026-42014
Fixes: GNUTLS-SA-2026-04-29-9
CVSS: 4.0 Medium CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/pkcs11_write.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/lib/pkcs11_write.c b/lib/pkcs11_write.c
index 5685411ee..194126e95 100644
--- a/lib/pkcs11_write.c
+++ b/lib/pkcs11_write.c
@@ -1297,10 +1297,9 @@ gnutls_pkcs11_token_set_pin(const char *token_url,
ses_flags = SESSION_WRITE | SESSION_LOGIN;
ret = pkcs11_open_session(&sinfo, NULL, info, ses_flags);
- p11_kit_uri_free(info);
-
if (ret < 0) {
gnutls_assert();
+ p11_kit_uri_free(info);
return ret;
}
@@ -1322,8 +1321,10 @@ gnutls_pkcs11_token_set_pin(const char *token_url,
oldpin_size = L(oldpin);
if (!(sinfo.tinfo.flags & CKF_PROTECTED_AUTHENTICATION_PATH)) {
- if (newpin == NULL)
- return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+ if (newpin == NULL) {
+ ret = gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+ goto finish;
+ }
if (oldpin == NULL) {
struct pin_info_st pin_info;
@@ -1354,6 +1355,7 @@ gnutls_pkcs11_token_set_pin(const char *token_url,
ret = 0;
finish:
+ p11_kit_uri_free(info);
pkcs11_close_session(&sinfo);
return ret;
--
2.53.0

View File

@ -1,44 +0,0 @@
From a3e7c50d3e1761e5ef1d4b225507cab8f2b2c3ca Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 20 Apr 2026 22:42:20 +0200
Subject: [PATCH] x509/pkcs12_bag: fix off-by-one in bag element bounds check
Appending elements to a PKCS#12 bag had a bounds check that
prevented adding the 32nd element.
On the other hand, it is possible to import one that already has 32.
Subsequent appending then led to writing past the 32-element array,
smashing its length.
Tighten the check to reject any bag with 32 or more elements.
We'll treat this vulnerability as a Low due to how contrived
the requirements are: for the code to be vulnerable,
it needs to append to an imported untrusted unencrypted PKCS#12 structure.
Reported-by: Zou Dikai
Fixes: #1840
Fixes: CVE-2026-42015
Fixes: GNUTLS-SA-2026-04-29-11
CVSS: 6.1 Medium CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:H
Severity: Low
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/pkcs12_bag.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/x509/pkcs12_bag.c b/lib/x509/pkcs12_bag.c
index 163b0fadb..351996b2f 100644
--- a/lib/x509/pkcs12_bag.c
+++ b/lib/x509/pkcs12_bag.c
@@ -394,7 +394,7 @@ gnutls_pkcs12_bag_set_data(gnutls_pkcs12_bag_t bag,
return GNUTLS_E_INVALID_REQUEST;
}
- if (bag->bag_elements == MAX_BAG_ELEMENTS - 1) {
+ if (bag->bag_elements >= MAX_BAG_ELEMENTS - 1) {
gnutls_assert();
/* bag is full */
return GNUTLS_E_MEMORY_ERROR;
--
2.53.0

View File

@ -1,107 +0,0 @@
From 77228f2d1ac207d2f894e5a168fbb47e5378e42f Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 30 Mar 2026 17:31:07 +0200
Subject: [PATCH 1/2] lib/auth/rsa: check that ciphertext matches the modulus
size
A client sending extremely short premaster secret as part of an
RSA key exchange could've theoretically triggered a short heap overread
to nowhere when the RSA key was backed with a PKCS#11 token.
With this fix, the internal decryption function will not be called
with an mismatching plaintext length specified, avoiding the overread.
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1814
Fixes: CVE-2026-5260
Fixes: GNUTLS-SA-2026-04-29-10
CVSS: 5.9 Medium CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/auth/rsa.c | 5 +++++
lib/auth/rsa_psk.c | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/lib/auth/rsa.c b/lib/auth/rsa.c
index 02b6a3425..b2665d3af 100644
--- a/lib/auth/rsa.c
+++ b/lib/auth/rsa.c
@@ -159,6 +159,7 @@ proc_rsa_client_kx(gnutls_session_t session, uint8_t * data,
int ret, dsize;
ssize_t data_size = _data_size;
volatile uint8_t ver_maj, ver_min;
+ unsigned int key_bits;
#ifdef ENABLE_SSL3
if (get_num_version(session) == GNUTLS_SSL3) {
@@ -181,6 +182,10 @@ proc_rsa_client_kx(gnutls_session_t session, uint8_t * data,
}
ciphertext.size = dsize;
}
+ gnutls_privkey_get_pk_algorithm(session->internals.selected_key,
+ &key_bits);
+ if (ciphertext.size != (key_bits + 7) / 8)
+ return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
ver_maj = _gnutls_get_adv_version_major(session);
ver_min = _gnutls_get_adv_version_minor(session);
diff --git a/lib/auth/rsa_psk.c b/lib/auth/rsa_psk.c
index 8813eeeec..7768b60f2 100644
--- a/lib/auth/rsa_psk.c
+++ b/lib/auth/rsa_psk.c
@@ -270,6 +270,7 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
ssize_t data_size = _data_size;
gnutls_psk_server_credentials_t cred;
volatile uint8_t ver_maj, ver_min;
+ unsigned int rsa_key_bits;
cred = (gnutls_psk_server_credentials_t)
_gnutls_get_cred(session, GNUTLS_CRD_PSK);
@@ -324,6 +325,10 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
}
ciphertext.size = dsize;
+ gnutls_privkey_get_pk_algorithm(session->internals.selected_key,
+ &rsa_key_bits);
+ if (ciphertext.size != (rsa_key_bits + 7) / 8)
+ return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
ver_maj = _gnutls_get_adv_version_major(session);
ver_min = _gnutls_get_adv_version_minor(session);
--
2.53.0
From cf6bdc5e4df49e5583d3fb4d2296779785f10683 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 30 Mar 2026 17:46:40 +0200
Subject: [PATCH 2/2] lib/pkcs11_privkey: guard against overreading on short
ciphertexts
This is an alternative fix for the callee side.
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1814
Fixes: CVE-2026-5260
Fixes: GNUTLS-SA-2026-04-29-10
CVSS: 5.9 Medium CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/pkcs11_privkey.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pkcs11_privkey.c b/lib/pkcs11_privkey.c
index 3ecd1837b..334132ef3 100644
--- a/lib/pkcs11_privkey.c
+++ b/lib/pkcs11_privkey.c
@@ -769,7 +769,7 @@ _gnutls_pkcs11_privkey_decrypt_data2(gnutls_pkcs11_privkey_t key,
if (ret != 0)
return gnutls_assert_val(GNUTLS_E_LOCKING_ERROR);
- buffer = gnutls_malloc(siglen);
+ buffer = gnutls_malloc(MAX((size_t)siglen, plaintext_size));
if (!buffer) {
gnutls_assert();
return GNUTLS_E_MEMORY_ERROR;
--
2.53.0

View File

@ -1,38 +0,0 @@
commit 3b68043ef7e338118bce3ccdcbfafc8f005a6725
Author: Daiki Ueno <ueno@gnu.org>
Date: Mon Jul 7 10:44:12 2025 +0900
x509: avoid double free when exporting othernames in SAN
Previously, the _gnutls_write_new_othername function, called by
gnutls_x509_ext_export_subject_alt_names to export "otherName" in a
certificate's SAN extension, freed the caller allocated ASN.1
structure upon error, resulting in a potential double-free.
Reported by OpenAI Security Research Team.
Signed-off-by: Daiki Ueno <ueno@gnu.org>
Backported-by: Alexander Sosedkin <asosedki@redhat.com>
Backported-from: 608829769cbc247679ffe98841109fc73875e573
Fixes: CVE-2025-32988
diff --git a/lib/x509/extensions.c b/lib/x509/extensions.c
index c9fef21a12..c0acdf9a94 100644
--- a/lib/x509/extensions.c
+++ b/lib/x509/extensions.c
@@ -805,7 +805,6 @@ _gnutls_write_new_othername(ASN1_TYPE ext, const char *ext_name,
result = asn1_write_value(ext, name2, oid, 1);
if (result != ASN1_SUCCESS) {
gnutls_assert();
- asn1_delete_structure(&ext);
return _gnutls_asn2err(result);
}
@@ -814,7 +813,6 @@ _gnutls_write_new_othername(ASN1_TYPE ext, const char *ext_name,
result = asn1_write_value(ext, name2, data, data_size);
if (result != ASN1_SUCCESS) {
gnutls_assert();
- asn1_delete_structure(&ext);
return _gnutls_asn2err(result);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,272 +0,0 @@
commit 8af3c1686d41b059f6f4b8352d36d9686cf7febe
Author: Daiki Ueno <ueno@gnu.org>
Date: Mon Jul 7 11:15:45 2025 +0900
handshake: clear HSK_PSK_SELECTED is when resetting binders
When a TLS 1.3 handshake involves HRR and resumption or PSK, and the
second Client Hello omits PSK, the server would result in a NULL
pointer dereference as the PSK binder information is cleared while the
HSK_PSK_SELECTED flag is still set. This makes sure that
HSK_PSK_SELECTED flag is always cleared when the PSK binders are
reset. This also makes it clear the HSK_PSK_SELECTED flag is valid
only during a handshake; after that, whether PSK is used can be
checked with gnutls_auth_client_get_type.
Reported by Stefan Bühler.
Signed-off-by: Daiki Ueno <ueno@gnu.org>
Backported-by: Alexander Sosedkin <asosedki@redhat.com>
Backported-from: 23135619773e6ec087ff2abc65405bd4d5676bad
Fixes: CVE-2025-6395
diff --git a/lib/handshake.c b/lib/handshake.c
index ce2d160e20..b156223cbc 100644
--- a/lib/handshake.c
+++ b/lib/handshake.c
@@ -580,9 +580,28 @@ static int set_auth_types(gnutls_session_t session)
/* Under TLS1.3 this returns a KX which matches the negotiated
* groups from the key shares; if we are resuming then the KX seen
* here doesn't match the original session. */
- if (session->internals.resumed == RESUME_FALSE)
- kx = gnutls_kx_get(session);
- else
+ if (session->internals.resumed == RESUME_FALSE) {
+ const gnutls_group_entry_st *group = get_group(session);
+
+ if (session->internals.hsk_flags & HSK_PSK_SELECTED) {
+ if (group) {
+ kx = group->pk == GNUTLS_PK_DH ?
+ GNUTLS_KX_DHE_PSK :
+ GNUTLS_KX_ECDHE_PSK;
+ } else {
+ kx = GNUTLS_KX_PSK;
+ }
+ } else if (group) {
+ /* Not necessarily be RSA, but just to
+ * make _gnutls_map_kx_get_cred below
+ * work.
+ */
+ kx = group->pk == GNUTLS_PK_DH ?
+ GNUTLS_KX_DHE_RSA :
+ GNUTLS_KX_ECDHE_RSA;
+ } else
+ kx = GNUTLS_KX_UNKNOWN;
+ } else
kx = GNUTLS_KX_UNKNOWN;
} else {
/* TLS1.2 or earlier, kx is associated with ciphersuite */
diff --git a/lib/state.c b/lib/state.c
index 817a7b8cd8..2bd08c3190 100644
--- a/lib/state.c
+++ b/lib/state.c
@@ -175,7 +175,8 @@ gnutls_kx_algorithm_t gnutls_kx_get(gnutls_session_t session)
const gnutls_group_entry_st *group = get_group(session);
if (ver->tls13_sem) {
- if (session->internals.hsk_flags & HSK_PSK_SELECTED) {
+ if (gnutls_auth_client_get_type(session) ==
+ GNUTLS_CRD_PSK) {
if (group) {
if (group->pk == GNUTLS_PK_DH)
return GNUTLS_KX_DHE_PSK;
@@ -264,6 +265,7 @@ void reset_binders(gnutls_session_t session)
_gnutls_free_temp_key_datum(&session->key.binders[0].psk);
_gnutls_free_temp_key_datum(&session->key.binders[1].psk);
memset(session->key.binders, 0, sizeof(session->key.binders));
+ session->internals.hsk_flags &= ~HSK_PSK_SELECTED;
}
/* Check whether certificate credentials of type @cert_type are set
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b04cb081b4..1019f6c1d8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -118,6 +118,8 @@ ctests = tls13/supported_versions tls13/tls12-no-tls13-exts \
ctests += tls13/hello_retry_request
+ctests += tls13/hello_retry_request_psk
+
ctests += tls13/psk-ext
ctests += tls13/key_update
diff --git a/tests/tls13/hello_retry_request_psk.c b/tests/tls13/hello_retry_request_psk.c
new file mode 100644
index 0000000000..a20cb0d965
--- /dev/null
+++ b/tests/tls13/hello_retry_request_psk.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017-2025 Red Hat, Inc.
+ *
+ * Author: Nikos Mavrogiannopoulos, 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 <https://www.gnu.org/licenses/>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <assert.h>
+
+#include "cert-common.h"
+#include "utils.h"
+#include "tls13/ext-parse.h"
+#include "eagain-common.h"
+
+/* This program exercises the case where a TLS 1.3 handshake ends up
+ * with HRR, and the first CH includes PSK while the 2nd CH omits
+ * it */
+
+const char *testname = "hello entry request";
+
+const char *side = "";
+
+#define myfail(fmt, ...) fail("%s: " fmt, testname, ##__VA_ARGS__)
+
+static void tls_log_func(int level, const char *str)
+{
+ fprintf(stderr, "%s|<%d>| %s", side, level, str);
+}
+
+struct ctx_st {
+ unsigned hrr_seen;
+ unsigned hello_counter;
+};
+
+static int pskfunc(gnutls_session_t session, const char *username,
+ gnutls_datum_t *key)
+{
+ if (debug)
+ printf("psk: username %s\n", username);
+ key->data = gnutls_malloc(4);
+ key->data[0] = 0xDE;
+ key->data[1] = 0xAD;
+ key->data[2] = 0xBE;
+ key->data[3] = 0xEF;
+ key->size = 4;
+ return 0;
+}
+
+static int hello_callback(gnutls_session_t session, unsigned int htype,
+ unsigned post, unsigned int incoming,
+ const gnutls_datum_t *msg)
+{
+ struct ctx_st *ctx = gnutls_session_get_ptr(session);
+ assert(ctx != NULL);
+
+ if (htype == GNUTLS_HANDSHAKE_HELLO_RETRY_REQUEST)
+ ctx->hrr_seen = 1;
+
+ if (htype == GNUTLS_HANDSHAKE_CLIENT_HELLO) {
+ if (post == GNUTLS_HOOK_POST)
+ ctx->hello_counter++;
+ else {
+ /* Unset the PSK credential to omit the extension */
+ gnutls_credentials_set(session, GNUTLS_CRD_PSK, NULL);
+ }
+ }
+
+ return 0;
+}
+
+void doit(void)
+{
+ int sret, cret;
+ gnutls_psk_server_credentials_t scred;
+ gnutls_psk_client_credentials_t ccred;
+ gnutls_certificate_credentials_t ccred2;
+ gnutls_session_t server, client;
+ /* Need to enable anonymous KX specifically. */
+ const gnutls_datum_t key = { (void *)"DEADBEEF", 8 };
+
+ struct ctx_st ctx;
+ memset(&ctx, 0, sizeof(ctx));
+
+ global_init();
+
+ gnutls_global_set_log_function(tls_log_func);
+ if (debug)
+ gnutls_global_set_log_level(9);
+
+ /* Init server */
+ assert(gnutls_psk_allocate_server_credentials(&scred) >= 0);
+ gnutls_psk_set_server_credentials_function(scred, pskfunc);
+
+ gnutls_init(&server, GNUTLS_SERVER);
+
+ assert(gnutls_priority_set_direct(
+ server,
+ "NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-X25519:+DHE-PSK",
+ NULL) >= 0);
+
+ gnutls_credentials_set(server, GNUTLS_CRD_PSK, scred);
+ gnutls_transport_set_push_function(server, server_push);
+ gnutls_transport_set_pull_function(server, server_pull);
+ gnutls_transport_set_ptr(server, server);
+
+ /* Init client */
+ assert(gnutls_psk_allocate_client_credentials(&ccred) >= 0);
+ gnutls_psk_set_client_credentials(ccred, "test", &key,
+ GNUTLS_PSK_KEY_HEX);
+ assert(gnutls_certificate_allocate_credentials(&ccred2) >= 0);
+
+ assert(gnutls_init(&client, GNUTLS_CLIENT | GNUTLS_KEY_SHARE_TOP) >= 0);
+
+ gnutls_session_set_ptr(client, &ctx);
+
+ cret = gnutls_priority_set_direct(
+ client,
+ "NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:+DHE-PSK",
+ NULL);
+ if (cret < 0)
+ myfail("cannot set TLS 1.3 priorities\n");
+
+ gnutls_credentials_set(client, GNUTLS_CRD_PSK, ccred);
+ gnutls_credentials_set(client, GNUTLS_CRD_CERTIFICATE, ccred2);
+ gnutls_transport_set_push_function(client, client_push);
+ gnutls_transport_set_pull_function(client, client_pull);
+ gnutls_transport_set_ptr(client, client);
+
+ gnutls_handshake_set_hook_function(client, GNUTLS_HANDSHAKE_ANY,
+ GNUTLS_HOOK_BOTH, hello_callback);
+
+ HANDSHAKE_EXPECT(client, server, GNUTLS_E_AGAIN,
+ GNUTLS_E_INSUFFICIENT_CREDENTIALS);
+
+ assert(ctx.hrr_seen != 0);
+
+ gnutls_bye(client, GNUTLS_SHUT_WR);
+ gnutls_bye(server, GNUTLS_SHUT_WR);
+
+ gnutls_deinit(client);
+ gnutls_deinit(server);
+
+ gnutls_psk_free_server_credentials(scred);
+ gnutls_psk_free_client_credentials(ccred);
+ gnutls_certificate_free_credentials(ccred2);
+
+ gnutls_global_deinit();
+ reset_buffers();
+}

View File

@ -1,474 +0,0 @@
From 0d39e4120bc5ece53c86c5802c546259b8ca286a Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Fri, 12 Jan 2024 17:56:58 +0900
Subject: [PATCH] nettle: avoid normalization of mpz_t in deterministic ECDSA
This removes function calls that potentially leak bit-length of a
private key used to calculate a nonce in deterministic ECDSA. Namely:
- _gnutls_dsa_compute_k has been rewritten to work on always
zero-padded mp_limb_t arrays instead of mpz_t
- rnd_mpz_func has been replaced with rnd_datum_func, which is backed
by a byte array instead of an mpz_t value
Signed-off-by: Daiki Ueno <ueno@gnu.org>
---
lib/nettle/int/dsa-compute-k.c | 84 +++++++++++++++++++------------
lib/nettle/int/dsa-compute-k.h | 31 +++++++++---
lib/nettle/int/ecdsa-compute-k.c | 71 +++++++++-----------------
lib/nettle/int/ecdsa-compute-k.h | 8 +--
lib/nettle/pk.c | 79 ++++++++++++++++++++---------
tests/sign-verify-deterministic.c | 2 +-
6 files changed, 158 insertions(+), 117 deletions(-)
diff --git a/lib/nettle/int/dsa-compute-k.c b/lib/nettle/int/dsa-compute-k.c
index 17d63318c4..ddeb6f6d1e 100644
--- a/lib/nettle/int/dsa-compute-k.c
+++ b/lib/nettle/int/dsa-compute-k.c
@@ -31,33 +31,37 @@
#include "mpn-base256.h"
#include <string.h>
-#define BITS_TO_LIMBS(bits) (((bits) + GMP_NUMB_BITS - 1) / GMP_NUMB_BITS)
-
-/* The maximum size of q, choosen from the fact that we support
- * 521-bit elliptic curve generator and 512-bit DSA subgroup at
- * maximum. */
-#define MAX_Q_BITS 521
-#define MAX_Q_SIZE ((MAX_Q_BITS + 7) / 8)
-#define MAX_Q_LIMBS BITS_TO_LIMBS(MAX_Q_BITS)
-
-#define MAX_HASH_BITS (MAX_HASH_SIZE * 8)
-#define MAX_HASH_LIMBS BITS_TO_LIMBS(MAX_HASH_BITS)
-
-int
-_gnutls_dsa_compute_k(mpz_t k,
- const mpz_t q,
- const mpz_t x,
- gnutls_mac_algorithm_t mac,
- const uint8_t *digest,
- size_t length)
+/* For mini-gmp */
+#ifndef GMP_LIMB_BITS
+#define GMP_LIMB_BITS GMP_NUMB_BITS
+#endif
+
+static inline int is_zero_limb(mp_limb_t x)
+{
+ x |= (x << 1);
+ return ((x >> 1) - 1) >> (GMP_LIMB_BITS - 1);
+}
+
+static int sec_zero_p(const mp_limb_t *ap, mp_size_t n)
+{
+ volatile mp_limb_t w;
+ mp_size_t i;
+
+ for (i = 0, w = 0; i < n; i++)
+ w |= ap[i];
+
+ return is_zero_limb(w);
+}
+
+int _gnutls_dsa_compute_k(mp_limb_t *h, const mp_limb_t *q, const mp_limb_t *x,
+ mp_size_t qn, mp_bitcnt_t q_bits,
+ gnutls_mac_algorithm_t mac, const uint8_t *digest,
+ size_t length)
{
uint8_t V[MAX_HASH_SIZE];
uint8_t K[MAX_HASH_SIZE];
uint8_t xp[MAX_Q_SIZE];
uint8_t tp[MAX_Q_SIZE];
- mp_limb_t h[MAX(MAX_Q_LIMBS, MAX_HASH_LIMBS)];
- mp_bitcnt_t q_bits = mpz_sizeinbase (q, 2);
- mp_size_t qn = mpz_size(q);
mp_bitcnt_t h_bits = length * 8;
mp_size_t hn = BITS_TO_LIMBS(h_bits);
size_t nbytes = (q_bits + 7) / 8;
@@ -66,6 +70,7 @@ _gnutls_dsa_compute_k(mpz_t k,
mp_limb_t cy;
gnutls_hmac_hd_t hd;
int ret = 0;
+ mp_limb_t scratch[MAX_Q_LIMBS];
if (unlikely(q_bits > MAX_Q_BITS))
return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
@@ -73,7 +78,7 @@ _gnutls_dsa_compute_k(mpz_t k,
return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
/* int2octets(x) */
- mpn_get_base256(xp, nbytes, mpz_limbs_read(x), qn);
+ mpn_get_base256(xp, nbytes, x, qn);
/* bits2octets(h) */
mpn_set_base256(h, hn, digest, length);
@@ -97,12 +102,12 @@ _gnutls_dsa_compute_k(mpz_t k,
mpn_rshift(h, h, hn, shift % GMP_NUMB_BITS);
}
- cy = mpn_sub_n(h, h, mpz_limbs_read(q), qn);
+ cy = mpn_sub_n(h, h, q, qn);
/* Fall back to addmul_1, if nettle is linked with mini-gmp. */
#ifdef mpn_cnd_add_n
- mpn_cnd_add_n(cy, h, h, mpz_limbs_read(q), qn);
+ mpn_cnd_add_n(cy, h, h, q, qn);
#else
- mpn_addmul_1(h, mpz_limbs_read(q), qn, cy != 0);
+ mpn_addmul_1(h, q, qn, cy != 0);
#endif
mpn_get_base256(tp, nbytes, h, qn);
@@ -178,12 +183,8 @@ _gnutls_dsa_compute_k(mpz_t k,
if (tlen * 8 > q_bits)
mpn_rshift (h, h, qn, tlen * 8 - q_bits);
/* Check if k is in [1,q-1] */
- if (!mpn_zero_p (h, qn) &&
- mpn_cmp (h, mpz_limbs_read(q), qn) < 0) {
- mpn_copyi(mpz_limbs_write(k, qn), h, qn);
- mpz_limbs_finish(k, qn);
+ if (!sec_zero_p(h, qn) && mpn_sub_n(scratch, h, q, qn))
break;
- }
ret = gnutls_hmac_init(&hd, mac, K, length);
if (ret < 0)
@@ -207,3 +208,24 @@ _gnutls_dsa_compute_k(mpz_t k,
return ret;
}
+
+/* cancel-out dsa_sign's addition of 1 to random data */
+void _gnutls_dsa_compute_k_finish(uint8_t *k, size_t nbytes, mp_limb_t *h,
+ mp_size_t n)
+{
+ /* Fall back to sub_1, if nettle is linked with mini-gmp. */
+#ifdef mpn_sec_sub_1
+ mp_limb_t t[MAX_Q_LIMBS];
+
+ mpn_sec_sub_1(h, h, n, 1, t);
+#else
+ mpn_sub_1(h, h, n, 1);
+#endif
+ mpn_get_base256(k, nbytes, h, n);
+}
+
+void _gnutls_ecdsa_compute_k_finish(uint8_t *k, size_t nbytes, mp_limb_t *h,
+ mp_size_t n)
+{
+ mpn_get_base256(k, nbytes, h, n);
+}
diff --git a/lib/nettle/int/dsa-compute-k.h b/lib/nettle/int/dsa-compute-k.h
index 64e90e0ca2..e88fce0a6d 100644
--- a/lib/nettle/int/dsa-compute-k.h
+++ b/lib/nettle/int/dsa-compute-k.h
@@ -26,12 +26,29 @@
#include <gnutls/gnutls.h>
#include <nettle/bignum.h> /* includes gmp.h */
-int
-_gnutls_dsa_compute_k(mpz_t k,
- const mpz_t q,
- const mpz_t x,
- gnutls_mac_algorithm_t mac,
- const uint8_t *digest,
- size_t length);
+#define BITS_TO_LIMBS(bits) (((bits) + GMP_NUMB_BITS - 1) / GMP_NUMB_BITS)
+
+/* The maximum size of q, chosen from the fact that we support
+ * 521-bit elliptic curve generator and 512-bit DSA subgroup at
+ * maximum. */
+#define MAX_Q_BITS 521
+#define MAX_Q_SIZE ((MAX_Q_BITS + 7) / 8)
+#define MAX_Q_LIMBS BITS_TO_LIMBS(MAX_Q_BITS)
+
+#define MAX_HASH_BITS (MAX_HASH_SIZE * 8)
+#define MAX_HASH_LIMBS BITS_TO_LIMBS(MAX_HASH_BITS)
+
+#define DSA_COMPUTE_K_ITCH MAX(MAX_Q_LIMBS, MAX_HASH_LIMBS)
+
+int _gnutls_dsa_compute_k(mp_limb_t *h, const mp_limb_t *q, const mp_limb_t *x,
+ mp_size_t qn, mp_bitcnt_t q_bits,
+ gnutls_mac_algorithm_t mac, const uint8_t *digest,
+ size_t length);
+
+void _gnutls_dsa_compute_k_finish(uint8_t *k, size_t nbytes, mp_limb_t *h,
+ mp_size_t n);
+
+void _gnutls_ecdsa_compute_k_finish(uint8_t *k, size_t nbytes, mp_limb_t *h,
+ mp_size_t n);
#endif /* GNUTLS_LIB_NETTLE_INT_DSA_COMPUTE_K_H */
diff --git a/lib/nettle/int/ecdsa-compute-k.c b/lib/nettle/int/ecdsa-compute-k.c
index 94914ebdfa..819302c1c7 100644
--- a/lib/nettle/int/ecdsa-compute-k.c
+++ b/lib/nettle/int/ecdsa-compute-k.c
@@ -29,67 +29,46 @@
#include "dsa-compute-k.h"
#include "gnutls_int.h"
-static inline int
-_gnutls_ecc_curve_to_dsa_q(mpz_t *q, gnutls_ecc_curve_t curve)
+int _gnutls_ecc_curve_to_dsa_q(mpz_t q, gnutls_ecc_curve_t curve)
{
switch (curve) {
#ifdef ENABLE_NON_SUITEB_CURVES
case GNUTLS_ECC_CURVE_SECP192R1:
- mpz_init_set_str(*q,
- "FFFFFFFFFFFFFFFFFFFFFFFF99DEF836"
- "146BC9B1B4D22831",
- 16);
+ mpz_set_str(q,
+ "FFFFFFFFFFFFFFFFFFFFFFFF99DEF836"
+ "146BC9B1B4D22831",
+ 16);
return 0;
case GNUTLS_ECC_CURVE_SECP224R1:
- mpz_init_set_str(*q,
- "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2"
- "E0B8F03E13DD29455C5C2A3D",
- 16);
+ mpz_set_str(q,
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2"
+ "E0B8F03E13DD29455C5C2A3D",
+ 16);
return 0;
#endif
case GNUTLS_ECC_CURVE_SECP256R1:
- mpz_init_set_str(*q,
- "FFFFFFFF00000000FFFFFFFFFFFFFFFF"
- "BCE6FAADA7179E84F3B9CAC2FC632551",
- 16);
+ mpz_set_str(q,
+ "FFFFFFFF00000000FFFFFFFFFFFFFFFF"
+ "BCE6FAADA7179E84F3B9CAC2FC632551",
+ 16);
return 0;
case GNUTLS_ECC_CURVE_SECP384R1:
- mpz_init_set_str(*q,
- "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
- "FFFFFFFFFFFFFFFFC7634D81F4372DDF"
- "581A0DB248B0A77AECEC196ACCC52973",
- 16);
+ mpz_set_str(q,
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFC7634D81F4372DDF"
+ "581A0DB248B0A77AECEC196ACCC52973",
+ 16);
return 0;
case GNUTLS_ECC_CURVE_SECP521R1:
- mpz_init_set_str(*q,
- "1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
- "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
- "FFA51868783BF2F966B7FCC0148F709A"
- "5D03BB5C9B8899C47AEBB6FB71E91386"
- "409",
- 16);
+ mpz_set_str(q,
+ "1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFA51868783BF2F966B7FCC0148F709A"
+ "5D03BB5C9B8899C47AEBB6FB71E91386"
+ "409",
+ 16);
return 0;
default:
return gnutls_assert_val(GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM);
}
}
-
-int
-_gnutls_ecdsa_compute_k (mpz_t k,
- gnutls_ecc_curve_t curve,
- const mpz_t x,
- gnutls_mac_algorithm_t mac,
- const uint8_t *digest,
- size_t length)
-{
- mpz_t q;
- int ret;
-
- ret = _gnutls_ecc_curve_to_dsa_q(&q, curve);
- if (ret < 0)
- return gnutls_assert_val(ret);
-
- ret = _gnutls_dsa_compute_k (k, q, x, mac, digest, length);
- mpz_clear(q);
- return ret;
-}
diff --git a/lib/nettle/int/ecdsa-compute-k.h b/lib/nettle/int/ecdsa-compute-k.h
index 7ca401d6e4..a7e612bcab 100644
--- a/lib/nettle/int/ecdsa-compute-k.h
+++ b/lib/nettle/int/ecdsa-compute-k.h
@@ -26,12 +26,6 @@
#include <gnutls/gnutls.h>
#include <nettle/bignum.h> /* includes gmp.h */
-int
-_gnutls_ecdsa_compute_k (mpz_t k,
- gnutls_ecc_curve_t curve,
- const mpz_t x,
- gnutls_mac_algorithm_t mac,
- const uint8_t *digest,
- size_t length);
+int _gnutls_ecc_curve_to_dsa_q(mpz_t q, gnutls_ecc_curve_t curve);
#endif /* GNUTLS_LIB_NETTLE_INT_ECDSA_COMPUTE_K_H */
diff --git a/lib/nettle/pk.c b/lib/nettle/pk.c
index 588e9df502..b19fe3804a 100644
--- a/lib/nettle/pk.c
+++ b/lib/nettle/pk.c
@@ -102,10 +102,16 @@ static void rnd_nonce_func(void *_ctx, size_t length, uint8_t * data)
}
}
-static void rnd_mpz_func(void *_ctx, size_t length, uint8_t * data)
+static void rnd_datum_func(void *ctx, size_t length, uint8_t *data)
{
- mpz_t *k = _ctx;
- nettle_mpz_get_str_256 (length, data, *k);
+ gnutls_datum_t *d = ctx;
+
+ if (length > d->size) {
+ memset(data, 0, length - d->size);
+ memcpy(data + (length - d->size), d->data, d->size);
+ } else {
+ memcpy(data, d->data, length);
+ }
}
static void rnd_nonce_func_fallback(void *_ctx, size_t length, uint8_t * data)
@@ -976,7 +982,10 @@ _wrap_nettle_pk_sign(gnutls_pk_algorithm_t algo,
struct dsa_signature sig;
int curve_id = pk_params->curve;
const struct ecc_curve *curve;
- mpz_t k;
+ mpz_t q;
+ /* 521-bit elliptic curve generator at maximum */
+ uint8_t buf[(521 + 7) / 8];
+ gnutls_datum_t k = { NULL, 0 };
void *random_ctx;
nettle_random_func *random_func;
@@ -1005,19 +1014,32 @@ _wrap_nettle_pk_sign(gnutls_pk_algorithm_t algo,
hash_len = vdata->size;
}
- mpz_init(k);
+ mpz_init(q);
+
if (_gnutls_get_lib_state() == LIB_STATE_SELFTEST ||
(sign_params->flags & GNUTLS_PK_FLAG_REPRODUCIBLE)) {
- ret = _gnutls_ecdsa_compute_k(k,
- curve_id,
- pk_params->params[ECC_K],
- DIG_TO_MAC(sign_params->dsa_dig),
- vdata->data,
- vdata->size);
+ mp_limb_t h[DSA_COMPUTE_K_ITCH];
+
+ ret = _gnutls_ecc_curve_to_dsa_q(q, curve_id);
if (ret < 0)
goto ecdsa_cleanup;
+
+ ret = _gnutls_dsa_compute_k(
+ h, mpz_limbs_read(q), priv.p,
+ ecc_size(priv.ecc), ecc_bit_size(priv.ecc),
+ DIG_TO_MAC(sign_params->dsa_dig), vdata->data,
+ vdata->size);
+ if (ret < 0)
+ goto ecdsa_cleanup;
+
+ k.data = buf;
+ k.size = (ecc_bit_size(priv.ecc) + 7) / 8;
+
+ _gnutls_ecdsa_compute_k_finish(k.data, k.size, h,
+ ecc_size(priv.ecc));
+
random_ctx = &k;
- random_func = rnd_mpz_func;
+ random_func = rnd_datum_func;
} else {
random_ctx = NULL;
random_func = rnd_nonce_func;
@@ -1038,7 +1060,7 @@ _wrap_nettle_pk_sign(gnutls_pk_algorithm_t algo,
ecdsa_cleanup:
dsa_signature_clear(&sig);
ecc_scalar_zclear(&priv);
- mpz_clear(k);
+ mpz_clear(q);
if (ret < 0) {
gnutls_assert();
@@ -1051,7 +1073,9 @@ _wrap_nettle_pk_sign(gnutls_pk_algorithm_t algo,
struct dsa_params pub;
bigint_t priv;
struct dsa_signature sig;
- mpz_t k;
+ /* 512-bit DSA subgroup at maximum */
+ uint8_t buf[(512 + 7) / 8];
+ gnutls_datum_t k = { NULL, 0 };
void *random_ctx;
nettle_random_func *random_func;
@@ -1074,21 +1098,27 @@ _wrap_nettle_pk_sign(gnutls_pk_algorithm_t algo,
hash_len = vdata->size;
}
- mpz_init(k);
if (_gnutls_get_lib_state() == LIB_STATE_SELFTEST ||
(sign_params->flags & GNUTLS_PK_FLAG_REPRODUCIBLE)) {
- ret = _gnutls_dsa_compute_k(k,
- pub.q,
- TOMPZ(priv),
- DIG_TO_MAC(sign_params->dsa_dig),
- vdata->data,
- vdata->size);
+ mp_limb_t h[DSA_COMPUTE_K_ITCH];
+
+ ret = _gnutls_dsa_compute_k(
+ h, mpz_limbs_read(pub.q),
+ mpz_limbs_read(TOMPZ(priv)), mpz_size(pub.q),
+ mpz_sizeinbase(pub.q, 2),
+ DIG_TO_MAC(sign_params->dsa_dig), vdata->data,
+ vdata->size);
if (ret < 0)
goto dsa_fail;
- /* cancel-out dsa_sign's addition of 1 to random data */
- mpz_sub_ui (k, k, 1);
+
+ k.data = buf;
+ k.size = (mpz_sizeinbase(pub.q, 2) + 7) / 8;
+
+ _gnutls_dsa_compute_k_finish(k.data, k.size, h,
+ mpz_size(pub.q));
+
random_ctx = &k;
- random_func = rnd_mpz_func;
+ random_func = rnd_datum_func;
} else {
random_ctx = NULL;
random_func = rnd_nonce_func;
@@ -1108,7 +1138,6 @@ _wrap_nettle_pk_sign(gnutls_pk_algorithm_t algo,
dsa_fail:
dsa_signature_clear(&sig);
- mpz_clear(k);
if (ret < 0) {
gnutls_assert();
diff --git a/tests/sign-verify-deterministic.c b/tests/sign-verify-deterministic.c
index 6e907288ee..25aa553a59 100644
--- a/tests/sign-verify-deterministic.c
+++ b/tests/sign-verify-deterministic.c
@@ -197,7 +197,7 @@ void doit(void)
&signature);
if (ret < 0)
testfail("gnutls_pubkey_verify_data2\n");
- success(" - pass");
+ success(" - pass\n");
next:
gnutls_free(signature.data);
--
2.44.0

View File

@ -1,121 +0,0 @@
From fe912c5dba49dcecbd5c32bf8184e60a949af452 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Wed, 10 Jan 2024 19:13:17 +0900
Subject: [PATCH] rsa-psk: minimize branching after decryption
This moves any non-trivial code between gnutls_privkey_decrypt_data2
and the function return in _gnutls_proc_rsa_psk_client_kx up until the
decryption. This also avoids an extra memcpy to session->key.key.
Signed-off-by: Daiki Ueno <ueno@gnu.org>
---
lib/auth/rsa_psk.c | 68 ++++++++++++++++++++++++----------------------
1 file changed, 35 insertions(+), 33 deletions(-)
diff --git a/lib/auth/rsa_psk.c b/lib/auth/rsa_psk.c
index 93c2dc9998..8f3fe5a4bd 100644
--- a/lib/auth/rsa_psk.c
+++ b/lib/auth/rsa_psk.c
@@ -269,7 +269,6 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
int ret, dsize;
ssize_t data_size = _data_size;
gnutls_psk_server_credentials_t cred;
- gnutls_datum_t premaster_secret = { NULL, 0 };
volatile uint8_t ver_maj, ver_min;
cred = (gnutls_psk_server_credentials_t)
@@ -329,24 +328,48 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
ver_maj = _gnutls_get_adv_version_major(session);
ver_min = _gnutls_get_adv_version_minor(session);
- premaster_secret.data = gnutls_malloc(GNUTLS_MASTER_SIZE);
- if (premaster_secret.data == NULL) {
+ /* Find the key of this username. A random value will be
+ * filled in if the key is not found.
+ */
+ ret =
+ _gnutls_psk_pwd_find_entry(session, info->username, strlen(info->username), &pwd_psk);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ /* Allocate memory for premaster secret, and fill in the
+ * fields except the decryption result.
+ */
+ session->key.key.size = 2 + GNUTLS_MASTER_SIZE + 2 + pwd_psk.size;
+ session->key.key.data = gnutls_malloc(session->key.key.size);
+ if (session->key.key.data == NULL) {
gnutls_assert();
+ _gnutls_free_key_datum(&pwd_psk);
+ /* No need to zeroize, as the secret is not copied in yet */
+ _gnutls_free_datum(&session->key.key);
return GNUTLS_E_MEMORY_ERROR;
}
- premaster_secret.size = GNUTLS_MASTER_SIZE;
/* Fallback value when decryption fails. Needs to be unpredictable. */
- ret = gnutls_rnd(GNUTLS_RND_NONCE, premaster_secret.data,
- premaster_secret.size);
+ ret = gnutls_rnd(GNUTLS_RND_NONCE, session->key.key.data + 2,
+ GNUTLS_MASTER_SIZE);
if (ret < 0) {
gnutls_assert();
- goto cleanup;
+ _gnutls_free_key_datum(&pwd_psk);
+ /* No need to zeroize, as the secret is not copied in yet */
+ _gnutls_free_datum(&session->key.key);
+ return ret;
}
+ _gnutls_write_uint16(GNUTLS_MASTER_SIZE, session->key.key.data);
+ _gnutls_write_uint16(pwd_psk.size,
+ &session->key.key.data[2 + GNUTLS_MASTER_SIZE]);
+ memcpy(&session->key.key.data[2 + GNUTLS_MASTER_SIZE + 2],
+ pwd_psk.data, pwd_psk.size);
+ _gnutls_free_key_datum(&pwd_psk);
+
gnutls_privkey_decrypt_data2(session->internals.selected_key, 0,
- &ciphertext, premaster_secret.data,
- premaster_secret.size);
+ &ciphertext, session->key.key.data + 2,
+ GNUTLS_MASTER_SIZE);
/* After this point, any conditional on failure that cause differences
* in execution may create a timing or cache access pattern side
* channel that can be used as an oracle, so tread carefully */
@@ -365,31 +388,10 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
/* This is here to avoid the version check attack
* discussed above.
*/
- premaster_secret.data[0] = ver_maj;
- premaster_secret.data[1] = ver_min;
-
- /* find the key of this username
- */
- ret =
- _gnutls_psk_pwd_find_entry(session, info->username, strlen(info->username), &pwd_psk);
- if (ret < 0) {
- gnutls_assert();
- goto cleanup;
- }
-
- ret =
- set_rsa_psk_session_key(session, &pwd_psk, &premaster_secret);
- if (ret < 0) {
- gnutls_assert();
- goto cleanup;
- }
+ session->key.key.data[2] = ver_maj;
+ session->key.key.data[3] = ver_min;
- ret = 0;
- cleanup:
- _gnutls_free_key_datum(&pwd_psk);
- _gnutls_free_temp_key_datum(&premaster_secret);
-
- return ret;
+ return 0;
}
static int
--
2.43.0

View File

@ -1,202 +0,0 @@
From e007a54432c98618bde500649817d153225abf6b Mon Sep 17 00:00:00 2001
From: rpm-build <rpm-build>
Date: Thu, 7 Dec 2023 11:52:08 +0900
Subject: [PATCH] gnutls-3.6.16-rsa-psk-timing.patch
Signed-off-by: rpm-build <rpm-build>
---
lib/auth/rsa.c | 2 +-
lib/auth/rsa_psk.c | 93 +++++++++++++++++-----------------------------
lib/gnutls_int.h | 4 --
lib/priority.c | 1 -
4 files changed, 35 insertions(+), 65 deletions(-)
diff --git a/lib/auth/rsa.c b/lib/auth/rsa.c
index 858701f..02b6a34 100644
--- a/lib/auth/rsa.c
+++ b/lib/auth/rsa.c
@@ -207,7 +207,7 @@ proc_rsa_client_kx(gnutls_session_t session, uint8_t * data,
session->key.key.size);
/* After this point, any conditional on failure that cause differences
* in execution may create a timing or cache access pattern side
- * channel that can be used as an oracle, so treat very carefully */
+ * channel that can be used as an oracle, so tread carefully */
/* Error handling logic:
* In case decryption fails then don't inform the peer. Just use the
diff --git a/lib/auth/rsa_psk.c b/lib/auth/rsa_psk.c
index 1a9dab5..93c2dc9 100644
--- a/lib/auth/rsa_psk.c
+++ b/lib/auth/rsa_psk.c
@@ -264,14 +264,13 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
{
gnutls_datum_t username;
psk_auth_info_t info;
- gnutls_datum_t plaintext;
gnutls_datum_t ciphertext;
gnutls_datum_t pwd_psk = { NULL, 0 };
int ret, dsize;
- int randomize_key = 0;
ssize_t data_size = _data_size;
gnutls_psk_server_credentials_t cred;
gnutls_datum_t premaster_secret = { NULL, 0 };
+ volatile uint8_t ver_maj, ver_min;
cred = (gnutls_psk_server_credentials_t)
_gnutls_get_cred(session, GNUTLS_CRD_PSK);
@@ -327,71 +326,47 @@ _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session, uint8_t * data,
}
ciphertext.size = dsize;
- ret =
- gnutls_privkey_decrypt_data(session->internals.selected_key, 0,
- &ciphertext, &plaintext);
- if (ret < 0 || plaintext.size != GNUTLS_MASTER_SIZE) {
- /* In case decryption fails then don't inform
- * the peer. Just use a random key. (in order to avoid
- * attack against pkcs-1 formatting).
- */
+ ver_maj = _gnutls_get_adv_version_major(session);
+ ver_min = _gnutls_get_adv_version_minor(session);
+
+ premaster_secret.data = gnutls_malloc(GNUTLS_MASTER_SIZE);
+ if (premaster_secret.data == NULL) {
gnutls_assert();
- _gnutls_debug_log
- ("auth_rsa_psk: Possible PKCS #1 format attack\n");
- if (ret >= 0) {
- gnutls_free(plaintext.data);
- }
- randomize_key = 1;
- } else {
- /* If the secret was properly formatted, then
- * check the version number.
- */
- if (_gnutls_get_adv_version_major(session) !=
- plaintext.data[0]
- || (session->internals.allow_wrong_pms == 0
- && _gnutls_get_adv_version_minor(session) !=
- plaintext.data[1])) {
- /* No error is returned here, if the version number check
- * fails. We proceed normally.
- * That is to defend against the attack described in the paper
- * "Attacking RSA-based sessions in SSL/TLS" by Vlastimil Klima,
- * Ondej Pokorny and Tomas Rosa.
- */
- gnutls_assert();
- _gnutls_debug_log
- ("auth_rsa: Possible PKCS #1 version check format attack\n");
- }
+ return GNUTLS_E_MEMORY_ERROR;
}
+ premaster_secret.size = GNUTLS_MASTER_SIZE;
-
- if (randomize_key != 0) {
- premaster_secret.size = GNUTLS_MASTER_SIZE;
- premaster_secret.data =
- gnutls_malloc(premaster_secret.size);
- if (premaster_secret.data == NULL) {
- gnutls_assert();
- return GNUTLS_E_MEMORY_ERROR;
- }
-
- /* we do not need strong random numbers here.
- */
- ret = gnutls_rnd(GNUTLS_RND_NONCE, premaster_secret.data,
- premaster_secret.size);
- if (ret < 0) {
- gnutls_assert();
- goto cleanup;
- }
- } else {
- premaster_secret.data = plaintext.data;
- premaster_secret.size = plaintext.size;
+ /* Fallback value when decryption fails. Needs to be unpredictable. */
+ ret = gnutls_rnd(GNUTLS_RND_NONCE, premaster_secret.data,
+ premaster_secret.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
}
+ gnutls_privkey_decrypt_data2(session->internals.selected_key, 0,
+ &ciphertext, premaster_secret.data,
+ premaster_secret.size);
+ /* After this point, any conditional on failure that cause differences
+ * in execution may create a timing or cache access pattern side
+ * channel that can be used as an oracle, so tread carefully */
+
+ /* Error handling logic:
+ * In case decryption fails then don't inform the peer. Just use the
+ * random key previously generated. (in order to avoid attack against
+ * pkcs-1 formatting).
+ *
+ * If we get version mismatches no error is returned either. We
+ * proceed normally. This is to defend against the attack described
+ * in the paper "Attacking RSA-based sessions in SSL/TLS" by
+ * Vlastimil Klima, Ondej Pokorny and Tomas Rosa.
+ */
+
/* This is here to avoid the version check attack
* discussed above.
*/
-
- premaster_secret.data[0] = _gnutls_get_adv_version_major(session);
- premaster_secret.data[1] = _gnutls_get_adv_version_minor(session);
+ premaster_secret.data[0] = ver_maj;
+ premaster_secret.data[1] = ver_min;
/* find the key of this username
*/
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index 31cec5c..815f69b 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -971,7 +971,6 @@ struct gnutls_priority_st {
bool _no_etm;
bool _no_ext_master_secret;
bool _allow_key_usage_violation;
- bool _allow_wrong_pms;
bool _dumbfw;
unsigned int _dh_prime_bits; /* old (deprecated) variable */
@@ -989,7 +988,6 @@ struct gnutls_priority_st {
(x)->no_etm = 1; \
(x)->no_ext_master_secret = 1; \
(x)->allow_key_usage_violation = 1; \
- (x)->allow_wrong_pms = 1; \
(x)->dumbfw = 1
#define ENABLE_PRIO_COMPAT(x) \
@@ -998,7 +996,6 @@ struct gnutls_priority_st {
(x)->_no_etm = 1; \
(x)->_no_ext_master_secret = 1; \
(x)->_allow_key_usage_violation = 1; \
- (x)->_allow_wrong_pms = 1; \
(x)->_dumbfw = 1
/* DH and RSA parameters types.
@@ -1123,7 +1120,6 @@ typedef struct {
bool no_etm;
bool no_ext_master_secret;
bool allow_key_usage_violation;
- bool allow_wrong_pms;
bool dumbfw;
/* old (deprecated) variable. This is used for both srp_prime_bits
diff --git a/lib/priority.c b/lib/priority.c
index 0a284ae..67ec887 100644
--- a/lib/priority.c
+++ b/lib/priority.c
@@ -681,7 +681,6 @@ gnutls_priority_set(gnutls_session_t session, gnutls_priority_t priority)
COPY_TO_INTERNALS(no_etm);
COPY_TO_INTERNALS(no_ext_master_secret);
COPY_TO_INTERNALS(allow_key_usage_violation);
- COPY_TO_INTERNALS(allow_wrong_pms);
COPY_TO_INTERNALS(dumbfw);
COPY_TO_INTERNALS(dh_prime_bits);
--
2.43.0

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
Version: 3.6.16 Version: 3.6.16
Release: 8%{?dist}.6 Release: 7%{?dist}
Patch1: gnutls-3.2.7-rpath.patch Patch1: gnutls-3.2.7-rpath.patch
Patch2: gnutls-3.6.4-no-now-guile.patch Patch2: gnutls-3.6.4-no-now-guile.patch
Patch3: gnutls-3.6.13-enable-intel-cet.patch Patch3: gnutls-3.6.13-enable-intel-cet.patch
@ -12,42 +12,6 @@ Patch15: gnutls-3.6.16-pkcs7-verify.patch
Patch16: gnutls-3.6.16-cpuid.patch Patch16: gnutls-3.6.16-cpuid.patch
Patch17: gnutls-3.7.8-rsa-kx-timing.patch Patch17: gnutls-3.7.8-rsa-kx-timing.patch
Patch18: gnutls-3.6.16-rehandshake-tickets.patch Patch18: gnutls-3.6.16-rehandshake-tickets.patch
Patch19: gnutls-3.6.16-rsa-psk-timing.patch
Patch20: gnutls-3.6.16-rsa-psk-timing-followup.patch
Patch21: gnutls-3.6.16-deterministic-ecdsa-fixes.patch
Patch22: gnutls-3.8.9-CVE-2024-12243.patch
Patch23: gnutls-3.6.16-cve-2025-6395.patch
Patch24: gnutls-3.6.16-cve-2025-32988.patch
Patch25: gnutls-3.6.16-cve-2025-32990.patch
Patch26: gnutls-3.6.16-CVE-2025-9820.patch
Patch27: gnutls-3.6.16-CVE-2025-14831.patch
# CVE fixes backported from 3.8.13 release
# (https://gitlab.com/gnutls/gnutls/-/merge_requests/2102)
Patch28: gnutls-3.6.16-CVE-2026-33846-dtls-len.patch
Patch29: gnutls-3.6.16-CVE-2026-42009-dtls-qsort.patch
Patch30: gnutls-3.6.16-CVE-2026-33845-dtls-uflow.patch
Patch31: gnutls-3.6.16-CVE-2026-42010-psk-nul.patch
Patch32: gnutls-3.6.16-CVE-2026-3833-nc-case.patch
Patch33: gnutls-3.6.16-CVE-2026-42011-nc-intersect.patch
Patch34: gnutls-3.6.16-CVE-2026-42012-url-san-cn.patch
Patch35: gnutls-3.6.16-CVE-2026-42013-oversized-san.patch
Patch36: gnutls-3.6.16-CVE-2026-42014-so-pin-uaf.patch
Patch37: gnutls-3.6.16-CVE-2026-5260-p11-rsa-overread.patch
Patch38: gnutls-3.6.16-CVE-2026-42015-p12-bag32.patch
# not in 3.6: CVE-2026-3832-ocsp-rev-0 - since 3.8.9
# not in 3.6: CVE-2026-5419-p7-constant-time - since 3.7.7
# non-CVE security fixes from the same release
Patch39: gnutls-3.6.16-1808-psk-rehandshake.patch
Patch40: gnutls-3.6.16-1810-ocsp-truncated-eku.patch
# not in 3.6: 1813-p11p-aes-ephemeral
Patch41: gnutls-3.6.16-1818-rsa-coprime.patch
Patch42: gnutls-3.6.16-1818-pem-parsing.patch
Patch43: gnutls-3.6.16-1819-dblfree-mid-import.patch
# not in 3.6: 1822-sct-overread
# not in 3.6: 1823-cfg-clear-options
Patch44: gnutls-3.6.16-1817-security-parameters.patch
# not in 3.6: 1820-p11p-kdf
%bcond_without dane %bcond_without dane
%if 0%{?rhel} %if 0%{?rhel}
%bcond_with guile %bcond_with guile
@ -262,7 +226,7 @@ rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/gnutls-dane.pc
%find_lang gnutls %find_lang gnutls
%check %check
make check %{?_smp_mflags} V=1 VERBOSE=1 make check %{?_smp_mflags}
%post devel %post devel
if [ -f %{_infodir}/gnutls.info.gz ]; then if [ -f %{_infodir}/gnutls.info.gz ]; then
@ -332,42 +296,6 @@ fi
%endif %endif
%changelog %changelog
* Thu Apr 30 2026 Alexander Sosedkin <asosedkin@redhat.com> - 3.6.16-8.6
- Fix CVE-2026-33846 (DTLS fragment reassembly, High, heap overwrite)
- Fix CVE-2026-42009 (DTLS fragment reassembly, High, undefined behaviour)
- Fix CVE-2026-33845 (DTLS fragment reassembly, High, heap overread)
- Fix CVE-2026-42010 (PSK authentication, High, authentication bypass)
- Fix CVE-2026-3833 (Name constraints, Medium, name constraint bypass)
- Fix CVE-2026-42011 (Name constraints, Medium, name constraint bypass)
- Fix CVE-2026-42012 (CN fallback, Medium, certificate misuse)
- Fix CVE-2026-42013 (CN fallback, Medium, certificate misuse)
- Fix CVE-2026-42014 (PKCS#11 PIN change, Medium, use-after-free)
- Fix CVE-2026-5260 (PKCS#11 RSA, Medium, heap overread)
- Fix CVE-2026-42015 (PKCS#12 appending, Low, heap overwrite)
- Fix upstream security issue #1808 (PSK rehandshake)
- Fix upstream security issue #1810 (EKU OID prefix match)
- Fix upstream security issue #1818 (RSA correctness, OpenSSL format import)
- Fix upstream security issue #1819 (PKCS#11 trust removal error path)
- Fix upstream security issue #1817 (session parameter loading robustness)
* Thu Feb 12 2026 Alexander Sosedkin <asosedki@redhat.com> - 3.6.16-8.5
- Backport the fixes for CVE-2025-9820 and CVE-2025-14831
* Wed Feb 12 2025 Alexander Sosedkin <asosedki@redhat.com> - 3.6.16-8.4
- Backport the fixes for CVE-2025-6395, CVE-2025-32988 and CVE-2025-32990
* Wed Feb 12 2025 Alexander Sosedkin <asosedki@redhat.com> - 3.6.16-8.3
- Backport the fix for CVE-2024-12243
* Mon Mar 25 2024 Daiki Ueno <dueno@redhat.com> - 3.6.16-8.2
- Fix timing side-channel in deterministic ECDSA (RHEL-35231)
* Mon Jan 22 2024 Daiki Ueno <dueno@redhat.com> - 3.6.16-8.1
- auth/rsa-psk: minimize branching after decryption (RHEL-21550)
* Wed Dec 6 2023 Daiki Ueno <dueno@redhat.com> - 3.6.16-8
- auth/rsa_psk: side-step potential side-channel (RHEL-16754)
* Mon Jun 26 2023 Daiki Ueno <dueno@redhat.com> - 3.6.16-7 * Mon Jun 26 2023 Daiki Ueno <dueno@redhat.com> - 3.6.16-7
- Clear server's session ticket indication at rehandshake (#2089817) - Clear server's session ticket indication at rehandshake (#2089817)