import CS git gnutls-3.6.16-8.el8_10.6

This commit is contained in:
AlmaLinux RelEng Bot 2026-05-26 03:09:04 -04:00
parent fdb3259542
commit 09d4f85c6e
18 changed files with 5371 additions and 2 deletions

View File

@ -0,0 +1,226 @@
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

@ -0,0 +1,35 @@
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

@ -0,0 +1,116 @@
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

@ -0,0 +1,41 @@
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

@ -0,0 +1,58 @@
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

View File

@ -0,0 +1,488 @@
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

@ -0,0 +1,944 @@
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

@ -0,0 +1,166 @@
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

@ -0,0 +1,95 @@
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

@ -0,0 +1,498 @@
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

@ -0,0 +1,176 @@
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

@ -0,0 +1,552 @@
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

@ -0,0 +1,244 @@
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

@ -0,0 +1,60 @@
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

@ -0,0 +1,44 @@
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

@ -0,0 +1,107 @@
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,5 +1,5 @@
Version: 3.6.16
Release: 8%{?dist}.5
Release: 8%{?dist}.6
Patch1: gnutls-3.2.7-rpath.patch
Patch2: gnutls-3.6.4-no-now-guile.patch
Patch3: gnutls-3.6.13-enable-intel-cet.patch
@ -21,6 +21,33 @@ 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
%if 0%{?rhel}
%bcond_with guile
@ -235,7 +262,7 @@ rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/gnutls-dane.pc
%find_lang gnutls
%check
make check %{?_smp_mflags}
make check %{?_smp_mflags} V=1 VERBOSE=1
%post devel
if [ -f %{_infodir}/gnutls.info.gz ]; then
@ -305,6 +332,24 @@ fi
%endif
%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