Fix 3.8.13 CVEs and security issues, update Makefile.in

- Fix CVE-2026-33846
- Fix CVE-2026-42009
- Fix CVE-2026-33845
- Fix CVE-2026-42010
- Fix CVE-2026-3833
- Fix CVE-2026-42011
- Fix CVE-2026-42012
- Fix CVE-2026-42013
- Fix CVE-2026-42014
- Fix CVE-2026-5260
- Fix CVE-2026-42015
- Fix CVE-2026-3832
- Fix CVE-2026-5419
- Fix upstream security issue #1808
- Fix upstream security issue #1810
- Fix upstream security issue #1813
- Fix upstream security issue #1818
- Fix upstream security issue #1818
- Fix upstream security issue #1819
- Fix upstream security issue #1822
- Fix upstream security issue #1841
- Fix upstream security issue #1823
- Fix upstream security issue #1817
- Fix upstream security issue #1820
- gnutls-3.8.10-CVE-2025-9820.patch: update Makefile.in

Resolves: RHEL-159071
Resolves: RHEL-159066
Resolves: RHEL-154340
This commit is contained in:
Alexander Sosedkin 2026-04-27 15:39:24 +02:00
parent 0b376e4fd7
commit 1485aa278c
26 changed files with 6380 additions and 3 deletions

View File

