commit:     cbdd4551ab43dfbdca5a91939888bb86bf4f0934
Author:     Sam James <sam <AT> gentoo <DOT> org>
AuthorDate: Thu Oct  2 03:39:43 2025 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Thu Oct  2 03:40:04 2025 +0000
URL:        https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=cbdd4551

net-libs/gnutls: fix TLS regression

Closes: https://bugs.gentoo.org/961867
Signed-off-by: Sam James <sam <AT> gentoo.org>

 ...utls-3.8.10-tls-migration-qemu-regression.patch | 299 +++++++++++++++++++++
 net-libs/gnutls/gnutls-3.8.10-r1.ebuild            | 167 ++++++++++++
 2 files changed, 466 insertions(+)

diff --git 
a/net-libs/gnutls/files/gnutls-3.8.10-tls-migration-qemu-regression.patch 
b/net-libs/gnutls/files/gnutls-3.8.10-tls-migration-qemu-regression.patch
new file mode 100644
index 000000000000..f3d3f05e45f5
--- /dev/null
+++ b/net-libs/gnutls/files/gnutls-3.8.10-tls-migration-qemu-regression.patch
@@ -0,0 +1,299 @@
+https://bugs.gentoo.org/961867
+https://issues.redhat.com/browse/RHEL-98672
+https://gitlab.com/gnutls/gnutls/-/issues/1717
+https://gitlab.com/gnutls/gnutls/-/merge_requests/1990
+
+From 5376a0cabf94314316005e6bf411ffcc7628b386 Mon Sep 17 00:00:00 2001
+From: Daiki Ueno <[email protected]>
+Date: Tue, 22 Jul 2025 10:49:33 +0900
+Subject: [PATCH 1/3] key_update: fix state transition in KTLS code path
+
+Signed-off-by: Daiki Ueno <[email protected]>
+---
+ lib/record.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/lib/record.c b/lib/record.c
+index d37f79a550..ebc75addec 100644
+--- a/lib/record.c
++++ b/lib/record.c
+@@ -2045,7 +2045,7 @@ ssize_t gnutls_record_send2(gnutls_session_t session, 
const void *data,
+               FALLTHROUGH;
+       case RECORD_SEND_KEY_UPDATE_3:
+               if (IS_KTLS_ENABLED(session, GNUTLS_KTLS_SEND)) {
+-                      return _gnutls_ktls_send(
++                      ret = _gnutls_ktls_send(
+                               session,
+                               
session->internals.record_key_update_buffer.data,
+                               session->internals.record_key_update_buffer
+-- 
+GitLab
+
+
+From 30c264b661d49d135ef342426c6c4cd853209c06 Mon Sep 17 00:00:00 2001
+From: Daiki Ueno <[email protected]>
+Date: Thu, 31 Jul 2025 15:34:48 +0900
+Subject: [PATCH 2/3] constate: switch epoch lookup to linear search
+
+The previous logic of epoch lookup was utilizing the fact that epoch
+numbers are monotonically increasing and there are no gaps in between
+after garbarge collection. That is, however, no longer true when a TLS
+1.3 key update is happening in only one direction.
+
+This patch switches to using linear search instead, at the cost of
+approx MAX_EPOCH_INDEX * 2 (= 8) comparison.
+
+Signed-off-by: Daiki Ueno <[email protected]>
+---
+ lib/constate.c   | 47 ++++++++++++++++-------------------------------
+ lib/gnutls_int.h |  3 ---
+ 2 files changed, 16 insertions(+), 34 deletions(-)
+
+diff --git a/lib/constate.c b/lib/constate.c
+index ca253a2bea..b091d891ff 100644
+--- a/lib/constate.c
++++ b/lib/constate.c
+@@ -932,17 +932,23 @@ static inline int epoch_resolve(gnutls_session_t session,
+ static inline record_parameters_st **epoch_get_slot(gnutls_session_t session,
+                                                   uint16_t epoch)
+ {
+-      uint16_t epoch_index = epoch - session->security_parameters.epoch_min;
++      /* First look for a non-empty slot */
++      for (size_t i = 0; i < MAX_EPOCH_INDEX; i++) {
++              record_parameters_st **slot = &session->record_parameters[i];
++              if (*slot != NULL && (*slot)->epoch == epoch)
++                      return slot;
++      }
+ 
+-      if (epoch_index >= MAX_EPOCH_INDEX) {
+-              _gnutls_handshake_log(
+-                      "Epoch %d out of range (idx: %d, max: %d)\n",
+-                      (int)epoch, (int)epoch_index, MAX_EPOCH_INDEX);
+-              gnutls_assert();
+-              return NULL;
++      /* Then look for an empty slot */
++      for (size_t i = 0; i < MAX_EPOCH_INDEX; i++) {
++              record_parameters_st **slot = &session->record_parameters[i];
++              if (*slot == NULL)
++                      return slot;
+       }
+-      /* The slot may still be empty (NULL) */
+-      return &session->record_parameters[epoch_index];
++
++      gnutls_assert();
++      _gnutls_handshake_log("No slot available for epoch %u\n", epoch);
++      return NULL;
+ }
+ 
+ int _gnutls_epoch_get(gnutls_session_t session, unsigned int epoch_rel,
+@@ -1063,8 +1069,7 @@ static inline int epoch_alive(gnutls_session_t session,
+ 
+ void _gnutls_epoch_gc(gnutls_session_t session)
+ {
+-      int i, j;
+-      unsigned int min_index = 0;
++      int i;
+ 
+       _gnutls_record_log("REC[%p]: Start of epoch cleanup\n", session);
+ 
+@@ -1091,26 +1096,6 @@ void _gnutls_epoch_gc(gnutls_session_t session)
+               }
+       }
+ 
+-      /* Look for contiguous NULLs at the start of the array */
+-      for (i = 0;
+-           i < MAX_EPOCH_INDEX && session->record_parameters[i] == NULL; i++)
+-              ;
+-      min_index = i;
+-
+-      /* Pick up the slack in the epoch window. */
+-      if (min_index != 0) {
+-              for (i = 0, j = min_index; j < MAX_EPOCH_INDEX; i++, j++) {
+-                      session->record_parameters[i] =
+-                              session->record_parameters[j];
+-                      session->record_parameters[j] = NULL;
+-              }
+-      }
+-
+-      /* Set the new epoch_min */
+-      if (session->record_parameters[0] != NULL)
+-              session->security_parameters.epoch_min =
+-                      session->record_parameters[0]->epoch;
+-
+       gnutls_mutex_unlock(&session->internals.epoch_lock);
+ 
+       _gnutls_record_log("REC[%p]: End of epoch cleanup\n", session);
+diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
+index 539486bc7d..e083520055 100644
+--- a/lib/gnutls_int.h
++++ b/lib/gnutls_int.h
+@@ -876,9 +876,6 @@ typedef struct {
+       /* The epoch that the next handshake will initialize. */
+       uint16_t epoch_next;
+ 
+-      /* The epoch at index 0 of record_parameters. */
+-      uint16_t epoch_min;
+-
+       /* this is the ciphersuite we are going to use
+        * moved here from internals in order to be restored
+        * on resume;
+-- 
+GitLab
+
+
+From 1d830baac2f8a08a40b13e9eecfcc64ad032e7b5 Mon Sep 17 00:00:00 2001
+From: Daiki Ueno <[email protected]>
+Date: Sat, 19 Jul 2025 07:08:24 +0900
+Subject: [PATCH 3/3] key_update: rework the rekeying logic
+
+While RFC 8446 4.6.3 says that the sender of a KeyUpdate message
+should only update its sending key, the previous implementation
+updated both the sending and receiving keys, preventing that any
+application data interleaved being decrypted.
+
+This splits the key update logic into 2 phases: when sending a
+KeyUpdate, only update the sending key, and when receiving a
+KeyUpdate, only update the receiving key.  In both cases, KeyUpdate
+messages are encrypted/decrypted with the old keys.
+
+Signed-off-by: Daiki Ueno <[email protected]>
+---
+ lib/gnutls_int.h       |  2 +-
+ lib/tls13/key_update.c | 72 +++++++++++++++++++++++++++---------------
+ 2 files changed, 47 insertions(+), 27 deletions(-)
+
+diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
+index e083520055..f3caea1170 100644
+--- a/lib/gnutls_int.h
++++ b/lib/gnutls_int.h
+@@ -1672,7 +1672,7 @@ typedef struct {
+ } internals_st;
+ 
+ /* Maximum number of epochs we keep around. */
+-#define MAX_EPOCH_INDEX 4
++#define MAX_EPOCH_INDEX 16
+ 
+ #define reset_cand_groups(session)                                            
\
+       session->internals.cand_ec_group = session->internals.cand_dh_group = \
+diff --git a/lib/tls13/key_update.c b/lib/tls13/key_update.c
+index 41243651b5..beee1dc41a 100644
+--- a/lib/tls13/key_update.c
++++ b/lib/tls13/key_update.c
+@@ -52,45 +52,47 @@ static inline int set_ktls_keys(gnutls_session_t session,
+       return 0;
+ }
+ 
+-static int update_keys(gnutls_session_t session, hs_stage_t stage)
++static int update_sending_key(gnutls_session_t session, hs_stage_t stage)
+ {
+       int ret;
+ 
+-      ret = _tls13_update_secret(session,
+-                                 session->key.proto.tls13.temp_secret,
+-                                 session->key.proto.tls13.temp_secret_size);
++      _gnutls_epoch_bump(session);
++      ret = _gnutls_epoch_dup(session, EPOCH_WRITE_CURRENT);
+       if (ret < 0)
+               return gnutls_assert_val(ret);
+ 
+-      _gnutls_epoch_bump(session);
+-      ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT);
++      ret = _tls13_write_connection_state_init(session, stage);
+       if (ret < 0)
+               return gnutls_assert_val(ret);
+ 
+-      /* If we send a key update during early start, only update our
+-       * write keys */
+-      if (session->internals.recv_state == RECV_STATE_EARLY_START) {
+-              ret = _tls13_write_connection_state_init(session, stage);
++      if (IS_KTLS_ENABLED(session, GNUTLS_KTLS_SEND)) {
++              ret = set_ktls_keys(session, GNUTLS_KTLS_SEND);
+               if (ret < 0)
+                       return gnutls_assert_val(ret);
++      }
+ 
+-              if (IS_KTLS_ENABLED(session, GNUTLS_KTLS_SEND))
+-                      ret = set_ktls_keys(session, GNUTLS_KTLS_SEND);
+-      } else {
+-              ret = _tls13_connection_state_init(session, stage);
+-              if (ret < 0)
+-                      return gnutls_assert_val(ret);
++      return 0;
++}
+ 
+-              if (IS_KTLS_ENABLED(session, GNUTLS_KTLS_SEND) &&
+-                  stage == STAGE_UPD_OURS)
+-                      ret = set_ktls_keys(session, GNUTLS_KTLS_SEND);
+-              else if (IS_KTLS_ENABLED(session, GNUTLS_KTLS_RECV) &&
+-                       stage == STAGE_UPD_PEERS)
+-                      ret = set_ktls_keys(session, GNUTLS_KTLS_RECV);
+-      }
++static int update_receiving_key(gnutls_session_t session, hs_stage_t stage)
++{
++      int ret;
++
++      _gnutls_epoch_bump(session);
++      ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT);
++      if (ret < 0)
++              return gnutls_assert_val(ret);
++
++      ret = _tls13_read_connection_state_init(session, stage);
+       if (ret < 0)
+               return gnutls_assert_val(ret);
+ 
++      if (IS_KTLS_ENABLED(session, GNUTLS_KTLS_RECV)) {
++              ret = set_ktls_keys(session, GNUTLS_KTLS_RECV);
++              if (ret < 0)
++                      return gnutls_assert_val(ret);
++      }
++
+       return 0;
+ }
+ 
+@@ -128,7 +130,13 @@ int _gnutls13_recv_key_update(gnutls_session_t session, 
gnutls_buffer_st *buf)
+       switch (buf->data[0]) {
+       case 0:
+               /* peer updated its key, not requested our key update */
+-              ret = update_keys(session, STAGE_UPD_PEERS);
++              ret = _tls13_update_secret(
++                      session, session->key.proto.tls13.temp_secret,
++                      session->key.proto.tls13.temp_secret_size);
++              if (ret < 0)
++                      return gnutls_assert_val(ret);
++
++              ret = update_receiving_key(session, STAGE_UPD_PEERS);
+               if (ret < 0)
+                       return gnutls_assert_val(ret);
+ 
+@@ -141,7 +149,13 @@ int _gnutls13_recv_key_update(gnutls_session_t session, 
gnutls_buffer_st *buf)
+               }
+ 
+               /* peer updated its key, requested our key update */
+-              ret = update_keys(session, STAGE_UPD_PEERS);
++              ret = _tls13_update_secret(
++                      session, session->key.proto.tls13.temp_secret,
++                      session->key.proto.tls13.temp_secret_size);
++              if (ret < 0)
++                      return gnutls_assert_val(ret);
++
++              ret = update_receiving_key(session, STAGE_UPD_PEERS);
+               if (ret < 0)
+                       return gnutls_assert_val(ret);
+ 
+@@ -248,7 +262,13 @@ int gnutls_session_key_update(gnutls_session_t session, 
unsigned flags)
+       _gnutls_epoch_gc(session);
+ 
+       /* it was completely sent, update the keys */
+-      ret = update_keys(session, STAGE_UPD_OURS);
++      ret = _tls13_update_secret(session,
++                                 session->key.proto.tls13.temp_secret,
++                                 session->key.proto.tls13.temp_secret_size);
++      if (ret < 0)
++              return gnutls_assert_val(ret);
++
++      ret = update_sending_key(session, STAGE_UPD_OURS);
+       if (ret < 0)
+               return gnutls_assert_val(ret);
+ 
+-- 
+GitLab

diff --git a/net-libs/gnutls/gnutls-3.8.10-r1.ebuild 
b/net-libs/gnutls/gnutls-3.8.10-r1.ebuild
new file mode 100644
index 000000000000..4156212331c4
--- /dev/null
+++ b/net-libs/gnutls/gnutls-3.8.10-r1.ebuild
@@ -0,0 +1,167 @@
+# Copyright 1999-2025 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=8
+
+VERIFY_SIG_OPENPGP_KEY_PATH=/usr/share/openpgp-keys/gnutls.asc
+inherit libtool multilib-minimal verify-sig
+
+DESCRIPTION="A secure communications library implementing the SSL, TLS and 
DTLS protocols"
+HOMEPAGE="https://www.gnutls.org/";
+SRC_URI="mirror://gnupg/gnutls/v$(ver_cut 1-2)/${P}.tar.xz"
+SRC_URI+=" verify-sig? ( mirror://gnupg/gnutls/v$(ver_cut 1-2)/${P}.tar.xz.sig 
)"
+
+LICENSE="GPL-3 LGPL-2.1+"
+# As of 3.8.0, the C++ library is header-only, but we won't drop the subslot
+# component for it until libgnutls.so breaks ABI, to avoid pointless rebuilds.
+# Subslot format:
+# <libgnutls.so number>.<libgnutlsxx.so number>
+SLOT="0/30.30"
+KEYWORDS="~alpha ~amd64 ~arm ~arm64 ~hppa ~loong ~m68k ~mips ~ppc ~ppc64 
~riscv ~s390 ~sparc ~x86 ~amd64-linux ~x86-linux ~arm64-macos ~ppc-macos 
~x64-macos ~x64-solaris"
+IUSE="brotli +cxx dane doc examples +idn nls +openssl pkcs11 sslv2 sslv3 
static-libs test test-full +tls-heartbeat tools zlib zstd"
+REQUIRED_USE="test-full? ( cxx dane doc examples idn nls openssl pkcs11 
tls-heartbeat tools )"
+RESTRICT="!test? ( test )"
+
+# >=nettle-3.10 as a workaround for bug #936011
+RDEPEND="
+       >=dev-libs/libtasn1-4.9:=[${MULTILIB_USEDEP}]
+       dev-libs/libunistring:=[${MULTILIB_USEDEP}]
+       >=dev-libs/nettle-3.10:=[gmp,${MULTILIB_USEDEP}]
+       >=dev-libs/gmp-5.1.3-r1:=[${MULTILIB_USEDEP}]
+       brotli? ( >=app-arch/brotli-1.0.0:=[${MULTILIB_USEDEP}] )
+       dane? ( >=net-dns/unbound-1.4.20:=[${MULTILIB_USEDEP}] )
+       nls? ( >=virtual/libintl-0-r1:=[${MULTILIB_USEDEP}] )
+       pkcs11? ( >=app-crypt/p11-kit-0.23.1[${MULTILIB_USEDEP}] )
+       idn? ( >=net-dns/libidn2-0.16-r1:=[${MULTILIB_USEDEP}] )
+       zlib? ( sys-libs/zlib[${MULTILIB_USEDEP}] )
+       zstd? ( >=app-arch/zstd-1.3.0:=[${MULTILIB_USEDEP}] )
+"
+DEPEND="
+       ${RDEPEND}
+       test-full? ( sys-libs/libseccomp )
+"
+BDEPEND="
+       dev-build/gtk-doc-am
+       >=virtual/pkgconfig-0-r1
+       doc? ( dev-util/gtk-doc )
+       nls? ( sys-devel/gettext )
+       test-full? (
+               app-crypt/dieharder
+               || ( sys-libs/libfaketime >=app-misc/datefudge-1.22 )
+               dev-libs/softhsm:2[-bindist(-)]
+               net-dialup/ppp
+               net-misc/socat
+       )
+       verify-sig? ( >=sec-keys/openpgp-keys-gnutls-20240415 )
+"
+
+DOCS=( README.md doc/certtool.cfg )
+
+HTML_DOCS=()
+
+QA_CONFIG_IMPL_DECL_SKIP=(
+       # gnulib FPs
+       MIN
+       alignof
+       static_assert
+)
+
+PATCHES=(
+       "${FILESDIR}"/${PN}-3.8.10-tests.patch
+       "${FILESDIR}"/${PN}-3.8.10-tls-migration-qemu-regression.patch
+)
+
+src_prepare() {
+       default
+
+       # bug #520818
+       export TZ=UTC
+
+       use doc && HTML_DOCS+=( doc/gnutls.html )
+
+       # don't try to use system certificate store on macOS, it is
+       # confusingly ignoring our ca-certificates and more importantly
+       # fails to compile in certain configurations
+       sed -i -e 's/__APPLE__/__NO_APPLE__/' lib/system/certs.c || die
+
+       # Fails with some combinations of USE="brotli zlib zstd"
+       # https://gitlab.com/gnutls/gnutls/-/issues/1721
+       # https://gitlab.com/gnutls/gnutls/-/merge_requests/1980
+       cat <<-EOF > tests/system-override-compress-cert.sh || die
+       #!/bin/sh
+       exit 77
+       EOF
+       chmod +x tests/system-override-compress-cert.sh || die
+
+       elibtoolize
+}
+
+multilib_src_configure() {
+       LINGUAS="${LINGUAS//en/en@boldquot en@quot}"
+
+       local libconf=()
+
+       # TPM needs to be tested before being enabled
+       # Note that this may add a libltdl dep when enabled. Check configure.ac.
+       libconf+=(
+               --without-tpm
+               --without-tpm2
+       )
+
+       # hardware-accel is disabled on OSX because the asm files force
+       #   GNU-stack (as doesn't support that) and when that's removed ld
+       #   complains about duplicate symbols
+       [[ ${CHOST} == *-darwin* ]] && libconf+=( 
--disable-hardware-acceleration )
+
+       # -fanalyzer substantially slows down the build and isn't useful for
+       # us. It's useful for upstream as it's static analysis, but it's not
+       # useful when just getting something built.
+       export gl_cv_warn_c__fanalyzer=no
+
+       local myeconfargs=(
+               --disable-valgrind-tests
+               $(multilib_native_enable manpages)
+               $(multilib_native_use_enable doc gtk-doc)
+               $(multilib_native_use_enable doc)
+               $(multilib_native_use_enable test tests)
+               $(multilib_native_use_enable test-full full-test-suite)
+               $(multilib_native_use_enable test-full seccomp-tests)
+               $(multilib_native_use_enable tools)
+               $(use_enable cxx)
+               $(use_enable dane libdane)
+               $(use_enable nls)
+               $(use_enable openssl openssl-compatibility)
+               $(use_enable sslv2 ssl2-support)
+               $(use_enable sslv3 ssl3-support)
+               $(use_enable static-libs static)
+               $(use_enable tls-heartbeat heartbeat-support)
+               $(use_with brotli '' link)
+               $(use_with idn)
+               $(use_with pkcs11 p11-kit)
+               $(use_with zlib '' link)
+               $(use_with zstd '' link)
+               --disable-rpath
+               
--with-default-trust-store-file="${EPREFIX}"/etc/ssl/certs/ca-certificates.crt
+               
--with-unbound-root-key-file="${EPREFIX}"/etc/dnssec/root-anchors.txt
+               --without-included-libtasn1
+               $("${S}/configure" --help | grep -o -- '--without-.*-prefix')
+       )
+
+       ECONF_SOURCE="${S}" econf "${libconf[@]}" "${myeconfargs[@]}"
+
+       if [[ ${CHOST} == *-solaris* ]] ; then
+               # gnulib ends up defining its own pthread_mutexattr_gettype
+               # otherwise, which is causing versioning problems
+               echo "#define PTHREAD_IN_USE_DETECTION_HARD 1" >> config.h || 
die
+       fi
+}
+
+multilib_src_install_all() {
+       einstalldocs
+       find "${ED}" -type f -name '*.la' -delete || die
+
+       if use examples; then
+               docinto examples
+               dodoc doc/examples/*.c
+       fi
+}

Reply via email to