@ -0,0 +1,212 @@
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 | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/handshake-checks.c b/lib/handshake-checks.c
index 5231046e8..ad92932d7 100644
--- a/lib/handshake-checks.c
+++ b/lib/handshake-checks.c
@@ -80,10 +80,10 @@ int _gnutls_check_id_for_change(gnutls_session_t session)
if (session->internals.saved_username &&
session->internals.saved_username_size != -1) {
- if (session->internals.saved_username_size ==
- username_length &&
- strncmp(session->internals.saved_username, username,
- username_length)) {
+ 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(
--
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 | 46 +++++++++++++++++--------------
1 file changed, 25 insertions(+), 21 deletions(-)
diff --git a/tests/rehandshake-switch-psk-id.c b/tests/rehandshake-switch-psk-id.c
index 726ee06c2..a16048776 100644
--- a/tests/rehandshake-switch-psk-id.c
+++ b/tests/rehandshake-switch-psk-id.c
@@ -26,7 +26,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <errno.h>
#include <gnutls/gnutls.h>
#include "utils.h"
#include "eagain-common.h"
@@ -34,6 +33,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)
@@ -41,8 +42,6 @@ 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)
{
@@ -74,6 +73,9 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx,
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)
@@ -163,26 +165,28 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx,
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();
+ /* 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();
+ }
+ }
- /* 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();
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 | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/tests/rehandshake-switch-psk-id.c b/tests/rehandshake-switch-psk-id.c
index a16048776..84d8b9d67 100644
--- a/tests/rehandshake-switch-psk-id.c
+++ b/tests/rehandshake-switch-psk-id.c
@@ -57,7 +57,7 @@ static int pskfunc(gnutls_session_t session, const char *username,
}
static void try(const char *prio, gnutls_kx_algorithm_t kx,
- unsigned allow_change)
+ unsigned allow_change, const char *username)
{
int ret;
/* Server stuff. */
@@ -73,8 +73,8 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx,
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);
@@ -114,7 +114,7 @@ static void try(const char *prio, gnutls_kx_algorithm_t kx,
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);
@@ -177,14 +177,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,25 @@
From 9f0350271b12cb61d93e25af43ba6e6eedfcc304 Mon Sep 17 00:00:00 2001
From: Zoltan Fridrich <zfridric@redhat.com>
Date: Wed, 25 Mar 2026 19:43:33 +0100
Subject: [PATCH] pkcs11/p11_cipher: make AES keys ephemeral objects
Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
---
lib/pkcs11/p11_cipher.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/lib/pkcs11/p11_cipher.c b/lib/pkcs11/p11_cipher.c
index 8837aa803..5e64364cb 100644
--- a/lib/pkcs11/p11_cipher.c
+++ b/lib/pkcs11/p11_cipher.c
@@ -183,7 +183,6 @@ static int aes_set_key(struct p11_cipher_ctx *ctx, const void *key,
CK_ATTRIBUTE attrs[] = { { CKA_CLASS, &attr_class, sizeof(attr_class) },
{ CKA_KEY_TYPE, &attr_key_type,
sizeof(attr_key_type) },
- { CKA_TOKEN, &attr_true, sizeof(attr_true) },
{ CKA_ENCRYPT, &attr_true, sizeof(attr_true) },
{ CKA_DECRYPT, &attr_true, sizeof(attr_true) },
{ CKA_LABEL, label, sizeof(label) - 1 },
--
2.53.0

View File

@ -0,0 +1,35 @@
From 3d45a63b16f64ac53abe9f1a02135e8daf1020f8 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 | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/session_pack.c b/lib/session_pack.c
index bd1ce3361..6c1d98270 100644
--- a/lib/session_pack.c
+++ b/lib/session_pack.c
@@ -973,6 +973,10 @@ static int unpack_security_parameters(gnutls_session_t session,
&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,
session->internals.resumed_security_parameters.session_id_size);
--
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 eb8db9353..50eb6c040 100644
--- a/lib/x509/privkey_openssl.c
+++ b/lib/x509/privkey_openssl.c
@@ -173,7 +173,8 @@ int 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;
@@ -217,6 +218,8 @@ int 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);
if (ret < 0) {
--
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 4a270312d..b769f142b 100644
--- a/tests/key-openssl.c
+++ b/tests/key-openssl.c
@@ -108,6 +108,21 @@ const char key_lowercase_iv[] =
"57ohSPIR3bXgRZuefjxBhQYthUPcZ+qktrbURcvHNLs=\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 */
+
static int good_pwd_cb(void *userdata, int attempt, const char *token_url,
const char *token_label, unsigned int flags, char *pin,
size_t pin_max)
@@ -281,5 +296,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,39 @@
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 | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/lib/nettle/pk.c b/lib/nettle/pk.c
index 4047df377..e8b36f5fa 100644
--- a/lib/nettle/pk.c
+++ b/lib/nettle/pk.c
@@ -4434,8 +4434,11 @@ static int 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 = gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
goto rsa_cleanup;
--
2.53.0

View File

@ -0,0 +1,60 @@
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 | 6 ++----
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/lib/pkcs11.c b/lib/pkcs11.c
index 1fe4ee61c..a93a8f3f3 100644
--- a/lib/pkcs11.c
+++ b/lib/pkcs11.c
@@ -3869,6 +3869,7 @@ int 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 dc975baeb..3beb703ba 100644
--- a/lib/x509/verify-high2.c
+++ b/lib/x509/verify-high2.c
@@ -207,8 +207,7 @@ static int add_trust_list_pkcs11_object_url(gnutls_x509_trust_list_t list,
goto cleanup;
}
- xcrt_list = _gnutls_reallocarray(NULL, pcrt_list_size,
- sizeof(gnutls_x509_crt_t));
+ xcrt_list = gnutls_calloc(pcrt_list_size, sizeof(gnutls_x509_crt_t));
if (xcrt_list == NULL) {
ret = GNUTLS_E_MEMORY_ERROR;
goto cleanup;
@@ -254,8 +253,7 @@ static int remove_pkcs11_object_url(gnutls_x509_trust_list_t list,
goto cleanup;
}
- xcrt_list = _gnutls_reallocarray(NULL, pcrt_list_size,
- sizeof(gnutls_x509_crt_t));
+ 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,61 @@
From fdef6b6f493c303bdeb2513e1626ffef896a98f2 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 13 Apr 2026 13:42:52 +0200
Subject: [PATCH] lib/pkcs11: do not silently pass on unimplemented
functionality
When the relevant PKCS#11 header macros were not defined,
several functions for FIPS PKCS#11 provider wrongfully reported success.
They have been modified to return GNUTLS_E_UNIMPLEMENTED_FEATURE instead.
Fixes: #1820
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Co-authored-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/pkcs11/p11_mac.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/lib/pkcs11/p11_mac.c b/lib/pkcs11/p11_mac.c
index c2e3bcd61..02e897e68 100644
--- a/lib/pkcs11/p11_mac.c
+++ b/lib/pkcs11/p11_mac.c
@@ -806,8 +806,10 @@ static int wrap_p11_hkdf_extract(gnutls_mac_algorithm_t _mac, const void *key,
}
_p11_provider_close_session(session);
-#endif
return 0;
+#else
+ return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
+#endif
}
static int wrap_p11_hkdf_expand(gnutls_mac_algorithm_t _mac, const void *key,
@@ -871,8 +873,10 @@ static int wrap_p11_hkdf_expand(gnutls_mac_algorithm_t _mac, const void *key,
}
_p11_provider_close_session(session);
-#endif
return 0;
+#else
+ return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
+#endif
}
static int wrap_p11_pbkdf2(gnutls_mac_algorithm_t _mac, const void *key,
@@ -952,8 +956,10 @@ static int wrap_p11_pbkdf2(gnutls_mac_algorithm_t _mac, const void *key,
}
_p11_provider_close_session(session);
-#endif
return 0;
+#else
+ return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE);
+#endif
}
gnutls_crypto_mac_st _gnutls_p11_mac_ops = {
--
2.53.0

View File

@ -0,0 +1,59 @@
From 2a03da0d3d901dd4b5c87876f1903322114f8f74 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Mon, 13 Apr 2026 18:42:56 +0200
Subject: [PATCH] lib/x509/x509_ext: avoid a heap overread in SCT extension
parser
Parsing a specially crafted SCT extension could previously lead to
a short heap overread.
The list-length validation didn't account for the 2-byte length field.
The fix now accounts for the header field length,
ensuring the parsing stays within the buffer.
Fixes: #1822
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/x509_ext.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/lib/x509/x509_ext.c b/lib/x509/x509_ext.c
index 33a4c913e..f5cabe3b6 100644
--- a/lib/x509/x509_ext.c
+++ b/lib/x509/x509_ext.c
@@ -3758,13 +3758,13 @@ int gnutls_x509_ext_ct_import_scts(const gnutls_datum_t *ext,
if (retval < 0)
return gnutls_assert_val(retval);
- if (scts_content.size < 2) {
+ if (scts_content.size < sizeof(uint16_t)) {
gnutls_free(scts_content.data);
return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
}
length = _gnutls_read_uint16(scts_content.data);
- if (length < 4 || length > scts_content.size) {
+ if (length < 4 || length > scts_content.size - sizeof(uint16_t)) {
gnutls_free(scts_content.data);
return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
}
@@ -3775,12 +3775,12 @@ int gnutls_x509_ext_ct_import_scts(const gnutls_datum_t *ext,
break;
sct_length = _gnutls_read_uint16(ptr);
- if (sct_length == 0 || sct_length > length)
- break;
-
ptr += sizeof(uint16_t);
length -= sizeof(uint16_t);
+ if (sct_length == 0 || sct_length > length)
+ break;
+
/*
* _gnutls_parse_ct_sct() will try to read exactly sct_length bytes,
* returning an error if it can't
--
2.53.0

View File

@ -0,0 +1,62 @@
From 055b2c742d6faf44c2fdaaa7e37c744a01856abc Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 14 Apr 2026 18:21:19 +0200
Subject: [PATCH 1/2] src/cfg: fix iterating in clear_options, on the error
path
Calling testing tools bundled with GnuTLS with malformed arguments
could lead to crashing them.
This change makes the error path of option parsing more robust.
Fixes: #1823
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Co-authored-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
src/cfg.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cfg.c b/src/cfg.c
index 9a9627f18..47d2d6434 100644
--- a/src/cfg.c
+++ b/src/cfg.c
@@ -370,7 +370,7 @@ static int take_option(struct options_st *options, struct cfg_option_st *option)
static void clear_options(struct options_st *options)
{
- for (size_t i = 0; options->length; i++) {
+ for (size_t i = 0; i < options->length; i++) {
clear_option(&options->data[i]);
}
}
--
2.53.0
From 9649e899b677fdd945bf8f4f67b3f9f25cea314a Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 14 Apr 2026 18:25:13 +0200
Subject: [PATCH 2/2] src/cfg: avoid a data leak in clear_options, on the error
path
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
src/cfg.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/cfg.c b/src/cfg.c
index 47d2d6434..c1f351dfb 100644
--- a/src/cfg.c
+++ b/src/cfg.c
@@ -373,6 +373,8 @@ static void clear_options(struct options_st *options)
for (size_t i = 0; i < options->length; i++) {
clear_option(&options->data[i]);
}
+ free(options->data);
+ memset(options, 0, sizeof(struct options_st));
}
cfg_option_t cfg_load(const char *filename)
--
2.53.0

View File

@ -0,0 +1,80 @@
From dcdce673516f4c578f37ae1c503f369d385ceb18 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Wed, 15 Apr 2026 21:21:46 +0900
Subject: [PATCH] key_share: zeroize derived shared secret after compositing
Signed-off-by: Daiki Ueno <ueno@gnu.org>
---
lib/ext/key_share.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/lib/ext/key_share.c b/lib/ext/key_share.c
index 84cb031ae..85c1e46ec 100644
--- a/lib/ext/key_share.c
+++ b/lib/ext/key_share.c
@@ -462,7 +462,7 @@ static int server_use_key_share_single(gnutls_session_t session,
return gnutls_assert_val(ret);
ret = append_key_datum(&session->key.key, &key);
- _gnutls_free_datum(&key);
+ _gnutls_free_key_datum(&key);
if (ret < 0)
return gnutls_assert_val(ret);
@@ -506,7 +506,7 @@ static int server_use_key_share_single(gnutls_session_t session,
return gnutls_assert_val(ret);
ret = append_key_datum(&session->key.key, &key);
- _gnutls_free_datum(&key);
+ _gnutls_free_key_datum(&key);
if (ret < 0)
return gnutls_assert_val(ret);
@@ -603,7 +603,7 @@ static int server_use_key_share_single(gnutls_session_t session,
return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
ret = append_key_datum(&session->key.key, &key);
- _gnutls_free_datum(&key);
+ _gnutls_free_key_datum(&key);
if (ret < 0)
return gnutls_assert_val(ret);
@@ -700,7 +700,7 @@ static int client_use_key_share_single(gnutls_session_t session,
return gnutls_assert_val(ret);
ret = append_key_datum(&session->key.key, &key);
- _gnutls_free_datum(&key);
+ _gnutls_free_key_datum(&key);
if (ret < 0)
return gnutls_assert_val(ret);
@@ -739,7 +739,7 @@ static int client_use_key_share_single(gnutls_session_t session,
return gnutls_assert_val(ret);
ret = append_key_datum(&session->key.key, &key);
- _gnutls_free_datum(&key);
+ _gnutls_free_key_datum(&key);
if (ret < 0)
return gnutls_assert_val(ret);
@@ -776,7 +776,7 @@ static int client_use_key_share_single(gnutls_session_t session,
return gnutls_assert_val(ret);
ret = append_key_datum(&session->key.key, &key);
- _gnutls_free_datum(&key);
+ _gnutls_free_key_datum(&key);
if (ret < 0)
return gnutls_assert_val(ret);
@@ -797,7 +797,7 @@ static int client_use_key_share_single(gnutls_session_t session,
return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
ret = append_key_datum(&session->key.key, &key);
- _gnutls_free_datum(&key);
+ _gnutls_free_key_datum(&key);
if (ret < 0)
return gnutls_assert_val(ret);
--
2.53.0

View File

@ -181,8 +181,9 @@ Signed-off-by: Daiki Ueno <ueno@gnu.org>
---
lib/pkcs11_write.c | 5 +-
tests/Makefile.am | 4 +-
tests/Makefile.in | 86 +++++++++++++++++++++++++++++++-------
tests/pkcs11/long-label.c | 164 ++++++++++++++++++++++++++++++++++++++
3 files changed, 170 insertions(+), 3 deletions(-)
4 files changed, 237 insertions(+), 22 deletions(-)
create mode 100644 tests/pkcs11/long-label.c
diff --git a/lib/pkcs11_write.c b/lib/pkcs11_write.c
@ -236,6 +237,260 @@ index 62c4ec2f9..0e4d04342 100644
endif
endif
diff --git a/tests/Makefile.in b/tests/Makefile.in
index 86c271f..334d9fb 100644
--- a/tests/Makefile.in
+++ b/tests/Makefile.in
@@ -124,7 +124,8 @@ host_triplet = @host@
@CROSS_COMPILING_FALSE@am__append_9 = tls-pthread fips-mode-pthread dtls-pthread rng-pthread
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@am__append_10 = libpkcs11mock1.la \
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ libpkcs11mock2.la \
-@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ libpkcs11mock3.la
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ libpkcs11mock3.la \
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ libpkcs11mock4.la
@ENABLE_PKCS11_FALSE@pkcs11_cert_import_url_exts_DEPENDENCIES = \
@ENABLE_PKCS11_FALSE@ $(COMMON_GNUTLS_LDADD) libutils.la \
@ENABLE_PKCS11_FALSE@ $(am__DEPENDENCIES_2)
@@ -171,7 +172,7 @@ host_triplet = @host@
@HAVE_FORK_TRUE@ resume-with-record-size-limit
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@am__append_17 = tls13/post-handshake-with-cert-pkcs11 pkcs11/tls-neg-pkcs11-no-key \
-@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ global-init-override pkcs11/distrust-after
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ global-init-override pkcs11/distrust-after pkcs11/long-label
@ENABLE_TPM2_TRUE@am__append_18 = tpm2.sh
@@ -519,7 +520,8 @@ am__EXEEXT_2 = $(am__EXEEXT_1)
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@am__EXEEXT_14 = tls13/post-handshake-with-cert-pkcs11$(EXEEXT) \
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ pkcs11/tls-neg-pkcs11-no-key$(EXEEXT) \
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ global-init-override$(EXEEXT) \
-@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ pkcs11/distrust-after$(EXEEXT)
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ pkcs11/distrust-after$(EXEEXT) \
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ pkcs11/long-label$(EXEEXT)
@WINDOWS_TRUE@am__EXEEXT_15 = win32-certopenstore$(EXEEXT)
am__EXEEXT_16 = tls13/supported_versions$(EXEEXT) \
tls13/tls12-no-tls13-exts$(EXEEXT) \
@@ -789,6 +791,17 @@ libpkcs11mock3_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
$(AM_CFLAGS) $(CFLAGS) $(libpkcs11mock3_la_LDFLAGS) $(LDFLAGS) \
-o $@
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@am_libpkcs11mock3_la_rpath =
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@libpkcs11mock4_la_DEPENDENCIES = \
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ ../gl/libgnu.la
+am__libpkcs11mock4_la_SOURCES_DIST = pkcs11/pkcs11-mock4.c
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@am_libpkcs11mock4_la_OBJECTS = \
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@ pkcs11/pkcs11-mock4.lo
+libpkcs11mock4_la_OBJECTS = $(am_libpkcs11mock4_la_OBJECTS)
+libpkcs11mock4_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libpkcs11mock4_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@am_libpkcs11mock4_la_rpath =
libutils_la_DEPENDENCIES = ../lib/libgnutls.la
am_libutils_la_OBJECTS = utils.lo seccomp.lo utils-adv.lo
libutils_la_OBJECTS = $(am_libutils_la_OBJECTS)
@@ -1793,6 +1806,8 @@ pkcs11_list_tokens_OBJECTS = pkcs11/list-tokens.$(OBJEXT)
pkcs11_list_tokens_LDADD = $(LDADD)
pkcs11_list_tokens_DEPENDENCIES = $(COMMON_GNUTLS_LDADD) libutils.la \
$(am__DEPENDENCIES_2)
+pkcs11_long_label_SOURCES = pkcs11/long-label.c
+pkcs11_long_label_OBJECTS = pkcs11/long-label.$(OBJEXT)
pkcs11_pkcs11_chainverify_SOURCES = pkcs11/pkcs11-chainverify.c
pkcs11_pkcs11_chainverify_OBJECTS = \
pkcs11/pkcs11-chainverify.$(OBJEXT)
@@ -3602,7 +3617,7 @@ am__depfiles_remade = ./$(DEPDIR)/aead-cipher-vec.Po \
pkcs11/$(DEPDIR)/gnutls_x509_crt_list_import_url.Po \
pkcs11/$(DEPDIR)/import_url_privkey_caps-pkcs11-import-url-privkey.Po \
pkcs11/$(DEPDIR)/list-objects.Po \
- pkcs11/$(DEPDIR)/list-tokens.Po \
+ pkcs11/$(DEPDIR)/list-tokens.Po pkcs11/$(DEPDIR)/long-label.Po \
pkcs11/$(DEPDIR)/pkcs11-cert-import-url-exts.Po \
pkcs11/$(DEPDIR)/pkcs11-cert-import-url4-exts.Po \
pkcs11/$(DEPDIR)/pkcs11-chainverify.Po \
@@ -3619,6 +3634,7 @@ am__depfiles_remade = ./$(DEPDIR)/aead-cipher-vec.Po \
pkcs11/$(DEPDIR)/pkcs11-mock.Plo \
pkcs11/$(DEPDIR)/pkcs11-mock2.Plo \
pkcs11/$(DEPDIR)/pkcs11-mock3.Plo \
+ pkcs11/$(DEPDIR)/pkcs11-mock4.Plo \
pkcs11/$(DEPDIR)/pkcs11-obj-import.Po \
pkcs11/$(DEPDIR)/pkcs11-obj-raw.Po \
pkcs11/$(DEPDIR)/pkcs11-pin-func.Po \
@@ -3712,16 +3728,17 @@ am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
am__v_CXXLD_0 = @echo " CXXLD " $@;
am__v_CXXLD_1 =
SOURCES = $(libpkcs11mock1_la_SOURCES) $(libpkcs11mock2_la_SOURCES) \
- $(libpkcs11mock3_la_SOURCES) $(libutils_la_SOURCES) \
- aead-cipher-vec.c alerts.c alpn-server-prec.c anonself.c \
- atfork.c auto-verify.c base64.c base64-raw.c buffer.c cert.c \
- cert-status.c cert_verify_inv_utf8.c \
- certificate_set_x509_crl.c certuniqueid.c chainverify.c \
- chainverify-unsorted.c cipher-alignment.c cipher-padding.c \
- ciphersuite-name.c client-fastopen.c client-sign-md5-rep.c \
- client_dsa_key.c $(compress_cert_conf_SOURCES) conv-utf8.c \
- crl-basic.c crl_apis.c crlverify.c crq-basic.c crq_apis.c \
- crq_key_id.c crt_apis.c crt_inv_write.c custom-urls.c \
+ $(libpkcs11mock3_la_SOURCES) $(libpkcs11mock4_la_SOURCES) \
+ $(libutils_la_SOURCES) aead-cipher-vec.c alerts.c \
+ alpn-server-prec.c anonself.c atfork.c auto-verify.c base64.c \
+ base64-raw.c buffer.c cert.c cert-status.c \
+ cert_verify_inv_utf8.c certificate_set_x509_crl.c \
+ certuniqueid.c chainverify.c chainverify-unsorted.c \
+ cipher-alignment.c cipher-padding.c ciphersuite-name.c \
+ client-fastopen.c client-sign-md5-rep.c client_dsa_key.c \
+ $(compress_cert_conf_SOURCES) conv-utf8.c crl-basic.c \
+ crl_apis.c crlverify.c crq-basic.c crq_apis.c crq_key_id.c \
+ crt_apis.c crt_inv_write.c custom-urls.c \
custom-urls-override.c cve-2008-4989.c cve-2009-1415.c \
cve-2009-1416.c dane.c dane-strcodes.c dh-compute.c \
dh-compute2.c dh-params.c dhepskself.c dhex509self.c dn.c \
@@ -3791,8 +3808,9 @@ SOURCES = $(libpkcs11mock1_la_SOURCES) $(libpkcs11mock2_la_SOURCES) \
$(pkcs11_token_raw_SOURCES) pkcs11/distrust-after.c \
pkcs11/gnutls_pcert_list_import_x509_file.c \
pkcs11/gnutls_x509_crt_list_import_url.c pkcs11/list-objects.c \
- pkcs11/list-tokens.c pkcs11/pkcs11-chainverify.c \
- pkcs11/pkcs11-combo.c pkcs11/pkcs11-ec-privkey-test.c \
+ pkcs11/list-tokens.c pkcs11/long-label.c \
+ pkcs11/pkcs11-chainverify.c pkcs11/pkcs11-combo.c \
+ pkcs11/pkcs11-ec-privkey-test.c \
pkcs11/pkcs11-eddsa-privkey-test.c pkcs11/pkcs11-get-issuer.c \
pkcs11/pkcs11-import-with-pin.c pkcs11/pkcs11-is-known.c \
pkcs11/pkcs11-obj-import.c pkcs11/pkcs11-pin-func.c \
@@ -3911,7 +3929,8 @@ SOURCES = $(libpkcs11mock1_la_SOURCES) $(libpkcs11mock2_la_SOURCES) \
x509sign-verify-rsa.c xts-key-check.c
DIST_SOURCES = $(am__libpkcs11mock1_la_SOURCES_DIST) \
$(am__libpkcs11mock2_la_SOURCES_DIST) \
- $(am__libpkcs11mock3_la_SOURCES_DIST) $(libutils_la_SOURCES) \
+ $(am__libpkcs11mock3_la_SOURCES_DIST) \
+ $(am__libpkcs11mock4_la_SOURCES_DIST) $(libutils_la_SOURCES) \
aead-cipher-vec.c alerts.c alpn-server-prec.c anonself.c \
atfork.c auto-verify.c base64.c base64-raw.c buffer.c cert.c \
cert-status.c cert_verify_inv_utf8.c \
@@ -3992,8 +4011,9 @@ DIST_SOURCES = $(am__libpkcs11mock1_la_SOURCES_DIST) \
$(am__pkcs11_token_raw_SOURCES_DIST) pkcs11/distrust-after.c \
pkcs11/gnutls_pcert_list_import_x509_file.c \
pkcs11/gnutls_x509_crt_list_import_url.c pkcs11/list-objects.c \
- pkcs11/list-tokens.c pkcs11/pkcs11-chainverify.c \
- pkcs11/pkcs11-combo.c pkcs11/pkcs11-ec-privkey-test.c \
+ pkcs11/list-tokens.c pkcs11/long-label.c \
+ pkcs11/pkcs11-chainverify.c pkcs11/pkcs11-combo.c \
+ pkcs11/pkcs11-ec-privkey-test.c \
pkcs11/pkcs11-eddsa-privkey-test.c pkcs11/pkcs11-get-issuer.c \
pkcs11/pkcs11-import-with-pin.c pkcs11/pkcs11-is-known.c \
pkcs11/pkcs11-obj-import.c pkcs11/pkcs11-pin-func.c \
@@ -6747,6 +6767,7 @@ TESTS_ENVIRONMENT = HOST_OS=$$(uname) $(am__append_31) CC="$(CC)" \
P11MOCKLIB1=$(abs_builddir)/.libs/libpkcs11mock1.so \
P11MOCKLIB2=$(abs_builddir)/.libs/libpkcs11mock2.so \
P11MOCKLIB3=$(abs_builddir)/.libs/libpkcs11mock3.so \
+ P11MOCKLIB4=$(abs_builddir)/.libs/libpkcs11mock4.so \
PKCS12_MANY_CERTS_FILE=$(srcdir)/cert-tests/data/pkcs12_5certs.p12 \
PKCS12FILE=$(srcdir)/cert-tests/data/client.p12 \
PKCS12PASSWORD=foobar \
@@ -7083,6 +7104,9 @@ ssl30_cert_key_exchange_SOURCES = common-cert-key-exchange.c ssl30-cert-key-exch
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@libpkcs11mock3_la_SOURCES = pkcs11/pkcs11-mock3.c
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@libpkcs11mock3_la_LDFLAGS = -shared -rpath $(pkglibdir) -module -no-undefined -avoid-version
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@libpkcs11mock3_la_LIBADD = ../gl/libgnu.la
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@libpkcs11mock4_la_SOURCES = pkcs11/pkcs11-mock4.c
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@libpkcs11mock4_la_LDFLAGS = -shared -rpath $(pkglibdir) -module -no-undefined -avoid-version
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@libpkcs11mock4_la_LIBADD = ../gl/libgnu.la
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_cert_import_url_exts_SOURCES = pkcs11/pkcs11-cert-import-url-exts.c
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_cert_import_url_exts_DEPENDENCIES = libpkcs11mock1.la libutils.la
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_cert_import_url4_exts_SOURCES = pkcs11/pkcs11-cert-import-url4-exts.c
@@ -7173,6 +7197,8 @@ pathbuf_CPPFLAGS = $(AM_CPPFLAGS) \
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_tls_neg_pkcs11_no_key_LDADD = $(LDADD) $(LIBDL)
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_distrust_after_DEPENDENCIES = libpkcs11mock3.la libutils.la
@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_distrust_after_LDADD = $(LDADD) $(LIBDL)
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_long_label_DEPENDENCIES = libpkcs11mock4.la libutils.la
+@ENABLE_PKCS11_TRUE@@WINDOWS_FALSE@pkcs11_long_label_LDADD = $(LDADD) $(LIBDL)
dist_check_SCRIPTS = rfc2253-escape-test.sh \
rsa-md5-collision/rsa-md5-collision.sh systemkey.sh \
$(am__append_18) $(am__append_20) $(am__append_21) \
@@ -7280,6 +7306,11 @@ pkcs11/pkcs11-mock3.lo: pkcs11/$(am__dirstamp) \
libpkcs11mock3.la: $(libpkcs11mock3_la_OBJECTS) $(libpkcs11mock3_la_DEPENDENCIES) $(EXTRA_libpkcs11mock3_la_DEPENDENCIES)
$(AM_V_CCLD)$(libpkcs11mock3_la_LINK) $(am_libpkcs11mock3_la_rpath) $(libpkcs11mock3_la_OBJECTS) $(libpkcs11mock3_la_LIBADD) $(LIBS)
+pkcs11/pkcs11-mock4.lo: pkcs11/$(am__dirstamp) \
+ pkcs11/$(DEPDIR)/$(am__dirstamp)
+
+libpkcs11mock4.la: $(libpkcs11mock4_la_OBJECTS) $(libpkcs11mock4_la_DEPENDENCIES) $(EXTRA_libpkcs11mock4_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libpkcs11mock4_la_LINK) $(am_libpkcs11mock4_la_rpath) $(libpkcs11mock4_la_OBJECTS) $(libpkcs11mock4_la_LIBADD) $(LIBS)
libutils.la: $(libutils_la_OBJECTS) $(libutils_la_DEPENDENCIES) $(EXTRA_libutils_la_DEPENDENCIES)
$(AM_V_CCLD)$(LINK) $(libutils_la_OBJECTS) $(libutils_la_LIBADD) $(LIBS)
@@ -8145,6 +8176,12 @@ pkcs11/list-tokens.$(OBJEXT): pkcs11/$(am__dirstamp) \
pkcs11/list-tokens$(EXEEXT): $(pkcs11_list_tokens_OBJECTS) $(pkcs11_list_tokens_DEPENDENCIES) $(EXTRA_pkcs11_list_tokens_DEPENDENCIES) pkcs11/$(am__dirstamp)
@rm -f pkcs11/list-tokens$(EXEEXT)
$(AM_V_CCLD)$(LINK) $(pkcs11_list_tokens_OBJECTS) $(pkcs11_list_tokens_LDADD) $(LIBS)
+pkcs11/long-label.$(OBJEXT): pkcs11/$(am__dirstamp) \
+ pkcs11/$(DEPDIR)/$(am__dirstamp)
+
+pkcs11/long-label$(EXEEXT): $(pkcs11_long_label_OBJECTS) $(pkcs11_long_label_DEPENDENCIES) $(EXTRA_pkcs11_long_label_DEPENDENCIES) pkcs11/$(am__dirstamp)
+ @rm -f pkcs11/long-label$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(pkcs11_long_label_OBJECTS) $(pkcs11_long_label_LDADD) $(LIBS)
pkcs11/pkcs11-chainverify.$(OBJEXT): pkcs11/$(am__dirstamp) \
pkcs11/$(DEPDIR)/$(am__dirstamp)
@@ -9778,6 +9815,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/import_url_privkey_caps-pkcs11-import-url-privkey.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/list-objects.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/list-tokens.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/long-label.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-cert-import-url-exts.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-cert-import-url4-exts.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-chainverify.Po@am__quote@ # am--include-marker
@@ -9794,6 +9832,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-mock.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-mock2.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-mock3.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-mock4.Plo@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-obj-import.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-obj-raw.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@pkcs11/$(DEPDIR)/pkcs11-pin-func.Po@am__quote@ # am--include-marker
@@ -13673,6 +13712,13 @@ pkcs11/distrust-after.log: pkcs11/distrust-after$(EXEEXT)
--log-file $$b.log --trs-file $$b.trs \
$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
"$$tst" $(AM_TESTS_FD_REDIRECT)
+pkcs11/long-label.log: pkcs11/long-label$(EXEEXT)
+ @p='pkcs11/long-label$(EXEEXT)'; \
+ b='pkcs11/long-label'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
win32-certopenstore.log: win32-certopenstore$(EXEEXT)
@p='win32-certopenstore$(EXEEXT)'; \
b='win32-certopenstore'; \
@@ -14211,6 +14257,7 @@ distclean: distclean-recursive
-rm -f pkcs11/$(DEPDIR)/import_url_privkey_caps-pkcs11-import-url-privkey.Po
-rm -f pkcs11/$(DEPDIR)/list-objects.Po
-rm -f pkcs11/$(DEPDIR)/list-tokens.Po
+ -rm -f pkcs11/$(DEPDIR)/long-label.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-cert-import-url-exts.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-cert-import-url4-exts.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-chainverify.Po
@@ -14227,6 +14274,7 @@ distclean: distclean-recursive
-rm -f pkcs11/$(DEPDIR)/pkcs11-mock.Plo
-rm -f pkcs11/$(DEPDIR)/pkcs11-mock2.Plo
-rm -f pkcs11/$(DEPDIR)/pkcs11-mock3.Plo
+ -rm -f pkcs11/$(DEPDIR)/pkcs11-mock4.Plo
-rm -f pkcs11/$(DEPDIR)/pkcs11-obj-import.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-obj-raw.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-pin-func.Po
@@ -14734,6 +14782,7 @@ maintainer-clean: maintainer-clean-recursive
-rm -f pkcs11/$(DEPDIR)/import_url_privkey_caps-pkcs11-import-url-privkey.Po
-rm -f pkcs11/$(DEPDIR)/list-objects.Po
-rm -f pkcs11/$(DEPDIR)/list-tokens.Po
+ -rm -f pkcs11/$(DEPDIR)/long-label.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-cert-import-url-exts.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-cert-import-url4-exts.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-chainverify.Po
@@ -14750,6 +14799,7 @@ maintainer-clean: maintainer-clean-recursive
-rm -f pkcs11/$(DEPDIR)/pkcs11-mock.Plo
-rm -f pkcs11/$(DEPDIR)/pkcs11-mock2.Plo
-rm -f pkcs11/$(DEPDIR)/pkcs11-mock3.Plo
+ -rm -f pkcs11/$(DEPDIR)/pkcs11-mock4.Plo
-rm -f pkcs11/$(DEPDIR)/pkcs11-obj-import.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-obj-raw.Po
-rm -f pkcs11/$(DEPDIR)/pkcs11-pin-func.Po
diff --git a/tests/pkcs11/long-label.c b/tests/pkcs11/long-label.c
new file mode 100644
index 000000000..a70bc9728

View File

@ -0,0 +1,470 @@
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 | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index 09779a8f3..e9ddf0403 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -853,7 +853,7 @@ static int 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;
+ data_size, frag_length;
/* Note: SSL2_HEADERS == 1 */
if (_mbuffer_get_udata_size(bufel) < handshake_header_size)
@@ -868,7 +868,7 @@ static int parse_handshake_header(gnutls_session_t session, mbuffer_st *bufel,
handshake_header_size =
SSL2_HEADERS; /* we've already read one byte */
- frag_size =
+ frag_length =
_mbuffer_get_udata_size(bufel) -
handshake_header_size; /* we've read the first byte */
@@ -879,7 +879,7 @@ static int 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 */
@@ -894,13 +894,13 @@ static int parse_handshake_header(gnutls_session_t session, mbuffer_st *bufel,
if (IS_DTLS(session)) {
hsk->sequence = _gnutls_read_uint16(&dataptr[4]);
hsk->start_offset = _gnutls_read_uint24(&dataptr[6]);
- frag_size = _gnutls_read_uint24(&dataptr[9]);
+ frag_length = _gnutls_read_uint24(&dataptr[9]);
} else {
hsk->sequence = 0;
hsk->start_offset = 0;
- frag_size = MIN((_mbuffer_get_udata_size(bufel) -
- handshake_header_size),
- hsk->length);
+ frag_length = MIN((_mbuffer_get_udata_size(bufel) -
+ handshake_header_size),
+ hsk->length);
}
/* TLS1.3: distinguish server hello versus hello retry request.
@@ -919,8 +919,8 @@ static int 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;
@@ -928,15 +928,15 @@ static int 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, (int)hsk->sequence);
+ 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 && hsk->end_offset >= hsk->length))) {
+ (frag_length > data_size ||
+ (frag_length > 0 && hsk->end_offset >= hsk->length))) {
return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
} else if (hsk->length == 0 && hsk->end_offset != 0 &&
hsk->start_offset != 0)
--
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 | 51 +++++++++++++++++++++++++-----------------------
lib/gnutls_int.h | 4 ++--
2 files changed, 29 insertions(+), 26 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index e9ddf0403..c3df8a37b 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -919,10 +919,7 @@ static int 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",
@@ -936,9 +933,11 @@ static int parse_handshake_header(gnutls_session_t session, mbuffer_st *bufel,
if (hsk->length > 0 &&
(frag_length > data_size ||
- (frag_length > 0 && hsk->end_offset >= hsk->length))) {
+ (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 &&
+ } 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);
@@ -1002,11 +1001,10 @@ static int merge_handshake_packet(gnutls_session_t session,
hsk->data.length = hsk->length;
}
- if (hsk->length > 0 && hsk->end_offset > 0 &&
- hsk->end_offset - hsk->start_offset + 1 != 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++;
@@ -1040,20 +1038,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);
}
@@ -1113,8 +1118,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,
@@ -1125,8 +1130,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);
@@ -1313,9 +1319,7 @@ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session)
&session->internals.record_buffer,
bufel, ret);
- data_size = MIN(tmp.length,
- tmp.end_offset -
- tmp.start_offset + 1);
+ data_size = MIN(tmp.length, tmp.frag_length);
ret = _gnutls_buffer_append_data(
&tmp.data,
@@ -1331,7 +1335,6 @@ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session)
ret = merge_handshake_packet(session, &tmp);
if (ret < 0)
return gnutls_assert_val(ret);
-
} while (_mbuffer_get_udata_size(bufel) > 0);
prev = bufel;
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index d1643be9d..3e5a8f361 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -460,10 +460,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 | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/lib/buffers.c b/lib/buffers.c
index c3df8a37b..af77c5c0f 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -931,14 +931,9 @@ static int 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);
return handshake_header_size;
--
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 499a92a92..cde0ca5e6 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;
@@ -462,7 +506,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 | 59 +++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
index cde0ca5e6..ce61eb947 100644
--- a/tests/mini-dtls-fragments.c
+++ b/tests/mini-dtls-fragments.c
@@ -503,6 +503,63 @@ 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, get_dtls_retransmit_timeout(),
+ get_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,
+ 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();
@@ -516,6 +573,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,851 @@
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/7] tests/mini-dtls-fragments: implement a basic DTLS test
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/Makefile.am | 7 +-
tests/mini-dtls-fragments.c | 208 ++++++++++++++++++++++++++++++++++++
2 files changed, 214 insertions(+), 1 deletion(-)
create mode 100644 tests/mini-dtls-fragments.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index aeeaaf79d..586f1952d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -241,7 +241,8 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei
x509cert-dntypes id-on-xmppAddr tls13-compat-mode ciphersuite-name \
x509-upnconstraint xts-key-check cipher-padding pkcs7-verify-double-free \
fips-rsa-sizes tls12-rehandshake-ticket pathbuf tls-force-ems \
- psk-importer privkey-derive dh-compute2 ecdh-compute2
+ psk-importer privkey-derive dh-compute2 ecdh-compute2 \
+ mini-dtls-fragments
ctests += tls-channel-binding
@@ -513,6 +514,10 @@ pathbuf_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..ee75feeb6
--- /dev/null
+++ b/tests/mini-dtls-fragments.c
@@ -0,0 +1,208 @@
+/*
+ * 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, get_dtls_retransmit_timeout(),
+ get_timeout());
+ gnutls_dtls_set_timeouts(server, get_dtls_retransmit_timeout(),
+ get_timeout());
+
+ 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 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 2/7] 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 672380b05..d54c77022 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -967,9 +967,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;
@@ -1005,44 +1007,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 3/7] 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 d54c77022..5d4d16276 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -1010,6 +1010,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 4/7] tests/mini-dtls-fragments: extend with a #1816 reproducer
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/mini-dtls-fragments.c | 120 ++++++++++++++++++++++++++++++++++++
1 file changed, 120 insertions(+)
diff --git a/tests/mini-dtls-fragments.c b/tests/mini-dtls-fragments.c
index ee75feeb6..8d5a18acd 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);
@@ -198,10 +203,125 @@ 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, get_dtls_retransmit_timeout(),
+ get_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,
+ 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 5/7] 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 8d5a18acd..93490bac2 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;
@@ -316,12 +349,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 6/7] 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 5d4d16276..62f140ed3 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -971,7 +971,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 7/7] 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 93490bac2..499a92a92 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;
@@ -218,12 +218,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");
@@ -421,14 +431,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,372 @@
From 731861b9de8dccaf7d3b0c1446833051e48670c2 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 09:48:57 +0100
Subject: [PATCH 1/5] cert-session: fix multi-entry OCSP revocation bypass
In check_ocsp_response(), the code first searched
for the SingleResponse that matches the certificate being validated.
But later, the status was retrieved from entry 0 unconditionally,
rather than from the matched resp_indx.
As a result, if entry 0 corresponded to a different certificate and was good,
while the matched entry for the peer certificate is revoked,
the revocation check could've mistakenly accept the certificate.
Reported-by: Oleh Konko (1seal) <security@1seal.org>
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1801
Fixes: #1812
Fixes: CVE-2026-3832
Fixes: GNUTLS-SA-2026-04-29-12
CVSS: 3.7 Low CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
Introduced-in: ae404fe8488dee424876b5963c00d7e041672415 3.8.9
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/cert-session.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/cert-session.c b/lib/cert-session.c
index 34a15b19e..b8a70ad00 100644
--- a/lib/cert-session.c
+++ b/lib/cert-session.c
@@ -343,9 +343,9 @@ static int check_ocsp_response(gnutls_session_t session, gnutls_x509_crt_t cert,
goto cleanup;
}
- ret = gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL, NULL,
- &cert_status, &vtime, &ntime, &rtime,
- NULL);
+ ret = gnutls_ocsp_resp_get_single(resp, resp_indx, NULL, NULL, NULL,
+ NULL, &cert_status, &vtime, &ntime,
+ &rtime, NULL);
if (ret < 0) {
_gnutls_audit_log(
session,
--
2.53.0
From d52d5f4f383e8c5d8e9a03334f2421ff35d37d2e Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 15:25:24 +0100
Subject: [PATCH 2/5] tests/ocsp-tests/ocsp-must-staple-connection: test
CVE-2026-3832
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
.../ocsp-tests/ocsp-must-staple-connection.sh | 70 +++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/tests/ocsp-tests/ocsp-must-staple-connection.sh b/tests/ocsp-tests/ocsp-must-staple-connection.sh
index 94d41ce24..5e100b9d9 100755
--- a/tests/ocsp-tests/ocsp-must-staple-connection.sh
+++ b/tests/ocsp-tests/ocsp-must-staple-connection.sh
@@ -85,6 +85,7 @@ OCSP_RESPONSE_FILE="$testdir/ms-resp.tmp"
OCSP_REQ_FILE="$testdir/ms-req.tmp"
INDEXFILE="$testdir/ocsp_index.txt"
ATTRFILE="${INDEXFILE}.attr"
+SERVER_CERT_BAD_FILE="$testdir/ms-cert-bad.pem.tmp"
stop_servers ()
{
@@ -118,6 +119,20 @@ ${CERTTOOL} \
--load-privkey "${srcdir}/ocsp-tests/certs/server_good.key" \
--template "${TEMPLATE_FILE}" --outfile "${SERVER_CERT_FILE}" 2>/dev/null
+echo "=== Generating bad server certificate ==="
+
+rm -f "$TEMPLATE_FILE"
+cp "${srcdir}/ocsp-tests/certs/server_bad.template" "$TEMPLATE_FILE"
+chmod u+w "$TEMPLATE_FILE"
+echo "ocsp_uri=http://localhost:${OCSP_PORT}/ocsp/" >>"$TEMPLATE_FILE"
+
+${CERTTOOL} \
+ --attime "${CERTDATE}" \
+ --generate-certificate --load-ca-privkey "${srcdir}/ocsp-tests/certs/ca.key" \
+ --load-ca-certificate "${srcdir}/ocsp-tests/certs/ca.pem" \
+ --load-privkey "${srcdir}/ocsp-tests/certs/server_bad.key" \
+ --template "${TEMPLATE_FILE}" --outfile "${SERVER_CERT_BAD_FILE}" 2>/dev/null
+
echo "=== Bringing OCSP server up ==="
cp "${srcdir}/ocsp-tests/certs/ocsp_index.txt" ${INDEXFILE}
@@ -486,6 +501,61 @@ kill "${TLS_SERVER_PID}"
wait "${TLS_SERVER_PID}"
unset TLS_SERVER_PID
+echo "=== Test 10: Server with revoked certificate - CVE-2026-3832 ==="
+
+# The revocation status was always mistakenly checked for the first cert.
+# Check a pair of responses: (irrelevant good unrevoked, relevant bad revoked).
+
+rm -f "${OCSP_RESPONSE_FILE}"
+
+"$FAKETIME" "${TESTDATE}" \
+ ${OPENSSL} ocsp -index "${INDEXFILE}" \
+ -issuer "${srcdir}/ocsp-tests/certs/ca.pem" \
+ -CA "${srcdir}/ocsp-tests/certs/ca.pem" \
+ -rsigner "${srcdir}/ocsp-tests/certs/ocsp-server.pem" \
+ -rkey "${srcdir}/ocsp-tests/certs/ocsp-server.key" \
+ -cert "${SERVER_CERT_FILE}" \
+ -cert "${SERVER_CERT_BAD_FILE}" \
+ -respout "${OCSP_RESPONSE_FILE}"
+
+eval "${GETPORT}"
+# Port for gnutls-serv
+TLS_SERVER_PORT=$PORT
+PORT=${TLS_SERVER_PORT}
+launch_bare_server \
+ "${SERV}" --attime "${TESTDATE}" --echo --disable-client-cert \
+ --x509keyfile="${srcdir}/ocsp-tests/certs/server_bad.key" \
+ --x509certfile="${SERVER_CERT_BAD_FILE}" \
+ --port="${TLS_SERVER_PORT}" \
+ --ocsp-response="${OCSP_RESPONSE_FILE}" --ignore-ocsp-response-errors
+TLS_SERVER_PID="${!}"
+wait_server $TLS_SERVER_PID
+
+wait_for_port "${TLS_SERVER_PORT}"
+
+out=$(
+ echo "test 123456" | \
+ "${CLI}" -d1 --attime "${TESTDATE}" --ocsp \
+ --x509cafile "${srcdir}/ocsp-tests/certs/ca.pem" \
+ --port "${TLS_SERVER_PORT}" localhost \
+ 2>&1
+ rc=$?
+)
+printf '%s\n' "$out"
+
+if test "${rc}" = "0"; then
+ echo 'ERROR: client accepted a revoked leaf (CVE-2026-3832)'
+ exit 1
+fi
+if ! echo "${out}" | grep "The certificate was revoked via OCSP" >/dev/null
+then
+ echo '"The certificate was revoked via OCSP" not found in output'
+ exit 1
+fi
+
+kill "${TLS_SERVER_PID}"
+wait "${TLS_SERVER_PID}"
+unset TLS_SERVER_PID
kill ${OCSP_PID}
wait ${OCSP_PID}
--
2.53.0
From 8cb066878ae6dcb71e19b7f104ff90a141973352 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 10:42:49 +0100
Subject: [PATCH 3/5] tests/ocsp-tests/ocsp-must-staple-connection: grep for
specific...
... error message: 'Got OCSP response with an unrelated certificate'.
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
.../ocsp-tests/ocsp-must-staple-connection.sh | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/tests/ocsp-tests/ocsp-must-staple-connection.sh b/tests/ocsp-tests/ocsp-must-staple-connection.sh
index 5e100b9d9..568aece2e 100755
--- a/tests/ocsp-tests/ocsp-must-staple-connection.sh
+++ b/tests/ocsp-tests/ocsp-must-staple-connection.sh
@@ -292,21 +292,31 @@ wait_server $TLS_SERVER_PID
wait_for_port "${TLS_SERVER_PORT}"
-echo "test 123456" | \
- "${CLI}" --attime "${TESTDATE}" --ocsp --x509cafile="${srcdir}/ocsp-tests/certs/ca.pem" \
- --port="${TLS_SERVER_PORT}" localhost
+out=$(
+ echo "test 123456" | \
+ "${CLI}" --attime "${TESTDATE}" --ocsp \
+ --x509cafile="${srcdir}/ocsp-tests/certs/ca.pem" \
+ --port="${TLS_SERVER_PORT}" localhost \
+ 2>&1
+)
rc=$?
+printf '%s\n' "$out"
if test "${rc}" = "0"; then
echo "Connecting to server with valid certificate and invalid staple succeeded"
exit 1
fi
+if ! echo "${out}" | grep "Got OCSP response with an unrelated certificate" > /dev/null
+then
+ echo '"Got OCSP response with an unrelated certificate" not found in output'
+ exit 1
+fi
+
kill "${TLS_SERVER_PID}"
wait "${TLS_SERVER_PID}"
unset TLS_SERVER_PID
-
echo "=== Test 5: Server with valid certificate - expired staple ==="
rm -f "${OCSP_RESPONSE_FILE}"
--
2.53.0
From 6a7999807d72bd2320d959092235fb06e751c332 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 10:25:41 +0100
Subject: [PATCH 4/5] cert-session: log "no responses" case separately
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/cert-session.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/lib/cert-session.c b/lib/cert-session.c
index b8a70ad00..cb8abd736 100644
--- a/lib/cert-session.c
+++ b/lib/cert-session.c
@@ -283,10 +283,16 @@ static int check_ocsp_response(gnutls_session_t session, gnutls_x509_crt_t cert,
break;
}
if (ret < 0) {
+ if (resp_indx == 0 &&
+ ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+ _gnutls_audit_log(session, "Got OCSP response with"
+ " no certificates.\n");
+ } else {
+ _gnutls_audit_log(session,
+ "Got OCSP response with"
+ " an unrelated certificate.\n");
+ }
ret = gnutls_assert_val(0);
- _gnutls_audit_log(
- session,
- "Got OCSP response with an unrelated certificate.\n");
check_failed = 1;
*ostatus |= GNUTLS_CERT_INVALID;
*ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS;
--
2.53.0
From f36276e1224719160584ae52398a0d2ceb670ac2 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 10:57:14 +0100
Subject: [PATCH 5/5] tests/ocsp-tests/ocsp-must-staple-connection: no response
case
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/Makefile.am | 4 +-
tests/ocsp-tests/certs/ocsp-staple-empty.der | Bin 0 -> 1202 bytes
.../ocsp-tests/ocsp-must-staple-connection.sh | 45 ++++++++++++++++++
3 files changed, 48 insertions(+), 1 deletion(-)
create mode 100644 tests/ocsp-tests/certs/ocsp-staple-empty.der
diff --git a/tests/Makefile.am b/tests/Makefile.am
index aeeaaf79d..f7d6254a9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -61,7 +61,9 @@ EXTRA_DIST = suppressions.valgrind eagain-common.h cert-common.h test-chains.h \
ocsp-tests/response2.der ocsp-tests/response3.der ocsp-tests/certs/ocsp_index.txt ocsp-tests/certs/ocsp_index.txt.attr \
ocsp-tests/response1.pem ocsp-tests/response2.pem \
ocsp-tests/certs/server_good.key ocsp-tests/certs/server_bad.key ocsp-tests/certs/server_good.template \
- ocsp-tests/certs/server_bad.template ocsp-tests/certs/ocsp-staple-unrelated.der ocsp-tests/suppressions.valgrind \
+ ocsp-tests/certs/server_bad.template ocsp-tests/certs/ocsp-staple-unrelated.der \
+ ocsp-tests/certs/ocsp-staple-empty.der \
+ ocsp-tests/suppressions.valgrind \
ocsp-tests/signer-verify/response-ca.der \
ocsp-tests/signer-verify/response-delegated.der \
ocsp-tests/signer-verify/response-non-delegated.der \
diff --git a/tests/ocsp-tests/certs/ocsp-staple-empty.der b/tests/ocsp-tests/certs/ocsp-staple-empty.der
new file mode 100644
index 0000000000000000000000000000000000000000..eccb9ecbfc246819fc360849867741615fb973a2
GIT binary patch
literal 1202
zcmXqLVp+$<$grS^Ww}8U%VIW8Z8k<$R(1nMMwTX)DF#g}6AZK$iLr=q9@<;)`-D4E
z!%f`Pb8})(pWKuC68uI6hGqt)#s)@4rj{n=Q3ebKylk9WZ60mkc^MhGSs9p{7#SJ*
zKKDM+|K1uQylhIwtsoDzluFGA?ZXE&PIf+c5v+0TcGfcqZWZ&R{0~*Tet2d7TGO}l
z!+Vdn(S~bZU+(aTuuf$DRg@UAPN3;@PSjFIwM^bDm2Xeic<S8X;B*T)<*;unYk|an
z?oTh~UvOueb)fY2@gLi&<T<T77cA|#{_gFSgjd%esM-nJM{~T>xBGnO&Vn0V-)09L
z*eSU130qD}Xy|i~uN$K$EwFzmD1OwO;ob#fqvNKGW+KNHT=iSTU#5BCQ0tnU0}2aP
zEc`I}(nVu^@z36~k2CM{{Ozt2QojE27PC#aKTXs1yO3lppy98VZkKq@xVgoCy=7h9
zg-e0!EBM~03M^=1_BLo@b~k8Zb~b2Y(q6#K#K^?N6ZTo+n$W$O;yI@lK0pgL0~tdp
z0|_?fP!?uk!I0GAlFYnx1;^5ojQpa^l1c+Pab80+17jm&BO@bA6Qd|`UPE&vu7S3p
zrhz&{w*rQ41%Kz@0EM8`;)49Vl++@FCPpP>+Zn-;#?N5T#K^@2ioMHBDO|iss(BB;
zyvhC0a>vyoFYdL9cF)R5je>zUAG@5-jj_MDNl2{P?%k%HQ`cNh`h97qQE7>q`>YzV
z_4iLso+qxiV_v#y;@8jTUvtQn7yetLuUsJ@v5M!ho>k>n!7?#({%v-%xBh0=-T0p4
z#<7YQvD24)IPQPOuz_XY)>}6v9g6=(i`Y%hRq#7gb+%~=8;`5~Ipg&kl|psD$2D8X
z?k|o?JXCx|_E2tGyzEZCc`GdCs~58D_gue{<^00N|I&>fV&#F;HJ)uPJ`%=s$o)^`
z5?@O$!wZ)SB&`&*jekx5*&`NrYi--H^=$1w-b|h+abdy1iRV^xcD`}9^@~c8D84e4
ziJ6gsadDYJi2)BAbEvE^BjbM-CIbcoVGvK1g~x!4jRTTrm^lsjL4y1sC9KTM><tVC
zvLFFI7BLo)=XMJVL>X7y`c&Zj-seQS)ZrN=wg&PbX=N4(1F;4X-5Vma#IEN4`@P}8
zy^mjyDsx{>+J_u7u<TT|?8tYQtwjz6+tT;`X*njD;@!f2%s%$;;b}=}TV)E2gp>>Q
zqOUiee2{c1IOO7SIYsShk7wOAcb*`bP--4<y=|h}LJt3j%yC^;KAe8<zkBf>)BSC^
z9Xw{QbMG;oyqoaq2<NgFOIf|2Jru3io2kMil33pE(pJH~skLNvU^Me4!;UY#UOJ(D
z6Eb!uDq4JDUEX>0NQnL%N3Ux<Pq*1_`!zGW%6n1TWuZ6EA2vUWlHEIj-R<GNWoJ(8
zTb%Fz<|tdy>8QmDb`{bqU$5Ws&)&GsqiV&~>(|<zNy~`vsNSk$$XvG}G4Vk9vMWFP
cp0LfeGq;*rZ97x{|Gzs$UE<vmlQK%#0A#V}1poj5
literal 0
HcmV?d00001
diff --git a/tests/ocsp-tests/ocsp-must-staple-connection.sh b/tests/ocsp-tests/ocsp-must-staple-connection.sh
index 568aece2e..8dcbb6869 100755
--- a/tests/ocsp-tests/ocsp-must-staple-connection.sh
+++ b/tests/ocsp-tests/ocsp-must-staple-connection.sh
@@ -317,6 +317,51 @@ kill "${TLS_SERVER_PID}"
wait "${TLS_SERVER_PID}"
unset TLS_SERVER_PID
+echo "=== Test 4.1: Server with valid certificate - no response staple ==="
+
+rm -f "${OCSP_RESPONSE_FILE}"
+cp "${srcdir}/ocsp-tests/certs/ocsp-staple-empty.der" "${OCSP_RESPONSE_FILE}"
+
+eval "${GETPORT}"
+# Port for gnutls-serv
+TLS_SERVER_PORT=$PORT
+PORT=${TLS_SERVER_PORT}
+launch_bare_server \
+ "${SERV}" --attime "${TESTDATE}" --echo --disable-client-cert \
+ --x509keyfile="${srcdir}/ocsp-tests/certs/server_good.key" \
+ --x509certfile="${SERVER_CERT_FILE}" \
+ --port="${TLS_SERVER_PORT}" \
+ --ocsp-response="${OCSP_RESPONSE_FILE}" --ignore-ocsp-response-errors
+TLS_SERVER_PID="${!}"
+wait_server $TLS_SERVER_PID
+
+wait_for_port "${TLS_SERVER_PORT}"
+
+out=$(
+ echo "test 123456" | \
+ "${CLI}" --attime "${TESTDATE}" --ocsp \
+ --x509cafile="${srcdir}/ocsp-tests/certs/ca.pem" \
+ --port="${TLS_SERVER_PORT}" localhost \
+ 2>&1
+)
+rc=$?
+printf '%s\n' "$out"
+
+if test "${rc}" = "0"; then
+ echo "Connecting to server with valid certificate and no response staple succeeded"
+ exit 1
+fi
+
+if ! echo "${out}" | grep "Got OCSP response with no certificates" > /dev/null
+then
+ echo '"Got OCSP response with no certificates" not found in output'
+ exit 1
+fi
+
+kill "${TLS_SERVER_PID}"
+wait "${TLS_SERVER_PID}"
+unset TLS_SERVER_PID
+
echo "=== Test 5: Server with valid certificate - expired staple ==="
rm -f "${OCSP_RESPONSE_FILE}"
--
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 a89728451..410022239 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"
@@ -100,7 +101,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)
{
@@ -116,6 +117,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,
@@ -141,8 +158,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 71216b700..e85c03aae 100644
--- a/tests/name-constraints.c
+++ b/tests/name-constraints.c
@@ -366,6 +366,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 62f140ed3..48f4a3210 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -971,8 +971,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 48f4a3210..09779a8f3 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -844,11 +844,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,443 @@
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 1/5] tests/pskself2: extend with RSA-PSK support
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/pskself2.c | 79 ++++++++++++++++++++++++++++++++----------------
1 file changed, 53 insertions(+), 26 deletions(-)
diff --git a/tests/pskself2.c b/tests/pskself2.c
index e16146884..04283ca08 100644
--- a/tests/pskself2.c
+++ b/tests/pskself2.c
@@ -27,6 +27,7 @@
#include "config.h"
#endif
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -51,6 +52,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.
*/
@@ -65,12 +67,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;
@@ -110,6 +113,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
@@ -165,6 +173,8 @@ end:
gnutls_free(user.data);
gnutls_psk_free_client_credentials(pskcred);
+ if (xcred)
+ gnutls_certificate_free_credentials(xcred);
gnutls_global_deinit();
}
@@ -192,9 +202,10 @@ static int pskfunc(gnutls_session_t session, const gnutls_datum_t *username,
return 0;
}
-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;
@@ -214,6 +225,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
@@ -222,6 +240,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);
@@ -278,6 +299,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();
@@ -285,7 +308,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;
@@ -311,42 +334,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:-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", 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:+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", 0);
- run_test("NORMAL:-KX-ALL:+ECDHE-PSK", 0);
- run_test("NORMAL:-KX-ALL:+DHE-PSK", 0);
+ 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);
+
+ /* 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 2/5] 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, 1 insertion(+), 2 deletions(-)
diff --git a/lib/auth/rsa_psk.c b/lib/auth/rsa_psk.c
index 9f97569c5..8305f8386 100644
--- a/lib/auth/rsa_psk.c
+++ b/lib/auth/rsa_psk.c
@@ -321,8 +321,7 @@ static int _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session,
* filled in if the key is not found.
*/
ret = _gnutls_psk_pwd_find_entry(session, info->username,
- strlen(info->username), &pwd_psk,
- NULL);
+ info->username_len, &pwd_psk, NULL);
if (ret < 0)
return gnutls_assert_val(ret);
--
2.53.0
From 83e579a80ec4f165dc3b8e670d879370081f5945 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Tue, 21 Apr 2026 19:19:42 +0200
Subject: [PATCH 3/5] tests/pskself2: test username with NUL in the middle
(#1850)
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/pskself2.c | 33 +++++++++++++++++++++------------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/tests/pskself2.c b/tests/pskself2.c
index 04283ca08..247000077 100644
--- a/tests/pskself2.c
+++ b/tests/pskself2.c
@@ -86,12 +86,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,
@@ -189,14 +192,20 @@ end:
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;
@@ -209,8 +218,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
*/
@@ -262,8 +271,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 4/5] 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 247000077..07f08adcd 100644
--- a/tests/pskself2.c
+++ b/tests/pskself2.c
@@ -75,7 +75,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;
@@ -193,7 +193,7 @@ 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 };
+ const unsigned char expected_key[] = { 0xDE, 0xAD, 0x00, 0xBE, 0xEF };
if (debug)
printf("psk: Got username with length %d\n", username->size);
@@ -204,9 +204,9 @@ static int 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 5/5] 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 | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/lib/auth/psk_passwd.c b/lib/auth/psk_passwd.c
index 518756e7d..abefd0d4a 100644
--- a/lib/auth/psk_passwd.c
+++ b/lib/auth/psk_passwd.c
@@ -78,7 +78,7 @@ ATTRIBUTE_NONNULL((1, 2))
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, const char *line,
return false;
if (line_size == 0)
- return (username->size == 0);
+ return false;
/* move to first ':' */
i = 0;
@@ -99,6 +99,9 @@ static bool username_matches(const gnutls_datum_t *username, const char *line,
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];
@@ -107,19 +110,17 @@ static bool username_matches(const gnutls_datum_t *username, const char *line,
if (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;
}
/* Randomizes the given password entry. It actually sets a random password.
--
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 04722bdf4..232d466c4 100644
--- a/lib/x509/name_constraints.c
+++ b/lib/x509/name_constraints.c
@@ -723,9 +723,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 70376aaa7..3ff8d6c60 100644
--- a/tests/name-constraints-merge.c
+++ b/tests/name-constraints-merge.c
@@ -473,6 +473,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,506 @@
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/5] 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 92fcab2c8..ce3d2ca39 100644
--- a/lib/x509/virt-san.c
+++ b/lib/x509/virt-san.c
@@ -108,11 +108,8 @@ int _gnutls_alt_name_assign_virt_type(struct name_st *name, unsigned type,
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:
@@ -120,15 +117,19 @@ int _gnutls_alt_name_assign_virt_type(struct name_st *name, unsigned type,
&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/5] 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 | 6 ++++++
lib/x509/virt-san.c | 24 ++++++++++++++++++++++++
lib/x509/x509.c | 3 ++-
6 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index 964366ded..acce69301 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -2683,6 +2683,7 @@ void gnutls_psk_set_server_params_function(gnutls_psk_server_credentials_t res,
* @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_MSUSERPRINCIPAL: 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.
*/
@@ -2700,7 +2701,8 @@ typedef enum gnutls_x509_subject_alt_name_t {
Used by gnutls_x509_crt_get_subject_alt_othername_oid. */
GNUTLS_SAN_OTHERNAME_XMPP = 1000,
GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL,
- GNUTLS_SAN_OTHERNAME_MSUSERPRINCIPAL
+ GNUTLS_SAN_OTHERNAME_MSUSERPRINCIPAL,
+ 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 f039af15b..ed9409f62 100644
--- a/lib/x509/common.h
+++ b/lib/x509/common.h
@@ -107,6 +107,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 MSUSER_PRINCIPAL_NAME_OID "1.3.6.1.4.1.311.20.2.3"
+#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 PKIX1_RSA_OAEP_P_SPECIFIED_OID "1.9"
diff --git a/lib/x509/name_constraints.c b/lib/x509/name_constraints.c
index 3c6e30630..d3c624284 100644
--- a/lib/x509/name_constraints.c
+++ b/lib/x509/name_constraints.c
@@ -146,7 +146,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_OTHERNAME_MSUSERPRINCIPAL) {
+ type != GNUTLS_SAN_OTHERNAME_MSUSERPRINCIPAL &&
+ 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 4e983c659..78ad9cad7 100644
--- a/lib/x509/output.c
+++ b/lib/x509/output.c
@@ -121,6 +121,7 @@ static void print_name(gnutls_buffer_st *str, const char *prefix, unsigned type,
if ((type == GNUTLS_SAN_DNSNAME || type == GNUTLS_SAN_OTHERNAME_XMPP ||
type == GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL ||
type == GNUTLS_SAN_OTHERNAME_MSUSERPRINCIPAL ||
+ type == GNUTLS_SAN_OTHERNAME_SRV ||
type == GNUTLS_SAN_RFC822NAME || type == GNUTLS_SAN_URI) &&
sname != NULL && strlen(sname) != name->size) {
adds(str, _("warning: SAN contains an embedded NUL, "
@@ -180,6 +181,11 @@ static void print_name(gnutls_buffer_st *str, const char *prefix, unsigned type,
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 ce3d2ca39..e25b79b1c 100644
--- a/lib/x509/virt-san.c
+++ b/lib/x509/virt-san.c
@@ -45,6 +45,9 @@ static int san_othername_to_virtual(const char *oid, size_t size)
memcmp(oid, MSUSER_PRINCIPAL_NAME_OID,
sizeof(MSUSER_PRINCIPAL_NAME_OID) - 1) == 0)
return GNUTLS_SAN_OTHERNAME_MSUSERPRINCIPAL;
+ 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;
@@ -59,6 +62,8 @@ static const char *virtual_to_othername_oid(unsigned type)
return KRB5_PRINCIPAL_OID;
case GNUTLS_SAN_OTHERNAME_MSUSERPRINCIPAL:
return MSUSER_PRINCIPAL_NAME_OID;
+ case GNUTLS_SAN_OTHERNAME_SRV:
+ return SRV_OID;
default:
return NULL;
}
@@ -119,6 +124,16 @@ int _gnutls_alt_name_assign_virt_type(struct name_st *name, unsigned type,
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);
}
@@ -192,6 +207,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 a55389b34..e1d8c3cba 100644
--- a/lib/x509/x509.c
+++ b/lib/x509/x509.c
@@ -1562,7 +1562,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;
--
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/5] x509/hostname-verify: refactor and simplify CN fallback
logic
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
lib/x509/hostname-verify.c | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/lib/x509/hostname-verify.c b/lib/x509/hostname-verify.c
index 04e17aa23..dda19b54d 100644
--- a/lib/x509/hostname-verify.c
+++ b/lib/x509/hostname-verify.c
@@ -108,7 +108,7 @@ unsigned 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) \
+#define PRECLUDES_CN_FALLBACK(san) \
(san == GNUTLS_SAN_DNSNAME || san == GNUTLS_SAN_IPADDRESS)
/**
@@ -151,13 +151,12 @@ unsigned 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 */
@@ -213,9 +212,10 @@ hostname_fallback:
ret = gnutls_x509_crt_get_subject_alt_name(cert, i, dnsname,
&dnsnamesize, NULL);
+ if (PRECLUDES_CN_FALLBACK(ret))
+ cn_fallback_allowed = false;
+
if (ret == GNUTLS_SAN_DNSNAME) {
- found_dnsname = 1;
-
if (memchr(dnsname, '\0', dnsnamesize)) {
_gnutls_debug_log(
"certificate has %s with embedded null in name\n",
@@ -236,13 +236,10 @@ hostname_fallback:
ret = 1;
goto cleanup;
}
- } else {
- if (IS_SAN_SUPPORTED(ret))
- have_other_addresses = 1;
}
}
- if (!have_other_addresses && !found_dnsname &&
+ 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.
--
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 4/5] 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 dda19b54d..c772cece2 100644
--- a/lib/x509/hostname-verify.c
+++ b/lib/x509/hostname-verify.c
@@ -108,8 +108,9 @@ unsigned 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 5/5] tests/hostname-check: extend to exercise no-CN-fallback
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
tests/hostname-check.c | 140 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
diff --git a/tests/hostname-check.c b/tests/hostname-check.c
index 4edda6c40..4357f33f3 100644
--- a/tests/hostname-check.c
+++ b/tests/hostname-check.c
@@ -804,6 +804,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)
{
gnutls_x509_crt_t x509;
@@ -1175,6 +1268,53 @@ void doit(void)
if (ret)
fail("%d: Hostname incorrectly matches (%d)\n", __LINE__, ret);
+ 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,245 @@
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 dbef0bb86..3c22ffed3 100644
--- a/lib/x509/email-verify.c
+++ b/lib/x509/email-verify.c
@@ -42,7 +42,7 @@ unsigned gnutls_x509_crt_check_email(gnutls_x509_crt_t cert, const char *email,
{
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;
@@ -76,7 +76,7 @@ unsigned gnutls_x509_crt_check_email(gnutls_x509_crt_t cert, const char *email,
cert, i, rfc822name, &rfc822namesize, NULL);
if (ret == GNUTLS_SAN_RFC822NAME) {
- found_rfc822name = 1;
+ dn_fallback_allowed = false;
if (memchr(rfc822name, '\0', rfc822namesize)) {
_gnutls_debug_log(
@@ -102,12 +102,10 @@ unsigned gnutls_x509_crt_check_email(gnutls_x509_crt_t cert, const char *email,
}
}
- 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,
--
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 3c22ffed3..c6cf7a948 100644
--- a/lib/x509/email-verify.c
+++ b/lib/x509/email-verify.c
@@ -75,6 +75,20 @@ unsigned gnutls_x509_crt_check_email(gnutls_x509_crt_t cert, const char *email,
ret = gnutls_x509_crt_get_subject_alt_name(
cert, i, rfc822name, &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 c772cece2..2f1865a27 100644
--- a/lib/x509/hostname-verify.c
+++ b/lib/x509/hostname-verify.c
@@ -213,6 +213,20 @@ hostname_fallback:
ret = gnutls_x509_crt_get_subject_alt_name(cert, i, dnsname,
&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.sh | 11 ++++++
tests/hostname-check.c | 34 +++++++++++++++++++
3 files changed, 61 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.sh b/tests/cert-tests/email.sh
index 68fbe3e12..8d3ca3317 100644
--- a/tests/cert-tests/email.sh
+++ b/tests/cert-tests/email.sh
@@ -95,5 +95,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 4357f33f3..4a4cdf956 100644
--- a/tests/hostname-check.c
+++ b/tests/hostname-check.c
@@ -897,6 +897,25 @@ 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)
{
gnutls_x509_crt_t x509;
@@ -1315,6 +1334,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,61 @@
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 64b85a2df..1dff578f2 100644
--- a/lib/pkcs11_write.c
+++ b/lib/pkcs11_write.c
@@ -1266,10 +1266,9 @@ int gnutls_pkcs11_token_set_pin(const char *token_url, const char *oldpin,
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;
}
@@ -1290,9 +1289,11 @@ int gnutls_pkcs11_token_set_pin(const char *token_url, const char *oldpin,
oldpin_size = L(oldpin);
if (!(sinfo.tinfo.flags & CKF_PROTECTED_AUTHENTICATION_PATH)) {
- if (newpin == NULL)
- return gnutls_assert_val(
+ if (newpin == NULL) {
+ ret = gnutls_assert_val(
GNUTLS_E_INVALID_REQUEST);
+ goto finish;
+ }
if (oldpin == NULL) {
struct pin_info_st pin_info;
@@ -1324,6 +1325,7 @@ int gnutls_pkcs11_token_set_pin(const char *token_url, const char *oldpin,
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 911aeff93..38228613c 100644
--- a/lib/x509/pkcs12_bag.c
+++ b/lib/x509/pkcs12_bag.c
@@ -375,7 +375,7 @@ int 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 4d181327b..496c378b3 100644
--- a/lib/auth/rsa.c
+++ b/lib/auth/rsa.c
@@ -158,6 +158,7 @@ static int 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) {
@@ -180,6 +181,10 @@ static int 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 cc92b4aa9..dba40119e 100644
--- a/lib/auth/rsa_psk.c
+++ b/lib/auth/rsa_psk.c
@@ -257,6 +257,7 @@ static int _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session,
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);
@@ -313,6 +314,10 @@ static int _gnutls_proc_rsa_psk_client_kx(gnutls_session_t session,
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 7f5db8d26..ea5054978 100644
--- a/lib/pkcs11_privkey.c
+++ b/lib/pkcs11_privkey.c
@@ -838,7 +838,7 @@ int _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

@ -0,0 +1,360 @@
From 1e627aa5ad95c6dc0518d94e9a009997b081a1ab Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Wed, 1 Apr 2026 18:57:21 +0900
Subject: [PATCH 1/2] gnutls_cipher_decrypt3: make PKCS#7 unpadding branch free
This tries to make the logic of PKCS#7 padding removal constant-time,
by removing potential branching operations.
Reported-by: Doria Tang of Stony Brook University
Fixes: #1815
Fixes: CVE-2026-5419
Fixes: GNUTLS-SA-2026-04-29-13
CVSS: 3.7 Low CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
Signed-off-by: Daiki Ueno <ueno@gnu.org>
---
lib/crypto-api.c | 54 +++++++++++++++++------
lib/libgnutls.map | 2 +
tests/Makefile.am | 2 +-
tests/pkcs7-pad.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 153 insertions(+), 14 deletions(-)
create mode 100644 tests/pkcs7-pad.c
diff --git a/lib/crypto-api.c b/lib/crypto-api.c
index 01539d5b5..32143e9de 100644
--- a/lib/crypto-api.c
+++ b/lib/crypto-api.c
@@ -498,6 +498,39 @@ error:
return ret;
}
+/* If succeeds, returns the number of padding bytes to be removed;
+ * zero otherwise.
+ */
+unsigned int _gnutls_pkcs7_unpad(const uint8_t *block, unsigned int block_size)
+{
+ uint8_t padding = block[block_size - 1];
+ volatile unsigned int mask = ~0;
+ volatile unsigned int count = 0;
+
+ /* Count consecutive PADDING bytes from the end, in a
+ * constant-time manner.
+ */
+ for (size_t i = block_size; i > 0; i--) {
+ volatile unsigned int mask2;
+
+ mask2 = -(unsigned int)(block[i - 1] == padding);
+ mask2 &= -(unsigned int)(count < padding);
+
+ /* MASK is initially ~0 and will be flipped to 0 upon first
+ * non-padding bytes.
+ */
+ mask &= mask2;
+ count += 1 & mask;
+ }
+
+ /* PADDING == 0 is effectively excluded here, given COUNT
+ * will never be 0.
+ */
+ mask = -(unsigned int)(count <= block_size);
+ mask &= -(unsigned int)(count == padding);
+ return count & mask;
+}
+
/**
* gnutls_cipher_decrypt3:
* @handle: is a #gnutls_cipher_hd_t type
@@ -532,22 +565,17 @@ int gnutls_cipher_decrypt3(gnutls_cipher_hd_t handle, const void *ctext,
if (_gnutls_cipher_type(h->ctx_enc.e) == CIPHER_BLOCK &&
(flags & GNUTLS_CIPHER_PADDING_PKCS7)) {
uint8_t *p = ptext;
- uint8_t padding = p[*ptext_len - 1];
- if (!padding ||
- padding > _gnutls_cipher_get_block_size(h->ctx_enc.e)) {
- return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
- }
- /* Check that the prior bytes are all PADDING */
- for (size_t i = *ptext_len - padding; i < *ptext_len; i++) {
- if (padding != p[*ptext_len - 1]) {
- return gnutls_assert_val(
- GNUTLS_E_DECRYPTION_FAILED);
- }
- }
+ size_t block_size = _gnutls_cipher_get_block_size(h->ctx_enc.e);
+ uint8_t *block = &p[*ptext_len - block_size];
+ unsigned int padding = _gnutls_pkcs7_unpad(block, block_size);
+ volatile unsigned int mask;
+
+ mask = -(unsigned int)(padding == 0);
+ ret = GNUTLS_E_DECRYPTION_FAILED & mask;
*ptext_len -= padding;
}
- return 0;
+ return ret;
}
/**
diff --git a/lib/libgnutls.map b/lib/libgnutls.map
index c2366833d..e22150033 100644
--- a/lib/libgnutls.map
+++ b/lib/libgnutls.map
@@ -1560,4 +1560,6 @@ GNUTLS_PRIVATE_3_4 {
_gnutls_pathbuf_append;
_gnutls_pathbuf_truncate;
_gnutls_pathbuf_deinit;
+ # needed by tests/pkcs7-pad
+ _gnutls_pkcs7_unpad;
} GNUTLS_3_4;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b0311169c..3bc3d0340 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -241,7 +241,7 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei
x509cert-dntypes id-on-xmppAddr tls13-compat-mode ciphersuite-name \
x509-upnconstraint xts-key-check cipher-padding pkcs7-verify-double-free \
fips-rsa-sizes tls12-rehandshake-ticket pathbuf tls-force-ems \
- psk-importer privkey-derive dh-compute2 ecdh-compute2 \
+ psk-importer privkey-derive dh-compute2 ecdh-compute2 pkcs7-pad \
mini-dtls-fragments
ctests += tls-channel-binding
diff --git a/tests/pkcs7-pad.c b/tests/pkcs7-pad.c
new file mode 100644
index 000000000..4a7c231c8
--- /dev/null
+++ b/tests/pkcs7-pad.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ *
+ * 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 General Public License
+ * along with GnuTLS. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* Test that _gnutls_pkcs7_unpad is branch-free, using valgrind */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdint.h>
+#include <string.h>
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include "utils.h"
+
+static inline void _gnutls_memory_mark_undefined(void *addr, size_t size)
+{
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ if (RUNNING_ON_VALGRIND)
+ VALGRIND_MAKE_MEM_UNDEFINED(addr, size);
+#endif
+}
+
+static inline void _gnutls_memory_mark_defined(void *addr, size_t size)
+{
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ if (RUNNING_ON_VALGRIND)
+ VALGRIND_MAKE_MEM_DEFINED(addr, size);
+#endif
+}
+
+extern unsigned int _gnutls_pkcs7_unpad(const uint8_t *block,
+ unsigned int block_size);
+
+static unsigned int wrap_pkcs7_unpad(uint8_t *block, unsigned int block_size)
+{
+ unsigned int padding;
+
+ _gnutls_memory_mark_undefined(block, block_size);
+
+ padding = _gnutls_pkcs7_unpad(block, block_size);
+
+ _gnutls_memory_mark_defined(block, block_size);
+ _gnutls_memory_mark_defined(&padding, sizeof(padding));
+
+ return padding;
+}
+
+#define PAD 5
+
+void doit(void)
+{
+ uint8_t block[16];
+ unsigned int padding;
+
+ memset(block, 0xFF, sizeof(block));
+ memset(&block[sizeof(block) - PAD], PAD, PAD);
+
+ padding = wrap_pkcs7_unpad(block, sizeof(block));
+ if (padding != PAD)
+ fail("padding should be %d\n", PAD);
+
+ /* The last padding byte exceeds the block size */
+ block[sizeof(block) - 1] = sizeof(block) + 1;
+ padding = wrap_pkcs7_unpad(block, sizeof(block));
+ if (padding != 0)
+ fail("padding should be 0\n");
+ block[sizeof(block) - 1] = PAD;
+
+ /* The last padding byte is zero */
+ block[sizeof(block) - 1] = 0;
+ padding = wrap_pkcs7_unpad(block, sizeof(block));
+ if (padding != 0)
+ fail("padding should be 0\n");
+ block[sizeof(block) - 1] = PAD;
+
+ /* The first padding byte is invalid */
+ block[sizeof(block) - PAD] = PAD + 1;
+ padding = wrap_pkcs7_unpad(block, sizeof(block));
+ if (padding != 0)
+ fail("padding should be 0\n");
+ block[sizeof(block) - PAD] = PAD;
+
+ /* The byte before the first padding equals to PAD */
+ block[sizeof(block) - PAD - 1] = PAD;
+ padding = wrap_pkcs7_unpad(block, sizeof(block));
+ if (padding != PAD)
+ fail("padding should be %d\n", PAD);
+ block[sizeof(block) - PAD - 1] = 0xFF;
+}
--
2.53.0
From 74d8f53ed35a25c72c3756c5dfee52012dcf955e Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@gnu.org>
Date: Wed, 1 Apr 2026 19:01:50 +0900
Subject: [PATCH 2/2] tests/cipher-padding: exercise invalid padding case
This adds a negative test case, where a PKCS#7 padding is manipulated.
Signed-off-by: Daiki Ueno <ueno@gnu.org>
---
tests/cipher-padding.c | 53 +++++++++++++++++++++++++++++++-----------
1 file changed, 40 insertions(+), 13 deletions(-)
diff --git a/tests/cipher-padding.c b/tests/cipher-padding.c
index c5cca333f..2ee3588f5 100644
--- a/tests/cipher-padding.c
+++ b/tests/cipher-padding.c
@@ -43,9 +43,11 @@ static void start(gnutls_cipher_algorithm_t algo, size_t plaintext_size,
uint8_t key16[64];
uint8_t iv16[32];
uint8_t plaintext[128];
+ uint8_t plaintext2[128];
uint8_t ciphertext[128];
size_t block_size;
size_t size;
+ size_t ciphertext_size;
gnutls_datum_t key, iv;
success("%s %zu %u\n", gnutls_cipher_get_name(algo), plaintext_size,
@@ -80,39 +82,41 @@ static void start(gnutls_cipher_algorithm_t algo, size_t plaintext_size,
}
/* Get the ciphertext size */
- ret = gnutls_cipher_encrypt3(ch, plaintext, plaintext_size, NULL, &size,
- flags);
+ ret = gnutls_cipher_encrypt3(ch, plaintext, plaintext_size, NULL,
+ &ciphertext_size, flags);
if (ret < 0) {
fail("gnutls_cipher_encrypt3 failed\n");
}
if (flags & GNUTLS_CIPHER_PADDING_PKCS7) {
- if (size <= plaintext_size) {
+ if (ciphertext_size <= plaintext_size) {
fail("no padding appended\n");
}
- if (size != CLAMP(plaintext_size, block_size)) {
- fail("size does not match: %zu (expected %zu)\n", size,
+ if (ciphertext_size != CLAMP(plaintext_size, block_size)) {
+ fail("size does not match: %zu (expected %zu)\n",
+ ciphertext_size,
CLAMP(plaintext_size, block_size));
}
} else {
- if (size != plaintext_size) {
- fail("size does not match: %zu (expected %zu)\n", size,
- plaintext_size);
+ if (ciphertext_size != plaintext_size) {
+ fail("size does not match: %zu (expected %zu)\n",
+ ciphertext_size, plaintext_size);
}
}
/* Encrypt with padding */
ret = gnutls_cipher_encrypt3(ch, plaintext, plaintext_size, ciphertext,
- &size, flags);
+ &ciphertext_size, flags);
if (ret < 0) {
fail("gnutls_cipher_encrypt3 failed\n");
}
/* Decrypt with padding */
- ret = gnutls_cipher_decrypt3(ch, ciphertext, size, ciphertext, &size,
- flags);
+ size = ciphertext_size;
+ ret = gnutls_cipher_decrypt3(ch, ciphertext, ciphertext_size,
+ plaintext2, &size, flags);
if (ret < 0) {
- fail("gnutls_cipher_encrypt3 failed\n");
+ fail("gnutls_cipher_decrypt3 failed\n");
}
if (size != plaintext_size) {
@@ -120,10 +124,33 @@ static void start(gnutls_cipher_algorithm_t algo, size_t plaintext_size,
plaintext_size);
}
- if (memcmp(ciphertext, plaintext, size) != 0) {
+ if (memcmp(plaintext2, plaintext, size) != 0) {
fail("plaintext does not match\n");
}
+ if ((flags & GNUTLS_CIPHER_PADDING_PKCS7) &&
+ plaintext_size % block_size != 0) {
+ /* Encrypt with manual padding */
+ memset(&plaintext[plaintext_size],
+ ciphertext_size - plaintext_size,
+ ciphertext_size - plaintext_size);
+ /* Insert a wrong padding byte */
+ plaintext[plaintext_size] = block_size;
+ ret = gnutls_cipher_encrypt3(ch, plaintext, ciphertext_size,
+ ciphertext, &ciphertext_size, 0);
+ if (ret < 0) {
+ fail("gnutls_cipher_encrypt3 failed\n");
+ }
+
+ /* Decrypt with padding */
+ size = ciphertext_size;
+ ret = gnutls_cipher_decrypt3(ch, ciphertext, ciphertext_size,
+ plaintext, &size, flags);
+ if (ret != GNUTLS_E_DECRYPTION_FAILED) {
+ fail("gnutls_cipher_decrypt3 succeeded\n");
+ }
+ }
+
gnutls_cipher_deinit(ch);
}
--
2.53.0

View File

@ -13,7 +13,7 @@ print(string.sub(hash, 0, 16))
}
Version: 3.8.10
Release: 3%{?dist}
Release: 4%{?dist}
# not upstreamed
Patch: gnutls-3.2.7-rpath.patch
Patch: gnutls-3.7.2-enable-intel-cet.patch
@ -35,13 +35,41 @@ Patch: gnutls-3.8.10-rhel9-revert-pbmac1-fips-default.patch
# * da1df0a31 fips: Allow SigVer only with RSA keys with modulus >= 2048 bits
Patch: gnutls-3.8.10-rhel9-revert-rsa-less-than-2048.patch
# CVE fixes backported from 3.8.12 release
# upstreamed: https://gitlab.com/gnutls/gnutls/-/merge_requests/2041
Patch: gnutls-3.8.10-CVE-2025-9820.patch
# upstreamed: https://gitlab.com/gnutls/gnutls/-/merge_requests/2062
Patch: gnutls-3.8.10-CVE-2025-14831.patch
# intentionally omitted: CVE-2026-1584, since 3.8.10 is not vulnerable
# CVE fixes backported from 3.8.13 release
# (https://gitlab.com/gnutls/gnutls/-/merge_requests/2102)
Patch: gnutls-3.8.10-CVE-2026-33846-dtls-len.patch
Patch: gnutls-3.8.10-CVE-2026-42009-dtls-qsort.patch
Patch: gnutls-3.8.10-CVE-2026-33845-dtls-uflow.patch
Patch: gnutls-3.8.10-CVE-2026-42010-psk-nul.patch
Patch: gnutls-3.8.10-CVE-2026-3833-nc-case.patch
Patch: gnutls-3.8.10-CVE-2026-42011-nc-intersect.patch
Patch: gnutls-3.8.10-CVE-2026-42012-url-san-cn.patch
Patch: gnutls-3.8.10-CVE-2026-42013-oversized-san.patch
Patch: gnutls-3.8.10-CVE-2026-42014-so-pin-uaf.patch
Patch: gnutls-3.8.10-CVE-2026-5260-p11-rsa-overread.patch
Patch: gnutls-3.8.10-CVE-2026-42015-p12-bag32.patch
Patch: gnutls-3.8.10-CVE-2026-3832-ocsp-rev-0.patch
Patch: gnutls-3.8.10-CVE-2026-5419-p7-constant-time.patch
# non-CVE security fixes from the same release
Patch: gnutls-3.8.10-1808-psk-rehandshake.patch
Patch: gnutls-3.8.10-1810-ocsp-truncated-eku.patch
Patch: gnutls-3.8.10-1813-p11p-aes-ephemeral.patch
Patch: gnutls-3.8.10-1818-rsa-coprime.patch
Patch: gnutls-3.8.10-1818-pem-parsing.patch
Patch: gnutls-3.8.10-1819-dblfree-mid-import.patch
Patch: gnutls-3.8.10-1822-sct-overread.patch
Patch: gnutls-3.8.10-1841-hybrid-kx-zeroize.patch
Patch: gnutls-3.8.10-1823-cfg-clear-options.patch
Patch: gnutls-3.8.10-1817-security-parameters.patch
Patch: gnutls-3.8.10-1820-p11p-kdf.patch
%bcond_without bootstrap
%bcond_without dane
%if 0%{?rhel}
@ -486,6 +514,32 @@ make check %{?_smp_mflags} GNUTLS_SYSTEM_PRIORITY_FILE=/dev/null XFAIL_TESTS="$x
%endif
%changelog
* Thu Apr 30 2026 Alexander Sosedkin <asosedkin@redhat.com> - 3.8.10-4
- 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 CVE-2026-3832 (OCSP, Low, revocation bypass)
- Fix CVE-2026-5419 (PKCS#7, Low, timing side-channel)
- Fix upstream security issue #1808 (PSK rehandshake)
- Fix upstream security issue #1810 (EKU OID prefix match)
- Fix upstream security issue #1813 (pkcs11-provider persistent keys)
- 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 #1822 (SCT extension parser OOB read)
- Fix upstream security issue #1841 (key zeroization in hybrid kex)
- Fix upstream security issue #1823 (malformed certtool template)
- Fix upstream security issue #1817 (session parameter loading robustness)
- Fix upstream security issue #1820 (PKCS#11 KDF succeeding w/o deriving)
- gnutls-3.8.10-CVE-2025-9820.patch: update Makefile.in
* Fri Feb 6 2026 Alexander Sosedkin <asosedkin@redhat.com> - 3.8.10-3
- Fix PKCS#11 token initialization label overflow (CVE-2025-9820)
- Fix name constraint processing performance issue (CVE-2025-14831)