Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package s2n for openSUSE:Factory checked in 
at 2026-09-19 22:20:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/s2n (Old)
 and      /work/SRC/openSUSE:Factory/.s2n.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "s2n"

Sat Sep 19 22:20:15 2026 rev:44 rq:1378851 version:1.7.10

Changes:
--------
--- /work/SRC/openSUSE:Factory/s2n/s2n.changes  2026-09-11 18:07:49.591146526 
+0200
+++ /work/SRC/openSUSE:Factory/.s2n.new.383539/s2n.changes      2026-09-19 
22:21:07.883576575 +0200
@@ -1,0 +2,15 @@
+Thu Sep 17 14:04:22 UTC 2026 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- Update to version 1.7.10
+  * feat(bindings): add signature_public_key_type to Connection (#6076)
+  * fix: bounds-check cert pkey type before array indexing (#6054)
+  * fix(integration): replace BoringSSL fork git dependency with btls crate 
(#6067)
+  * feat(s2n-tls-metrics): Add hello_retry_request_count (#6078)
+  * fix: always free async offload op during connection teardown (#6080)
+  * fix: enforce JA4 list limit when building fingerprint (#6047)
+  * fix: zero-initialize s2n_tls13_keys in PSK binder functions (#6048)
+  * fix: free encoded point on write failure in s2n_ecc_evp_write_params_point 
(#5964)
+  * fix: free client pub_key on all error paths in server shared secret (#6063)
+  * feat(metrics-subscriber): expose compatibility profile allow-lists (#6086)
+
+-------------------------------------------------------------------

Old:
----
  v1.7.9.tar.gz

New:
----
  v1.7.10.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ s2n.spec ++++++
--- /var/tmp/diff_new_pack.KyqWDq/_old  2026-09-19 22:21:12.195756438 +0200
+++ /var/tmp/diff_new_pack.KyqWDq/_new  2026-09-19 22:21:12.197756522 +0200
@@ -19,7 +19,7 @@
 %define library_version 1.0.0
 %define library_soversion 1
 Name:           s2n
-Version:        1.7.9
+Version:        1.7.10
 Release:        0
 Summary:        AWS implementation of the TLS/SSL protocols
 License:        Apache-2.0

++++++ v1.7.9.tar.gz -> v1.7.10.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/.github/bin/check_boringssl_fork_freshness.sh 
new/s2n-tls-1.7.10/.github/bin/check_boringssl_fork_freshness.sh
--- old/s2n-tls-1.7.9/.github/bin/check_boringssl_fork_freshness.sh     
2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/.github/bin/check_boringssl_fork_freshness.sh    
1970-01-01 01:00:00.000000000 +0100
@@ -1,50 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-# The script is configurable via environment variablesß
-FORK_REPO="${FORK_REPO:-kaukabrizvi/boring}"          # owner/repo
-FORK_BRANCH="${FORK_BRANCH:-symbol-prefixing}"        # branch to check
-THRESHOLD_DAYS="${THRESHOLD_DAYS:-60}"               # ~2 months
-
-if ! command -v jq >/dev/null 2>&1; then
-  echo "ERROR: jq is required for the BoringSSL fork freshness check."
-  exit 2
-fi
-
-commit_api="https://api.github.com/repos/${FORK_REPO}/commits/${FORK_BRANCH}";
-json="$(curl -fsSL "${commit_api}")"
-
-last_commit_date="$(echo "${json}" | jq -r '.commit.committer.date')"
-last_commit_sha="$(echo "${json}" | jq -r '.sha')"
-
-if [[ -z "${last_commit_date}" || "${last_commit_date}" == "null" ]]; then
-  echo "ERROR: Could not determine last commit date for 
${FORK_REPO}@${FORK_BRANCH} from GitHub API."
-  exit 2
-fi
-
-now_epoch="$(date -u +%s)"
-commit_epoch="$(date -u -d "${last_commit_date}" +%s)"
-age_days="$(( (now_epoch - commit_epoch) / 86400 ))"
-
-if (( age_days > THRESHOLD_DAYS )); then
-  cat <<EOF
-ERROR: BoringSSL fork branch appears stale.
-
-  Fork:            ${FORK_REPO}
-  Branch:          ${FORK_BRANCH}
-  Last commit:     ${last_commit_sha}
-  Last commit date:${last_commit_date}  (~${age_days} days ago)
-  Threshold:       ${THRESHOLD_DAYS} days
-
-Why this is failing:
-  s2n-tls currently depends on a forked BoringSSL branch (for symbol 
prefixing) to avoid
-  OpenSSL symbol collisions in integration tests. A stale fork risks outdated 
testing.
-
-Refresh [email protected]:${FORK_REPO}/tree/${FORK_BRANCH} by syncing it with 
the upstream repository, 
-resolving merge conflicts if they appear. This should be done periodically and 
has not been done in
-~${age_days} days.
-EOF
-  exit 1
-fi
-
-echo "OK: ${FORK_REPO}@${FORK_BRANCH} last commit ${last_commit_sha} at 
${last_commit_date} (~${age_days} days ago) within threshold ${THRESHOLD_DAYS}."
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/.github/workflows/boringssl-fork-freshness.yml 
new/s2n-tls-1.7.10/.github/workflows/boringssl-fork-freshness.yml
--- old/s2n-tls-1.7.9/.github/workflows/boringssl-fork-freshness.yml    
2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/.github/workflows/boringssl-fork-freshness.yml   
1970-01-01 01:00:00.000000000 +0100
@@ -1,20 +0,0 @@
-name: BoringSSL fork freshness
-
-on:
-  pull_request:
-
-jobs:
-  freshness:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v7
-
-      - name: Install jq
-        run: sudo apt-get update && sudo apt-get install -y jq
-
-      - name: Check BoringSSL fork freshness (time-bomb)
-        env:
-          FORK_REPO: kaukabrizvi/boring
-          FORK_BRANCH: symbol-prefixing
-          THRESHOLD_DAYS: "60"
-        run: .github/bin/check_boringssl_fork_freshness.sh
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/api/unstable/async_offload.h 
new/s2n-tls-1.7.10/api/unstable/async_offload.h
--- old/s2n-tls-1.7.9/api/unstable/async_offload.h      2026-09-01 
22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/api/unstable/async_offload.h     2026-09-11 
02:02:31.000000000 +0200
@@ -87,7 +87,13 @@
  * Retrying s2n_negotiate() will produce the same result until 
s2n_async_offload_op_perform() is completed.
  * 
  * s2n_async_offload_op_perform() can only be called once for each triggered 
operation.
- * 
+ *
+ * The `op` is owned by s2n-tls and is freed together with its s2n_connection. 
The
+ * application MUST ensure any thread executing s2n_async_offload_op_perform() 
for a
+ * connection has finished before that connection is freed 
(s2n_connection_free) or
+ * reused (s2n_connection_wipe). s2n-tls does not synchronize with the perform 
thread,
+ * so freeing or wiping a connection during a perform call may cause a 
use-after-free.
+ *
  * @param op An opaque object representing the async operation
  */
 S2N_API extern int s2n_async_offload_op_perform(struct s2n_async_offload_op 
*op);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/extended/s2n-tls/src/connection.rs 
new/s2n-tls-1.7.10/bindings/rust/extended/s2n-tls/src/connection.rs
--- old/s2n-tls-1.7.9/bindings/rust/extended/s2n-tls/src/connection.rs  
2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/bindings/rust/extended/s2n-tls/src/connection.rs 
2026-09-11 02:02:31.000000000 +0200
@@ -1261,6 +1261,43 @@
         }
     }
 
+    /// Returns a string describing the public key algorithm and parameters of
+    /// the leaf certificate for the given connection `mode`.
+    ///
+    /// Use [`Mode::Server`] to query the server certificate and 
[`Mode::Client`]
+    /// to query the client certificate. The returned string has one of the
+    /// following forms:
+    /// - RSA: `"rsa2048"`, `"rsa3072"`, `"rsa4096"`, or `"rsa<keysize>"`
+    /// - ECDSA: `"ecdsa_secp256r1"`, `"ecdsa_secp384r1"`, `"ecdsa_secp521r1"`
+    /// - ML-DSA: `"mldsa44"`, `"mldsa65"`, `"mldsa87"`
+    ///
+    /// Querying the peer's certificate ([`Mode::Client`] on a server, or
+    /// [`Mode::Server`] on a client) requires that the peer's certificate 
chain
+    /// has been validated during the handshake.
+    ///
+    /// Corresponds to [`s2n_conn_get_signature_public_key_type`].
+    pub fn signature_public_key_type(&self, mode: Mode) -> Result<String, 
Error> {
+        // The API writes a null-terminated string into a caller-provided
+        // buffer. 32 bytes comfortably fits every defined output (the longest
+        // is "ecdsa_secp256r1" at 16 bytes including the null terminator); a
+        // larger output would surface as S2N_ERR_INSUFFICIENT_MEM_SIZE.
+        let mut buffer = [0u8; 32];
+        let mut output_size = buffer.len() as u32;
+        unsafe {
+            s2n_conn_get_signature_public_key_type(
+                self.connection.as_ptr(),
+                mode.into(),
+                buffer.as_mut_ptr() as *mut std::ffi::c_char,
+                &mut output_size,
+            )
+            .into_result()?;
+        }
+
+        // `output_size` includes the null terminator; drop it before decoding.
+        let len = (output_size as usize).saturating_sub(1);
+        String::from_utf8(buffer[..len].to_vec()).map_err(|_| 
Error::INVALID_INPUT)
+    }
+
     /// Corresponds to [`s2n_connection_get_selected_digest_algorithm`].
     pub fn selected_hash_algorithm(&self) -> Result<HashAlgorithm, Error> {
         let mut hash_alg = s2n_tls_hash_algorithm::NONE;
@@ -1973,6 +2010,67 @@
         Ok(())
     }
 
+    #[test]
+    fn signature_public_key_type_rsa() -> Result<(), Box<dyn 
std::error::Error>> {
+        // The default test cert is a 4096-bit RSA cert.
+        let config = build_config(&security::DEFAULT_TLS13)?;
+        let mut pair = TestPair::from_config(&config);
+        pair.handshake()?;
+
+        // The server's own certificate and the client's view of the peer
+        // (server) certificate should both report "rsa4096".
+        assert_eq!(
+            pair.server.signature_public_key_type(Mode::Server)?,
+            "rsa4096"
+        );
+        assert_eq!(
+            pair.client.signature_public_key_type(Mode::Server)?,
+            "rsa4096"
+        );
+
+        // No client auth was configured, so there is no validated client
+        // certificate to describe.
+        let err = pair
+            .server
+            .signature_public_key_type(Mode::Client)
+            .unwrap_err();
+        assert_eq!(err.name(), "S2N_ERR_CERT_NOT_VALIDATED");
+
+        Ok(())
+    }
+
+    #[test]
+    fn signature_public_key_type_ecdsa() -> Result<(), Box<dyn 
std::error::Error>> {
+        // Build a config that serves an ECDSA (secp256r1) certificate so we
+        // exercise a non-RSA output and the longest string variant.
+        let keypair = SniTestCerts::AlligatorEcdsa.get();
+        let config = {
+            let mut builder = crate::config::Builder::new();
+            builder.set_security_policy(&security::DEFAULT_TLS13)?;
+            builder.load_pem(keypair.cert(), keypair.key())?;
+            builder.set_verify_host_callback(
+                crate::testing::InsecureAcceptAllCertificatesHandler {},
+            )?;
+            builder.with_system_certs(false)?;
+            builder.trust_pem(keypair.cert())?;
+            builder.build()?
+        };
+
+        let mut pair = TestPair::from_config(&config);
+        pair.handshake()?;
+
+        assert_eq!(
+            pair.server.signature_public_key_type(Mode::Server)?,
+            "ecdsa_secp256r1"
+        );
+        assert_eq!(
+            pair.client.signature_public_key_type(Mode::Server)?,
+            "ecdsa_secp256r1"
+        );
+
+        Ok(())
+    }
+
     /// Reads the TLS record payload length from a record header in the buffer.
     ///
     /// TLS record header format:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/integration/Cargo.toml 
new/s2n-tls-1.7.10/bindings/rust/standard/integration/Cargo.toml
--- old/s2n-tls-1.7.9/bindings/rust/standard/integration/Cargo.toml     
2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/bindings/rust/standard/integration/Cargo.toml    
2026-09-11 02:02:31.000000000 +0200
@@ -58,10 +58,13 @@
 # This should be the latest version of metrics subscriber available on 
crates.io
 old-metrics-subscriber = { package = "s2n-tls-metrics-subscriber", version = 
"0.0.5" }
 
+# BoringSSL is provided by the `btls` crate; see the note in
+# tls-harness/Cargo.toml for why it is used instead of `boring`.
+#
 # NOTE: BoringSSL is disabled on macOS to avoid symbol collisions with
 # OpenSSL (see https://github.com/aws/s2n-tls/pull/5659), and on Windows 
because BoringSSL can't be built under the MSYS2/MinGW toolchain.
-[target.'cfg(not(any(target_os = "macos", target_os = 
"windows")))'.dev-dependencies.boring]
-git = "https://github.com/kaukabrizvi/boring.git";
+[target.'cfg(not(any(target_os = "macos", target_os = 
"windows")))'.dev-dependencies.btls]
+version = "0.5.6"
 features = ["prefix-symbols"]
 
 [build-dependencies]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/integration/src/mtls/mod.rs 
new/s2n-tls-1.7.10/bindings/rust/standard/integration/src/mtls/mod.rs
--- old/s2n-tls-1.7.9/bindings/rust/standard/integration/src/mtls/mod.rs        
2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/bindings/rust/standard/integration/src/mtls/mod.rs       
2026-09-11 02:02:31.000000000 +0200
@@ -28,7 +28,7 @@
 // NOTE: BoringSSL tests are disabled on macOS to avoid symbol collisions with
 // OpenSSL; see https://github.com/aws/s2n-tls/pull/5659 for details.
 #[cfg(all(feature = "boringssl", not(target_os = "macos")))]
-use boring::ssl::SslVersion;
+use btls::ssl::SslVersion;
 use rustls::ClientConfig;
 
 use s2n_tls::{
@@ -221,7 +221,7 @@
 fn boringssl_mtls_client(sig_type: SigType, version: SslVersion) -> 
BoringSslConfig {
     use tls_harness::harness::{Mode, TlsConfigBuilder};
 
-    let mut builder = 
boring::ssl::SslContextBuilder::new_test_config(Mode::Client);
+    let mut builder = 
btls::ssl::SslContextBuilder::new_test_config(Mode::Client);
     builder.set_trust(sig_type);
 
     builder
@@ -233,10 +233,10 @@
     builder
         .set_private_key_file(
             tls_harness::get_cert_path(PemType::ClientKey, sig_type),
-            boring::ssl::SslFiletype::PEM,
+            btls::ssl::SslFiletype::PEM,
         )
         .unwrap();
-    builder.set_verify(boring::ssl::SslVerifyMode::PEER);
+    builder.set_verify(btls::ssl::SslVerifyMode::PEER);
 
     // Pin the protocol version
     builder.set_min_proto_version(Some(version)).unwrap();
@@ -252,11 +252,11 @@
 fn boringssl_mtls_server(sig_type: SigType, version: SslVersion) -> 
BoringSslConfig {
     use tls_harness::harness::{Mode, TlsConfigBuilder};
 
-    let mut builder = 
boring::ssl::SslContextBuilder::new_test_config(Mode::Server);
+    let mut builder = 
btls::ssl::SslContextBuilder::new_test_config(Mode::Server);
     builder.set_chain(sig_type);
     builder.set_trust(sig_type);
     builder.set_verify(
-        boring::ssl::SslVerifyMode::PEER | 
boring::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT,
+        btls::ssl::SslVerifyMode::PEER | 
btls::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT,
     );
 
     // Pin the protocol version
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-schema/src/metric_names.rs
 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-schema/src/metric_names.rs
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-schema/src/metric_names.rs
 2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-schema/src/metric_names.rs
        2026-09-11 02:02:31.000000000 +0200
@@ -88,6 +88,7 @@
 }
 
 pub const SSLV2_CLIENT_HELLO: &str = "sslv2_client_hello";
+pub const HELLO_RETRY_REQUEST_COUNT: &str = "hello_retry_request_count";
 pub const HANDSHAKE_DURATION_US: &str = "handshake_duration_us";
 pub const HANDSHAKE_COMPUTE_US: &str = "handshake_compute_us";
 pub const SYNTHETIC_TRAFFIC_COUNT: &str = "synthetic_traffic_count";
@@ -103,6 +104,7 @@
     COMPATIBILITY_NEGOTIATED_CNSA1,
     COMPATIBILITY_NEGOTIATED_CNSA2,
     SSLV2_CLIENT_HELLO,
+    HELLO_RETRY_REQUEST_COUNT,
     HANDSHAKE_SUCCESS_COUNT,
     HANDSHAKE_FAILURE_COUNT,
     HANDSHAKE_DURATION_US,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-schema/src/record.rs 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-schema/src/record.rs
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-schema/src/record.rs   
    2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-schema/src/record.rs  
    2026-09-11 02:02:31.000000000 +0200
@@ -78,6 +78,12 @@
 
     #[serde(default)]
     pub sslv2_client_hello: u64,
+
+    /// Number of TLS 1.3 handshakes that required a HelloRetryRequest, and
+    /// therefore an additional round trip.
+    #[serde(default)]
+    pub hello_retry_request_count: u64,
+
     #[serde(default)]
     pub supported_protocols: FrozenCounter<PROTOCOL_COUNT, Version>,
     #[serde(default)]
@@ -155,6 +161,7 @@
             negotiated_groups: FrozenCounter::default(),
             negotiated_signatures: FrozenCounter::default(),
             sslv2_client_hello: 0,
+            hello_retry_request_count: 0,
             supported_protocols: FrozenCounter::default(),
             supported_ciphers: FrozenCounter::default(),
             supported_groups: FrozenCounter::default(),
@@ -303,6 +310,10 @@
 
         writer.value(names::SSLV2_CLIENT_HELLO, &self.sslv2_client_hello);
         writer.value(
+            names::HELLO_RETRY_REQUEST_COUNT,
+            &self.hello_retry_request_count,
+        );
+        writer.value(
             names::HANDSHAKE_SUCCESS_COUNT,
             &self.handshake_success_count,
         );
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/compatibility.rs
 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/compatibility.rs
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/compatibility.rs
    2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/compatibility.rs
   2026-09-11 02:02:31.000000000 +0200
@@ -7,6 +7,48 @@
 use crate::{parsing::ClientHelloSupportedParameters, 
record::NegotiatedParameters};
 use s2n_tls_metrics_schema::static_lists::{Cipher, Group, Signature, Version};
 
+/// A read-only view of a single compatibility profile's allow-lists.
+///
+/// This exposes only the static allow-list data for a profile; it does not
+/// expose any handshake-evaluation behavior.
+#[derive(Debug, Clone, Copy)]
+pub struct TlsProfileSpec {
+    pub allowed_versions: &'static [Version],
+    pub allowed_ciphers: &'static [Cipher],
+    pub allowed_groups: &'static [Group],
+    pub allowed_signatures: &'static [Signature],
+}
+
+/// Stable identifiers for the TLS compatibility profiles measured by this 
crate.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum CompatibilityProfile {
+    General20251201,
+    Fips20251201,
+    Cnsa1,
+    Cnsa2,
+}
+
+impl CompatibilityProfile {
+    /// Returns the read-only allow-list [`TlsProfileSpec`] for this profile.
+    pub fn spec(self) -> TlsProfileSpec {
+        fn spec_of<P: TlsProfile>() -> TlsProfileSpec {
+            TlsProfileSpec {
+                allowed_versions: P::ALLOWED_VERSIONS,
+                allowed_ciphers: P::ALLOWED_CIPHERS,
+                allowed_groups: P::ALLOWED_GROUPS,
+                allowed_signatures: P::ALLOWED_SIGNATURES,
+            }
+        }
+
+        match self {
+            CompatibilityProfile::General20251201 => 
spec_of::<General20251201>(),
+            CompatibilityProfile::Fips20251201 => spec_of::<Fips20251201>(),
+            CompatibilityProfile::Cnsa1 => spec_of::<Cnsa1>(),
+            CompatibilityProfile::Cnsa2 => spec_of::<Cnsa2>(),
+        }
+    }
+}
+
 pub(crate) trait TlsProfile {
     const ALLOWED_VERSIONS: &[Version];
     const ALLOWED_CIPHERS: &[Cipher];
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/lib.rs 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/lib.rs
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/lib.rs  
    2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/lib.rs 
    2026-09-11 02:02:31.000000000 +0200
@@ -3,7 +3,7 @@
 
 mod bounded_set;
 mod client_issue;
-mod compatibility;
+pub mod compatibility;
 pub(crate) mod counter;
 pub mod detector;
 #[cfg(feature = "fuzzing")]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/record.rs
 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/record.rs
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/record.rs
   2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/record.rs
  2026-09-11 02:02:31.000000000 +0200
@@ -101,6 +101,12 @@
     // we do not attempt to detect supported parameters for SSLv2 formatted 
client
     // hellos
     sslv2_client_hello: AtomicU64,
+
+    /// TLS 1.3 handshakes that required a HelloRetryRequest, and therefore an
+    /// additional round trip. This is detected from the handshake type, which 
is
+    /// available on both the client and server side of a connection.
+    hello_retry_request_count: AtomicU64,
+
     supported_protocols: Counter<PROTOCOL_COUNT, Version>,
     supported_ciphers: Counter<CIPHER_COUNT, Cipher>,
     supported_groups: Counter<GROUP_COUNT, Group>,
@@ -169,6 +175,7 @@
             negotiated_signatures: Counter::new(),
 
             sslv2_client_hello: Default::default(),
+            hello_retry_request_count: Default::default(),
             supported_groups: Counter::new(),
             supported_ciphers: Counter::new(),
             supported_protocols: Counter::new(),
@@ -285,6 +292,22 @@
             self.negotiated_signatures.increment(&sig);
         }
 
+        // An HRR costs an additional round trip, so it's tracked separately 
from
+        // the negotiated parameters that it was used to negotiate. The
+        // HELLO_RETRY_REQUEST flag only appears in the TLS 1.3 handshake type
+        // labels, so it can't be confused with a TLS 1.2 flag.
+        match conn.handshake_type() {
+            Ok(handshake_type) => {
+                if handshake_type.contains("HELLO_RETRY_REQUEST") {
+                    self.hello_retry_request_count
+                        .fetch_add(1, Ordering::Relaxed);
+                }
+            }
+            // Not treated as an internal failure: the rest of the record is
+            // still accurate, so continue recording telemetry.
+            Err(e) => tracing::error!("failed to retrieve handshake type: 
{e}"),
+        }
+
         // Compatibility based on the negotiated parameters. Unlike the client
         // hello based `compatibility_*` counters below, negotiated parameters
         // are available on both the client and server side, so these counters
@@ -488,6 +511,7 @@
             negotiated_signatures: self.negotiated_signatures.freeze(),
 
             sslv2_client_hello: 
self.sslv2_client_hello.load(Ordering::Relaxed),
+            hello_retry_request_count: 
self.hello_retry_request_count.load(Ordering::Relaxed),
             supported_protocols: self.supported_protocols.freeze(),
             supported_ciphers: self.supported_ciphers.freeze(),
             supported_groups: self.supported_groups.freeze(),
@@ -545,7 +569,9 @@
 
 #[cfg(test)]
 mod tests {
-    use crate::test_utils::{ARBITRARY_POLICY_1, TestEndpoint};
+    use crate::test_utils::{
+        ARBITRARY_POLICY_1, P256_PREFERRING_POLICY, 
STRONGLY_PREFERRED_GROUPS_POLICY, TestEndpoint,
+    };
     use s2n_tls_metrics_schema::{
         counter::FrozenCounter,
         static_lists::{Cipher, FiniteCounter},
@@ -553,6 +579,33 @@
 
     use super::*;
 
+    /// A handshake that requires a HelloRetryRequest increments
+    /// `hello_retry_request_count`, and one that doesn't leaves it at zero.
+    #[test]
+    fn record_contents_hello_retry_request() {
+        // the server strongly prefers secp384r1, but the client key shares
+        // secp256r1, so the server sends an HRR.
+        let hrr_endpoint = 
TestEndpoint::with_server_policy(&STRONGLY_PREFERRED_GROUPS_POLICY);
+        hrr_endpoint.client_handshake(&P256_PREFERRING_POLICY);
+        hrr_endpoint.subscriber.finish_record();
+
+        let records = hrr_endpoint.sink.records.lock().unwrap();
+        let record = &records[0].as_schema().handshake;
+        assert_eq!(record.handshake_success_count, 1);
+        assert_eq!(record.hello_retry_request_count, 1);
+
+        // the client key shares the group that the server prefers, so no HRR
+        // is needed.
+        let endpoint = 
TestEndpoint::with_server_policy(&STRONGLY_PREFERRED_GROUPS_POLICY);
+        endpoint.client_handshake(&STRONGLY_PREFERRED_GROUPS_POLICY);
+        endpoint.subscriber.finish_record();
+
+        let records = endpoint.sink.records.lock().unwrap();
+        let record = &records[0].as_schema().handshake;
+        assert_eq!(record.handshake_success_count, 1);
+        assert_eq!(record.hello_retry_request_count, 0);
+    }
+
     #[test]
     fn record_contents_negotiated_parameters() {
         let endpoint = TestEndpoint::new();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/test_utils.rs
 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/test_utils.rs
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/src/test_utils.rs
       2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/src/test_utils.rs
      2026-09-11 02:02:31.000000000 +0200
@@ -15,6 +15,17 @@
 pub(crate) static ARBITRARY_POLICY_1: LazyLock<Policy> =
     LazyLock::new(|| Policy::from_version("20240503").unwrap());
 
+/// A policy whose most preferred group is `secp256r1`, so a client using it 
will
+/// send a `secp256r1` key share.
+pub(crate) static P256_PREFERRING_POLICY: LazyLock<Policy> =
+    LazyLock::new(|| Policy::from_version("20240503").unwrap());
+
+/// A policy with `secp384r1` as a strongly preferred group. A server using 
this
+/// policy will send a HelloRetryRequest to any client that key shares a
+/// different group, e.g. a [`P256_PREFERRING_POLICY`] client.
+pub(crate) static STRONGLY_PREFERRED_GROUPS_POLICY: LazyLock<Policy> =
+    LazyLock::new(|| Policy::from_version("20251117").unwrap());
+
 /// A test helper that implements [`TelemetrySink`] by collecting records into 
a Vec.
 #[derive(Debug, Clone)]
 pub(crate) struct VecSink {
@@ -52,6 +63,10 @@
 
 impl TestEndpoint<VecSink> {
     pub fn new() -> Self {
+        Self::with_server_policy(&DEFAULT_TLS13)
+    }
+
+    pub fn with_server_policy(server_policy: &Policy) -> Self {
         let sink = VecSink::new();
         let attribution = Attribution {
             service: "test_server".to_owned(),
@@ -60,7 +75,7 @@
         };
         let subscriber = AggregatedMetricsSubscriber::new(sink.clone(), 
attribution);
         let server_config = {
-            let mut config = config_builder(&DEFAULT_TLS13).unwrap();
+            let mut config = config_builder(server_policy).unwrap();
             config.set_event_subscriber(subscriber.clone()).unwrap();
             config.build().unwrap()
         };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/tests/snapshots/entry_emf.json
 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/tests/snapshots/entry_emf.json
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/s2n-tls-metrics-subscriber/tests/snapshots/entry_emf.json
  2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/s2n-tls-metrics-subscriber/tests/snapshots/entry_emf.json
 2026-09-11 02:02:31.000000000 +0200
@@ -161,6 +161,9 @@
             "Name": "sslv2_client_hello"
           },
           {
+            "Name": "hello_retry_request_count"
+          },
+          {
             "Name": "handshake_success_count"
           },
           {
@@ -225,6 +228,7 @@
   "handshake_duration_us": "<DURATION>",
   "handshake_failure_count": 1,
   "handshake_success_count": 1,
+  "hello_retry_request_count": 0,
   "internal_failure": 0,
   "resource": "arn:aws:elasticloadbalancing:us-east-1:123:listener/abc",
   "service": "my-service",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/tls-harness/Cargo.toml 
new/s2n-tls-1.7.10/bindings/rust/standard/tls-harness/Cargo.toml
--- old/s2n-tls-1.7.9/bindings/rust/standard/tls-harness/Cargo.toml     
2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/bindings/rust/standard/tls-harness/Cargo.toml    
2026-09-11 02:02:31.000000000 +0200
@@ -1,6 +1,6 @@
 [features]
 default = ["renegotiate"]
-boringssl = ["dep:boring"]
+boringssl = ["dep:btls"]
 renegotiate = ["s2n-tls/unstable-renegotiate"]
 
 [package]
@@ -12,7 +12,7 @@
 [dependencies]
 s2n-tls = { path = "../../extended/s2n-tls" }
 errno = "0.3.13"
-libc = "0.2.174"
+libc = "0.2.185"
 strum = { version = "0.28", features = ["derive"] }
 rustls = "0.23.31"
 rustls-pemfile = "2.2.0"
@@ -20,18 +20,27 @@
 openssl-sys = "0.9.109"
 byteorder = "1.5.0"
 foreign-types-shared = "0.1.1"
-boring = { git = "https://github.com/kaukabrizvi/boring.git";, features = 
["prefix-symbols"], optional = true }
 
 brass-aphid-wire-decryption = "0.0.2"
 brass-aphid-wire-messages = "0.0.2"
 tracing = "0.1.43"
 
+# BoringSSL is provided by the `btls` crate, a maintained fork of
+# cloudflare/boring published to crates.io. It is used instead of `boring`
+# because it supports symbol prefixing, which is required to link BoringSSL
+# alongside the vendored OpenSSL above without duplicate symbol errors.
+# See https://github.com/cloudflare/boring/pull/401 for the upstream effort.
+#
+# `optional = true` gates this behind the `boringssl` feature, so the BoringSSL
+# build cost is only paid by jobs that actually run the interop tests.
+#
 # NOTE: BoringSSL is disabled on macOS to avoid symbol collisions with
 # OpenSSL (see https://github.com/aws/s2n-tls/pull/5659), and on Windows
 # because BoringSSL can't be built under the MSYS2/MinGW toolchain.
-[target.'cfg(not(any(target_os = "macos", target_os = 
"windows")))'.dependencies.boring]
-git = "https://github.com/kaukabrizvi/boring.git";
+[target.'cfg(not(any(target_os = "macos", target_os = 
"windows")))'.dependencies.btls]
+version = "0.5.6"
 features = ["prefix-symbols"]
+optional = true
 
 [dev-dependencies]
 # env_logger and log are used to enable logging for rustls, which can help with
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/s2n-tls-1.7.9/bindings/rust/standard/tls-harness/src/cohort/boringssl.rs 
new/s2n-tls-1.7.10/bindings/rust/standard/tls-harness/src/cohort/boringssl.rs
--- 
old/s2n-tls-1.7.9/bindings/rust/standard/tls-harness/src/cohort/boringssl.rs    
    2026-09-01 22:54:11.000000000 +0200
+++ 
new/s2n-tls-1.7.10/bindings/rust/standard/tls-harness/src/cohort/boringssl.rs   
    2026-09-11 02:02:31.000000000 +0200
@@ -6,7 +6,7 @@
     harness::{self, Mode, TlsConfigBuilder, TlsConnection, TlsInfo, ViewIO},
     PemType,
 };
-use boring::ssl::{
+use btls::ssl::{
     ErrorCode, ShutdownResult, Ssl, SslContext, SslContextBuilder, 
SslFiletype, SslMethod,
     SslSession, SslStream, SslVersion,
 };
@@ -175,11 +175,8 @@
 impl TlsConfigBuilder for SslContextBuilder {
     type Config = BoringSslConfig;
 
-    fn new_test_config(mode: Mode) -> Self {
-        match mode {
-            Mode::Client => 
SslContext::builder(SslMethod::tls_client()).unwrap(),
-            Mode::Server => 
SslContext::builder(SslMethod::tls_server()).unwrap(),
-        }
+    fn new_test_config(_mode: Mode) -> Self {
+        SslContext::builder(SslMethod::tls()).unwrap()
     }
 
     fn set_chain(&mut self, sig_type: crate::SigType) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/crypto/s2n_dhe.c 
new/s2n-tls-1.7.10/crypto/s2n_dhe.c
--- old/s2n-tls-1.7.9/crypto/s2n_dhe.c  2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/crypto/s2n_dhe.c 2026-09-11 02:02:31.000000000 +0200
@@ -27,6 +27,8 @@
 #include "utils/s2n_mem.h"
 #include "utils/s2n_safety.h"
 
+DEFINE_POINTER_CLEANUP_FUNC(BIGNUM *, BN_free);
+
 #define S2N_MIN_DH_PRIME_SIZE_BYTES (2048 / 8)
 
 /* Caller is not responsible for freeing values returned by these accessors
@@ -317,7 +319,7 @@
     uint16_t Yc_length = 0;
     struct s2n_blob Yc = { 0 };
     int shared_key_size = 0;
-    BIGNUM *pub_key = NULL;
+    DEFER_CLEANUP(BIGNUM *pub_key = NULL, BN_free_pointer);
 
     POSIX_GUARD(s2n_check_all_dh_params(server_dh_params));
     int server_dh_params_size = DH_size(server_dh_params->dh);
@@ -347,15 +349,10 @@
     POSIX_GUARD(s2n_alloc(shared_key, server_dh_params_size));
 
     shared_key_size = DH_compute_key(shared_key->data, pub_key, 
server_dh_params->dh);
-    if (shared_key_size <= 0) {
-        BN_free(pub_key);
-        POSIX_BAIL(S2N_ERR_DH_SHARED_SECRET);
-    }
+    POSIX_ENSURE(shared_key_size > 0, S2N_ERR_DH_SHARED_SECRET);
 
     s2n_dh_pad_shared_secret(shared_key, shared_key_size, 
server_dh_params_size);
 
-    BN_free(pub_key);
-
     return S2N_SUCCESS;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/crypto/s2n_ecc_evp.c 
new/s2n-tls-1.7.10/crypto/s2n_ecc_evp.c
--- old/s2n-tls-1.7.9/crypto/s2n_ecc_evp.c      2026-09-01 22:54:11.000000000 
+0200
+++ new/s2n-tls-1.7.10/crypto/s2n_ecc_evp.c     2026-09-11 02:02:31.000000000 
+0200
@@ -37,6 +37,10 @@
 DEFINE_POINTER_CLEANUP_FUNC(EVP_PKEY_CTX *, EVP_PKEY_CTX_free);
 DEFINE_POINTER_CLEANUP_FUNC(EC_KEY *, EC_KEY_free);
 
+#if EVP_APIS_SUPPORTED
+DEFINE_POINTER_CLEANUP_FUNC(uint8_t *, OPENSSL_free);
+#endif
+
 #if !EVP_APIS_SUPPORTED
 DEFINE_POINTER_CLEANUP_FUNC(EC_POINT *, EC_POINT_free);
 #endif
@@ -444,16 +448,11 @@
     POSIX_ENSURE_REF(out);
 
 #if EVP_APIS_SUPPORTED
-    uint8_t *encoded_point = NULL;
+    DEFER_CLEANUP(uint8_t *encoded_point = NULL, OPENSSL_free_pointer);
 
     size_t size = EVP_PKEY_get1_tls_encodedpoint(ecc_evp_params->evp_pkey, 
&encoded_point);
-    if (size != ecc_evp_params->negotiated_curve->share_size) {
-        OPENSSL_free(encoded_point);
-        POSIX_BAIL(S2N_ERR_ECDHE_SERIALIZING);
-    } else {
-        POSIX_GUARD(s2n_stuffer_write_bytes(out, encoded_point, size));
-        OPENSSL_free(encoded_point);
-    }
+    POSIX_ENSURE(size == ecc_evp_params->negotiated_curve->share_size, 
S2N_ERR_ECDHE_SERIALIZING);
+    POSIX_GUARD(s2n_stuffer_write_bytes(out, encoded_point, size));
 #else
     uint8_t point_len = 0;
     struct s2n_blob point_blob = { 0 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/nix/shell.sh 
new/s2n-tls-1.7.10/nix/shell.sh
--- old/s2n-tls-1.7.9/nix/shell.sh      2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/nix/shell.sh     2026-09-11 02:02:31.000000000 +0200
@@ -250,6 +250,15 @@
     echo "rust_test: Exporting s2n-tls headers and libs for Cargo"
     export S2N_TLS_LIB_DIR=$(pwd)/build/lib
     export S2N_TLS_INCLUDE_DIR=$(pwd)/api
+    # The btls crate (BoringSSL, used by the boringssl integration tests) links
+    # libstdc++ dynamically, so the test binary needs libstdc++.so.6 at 
runtime.
+    # Nix does not place it on the default loader path. Add it here, scoped to
+    # this function, rather than in the devshell hook: a global LD_LIBRARY_PATH
+    # leaks the Nix libgcc/libstdc++ into system tools (e.g. gmake) used by 
other
+    # jobs' CMake compiler checks, which then fail on GLIBC version mismatches.
+    local cxx_lib_dir
+    cxx_lib_dir=$(dirname "$(cc -print-file-name=libstdc++.so.6)")
+    export 
LD_LIBRARY_PATH="${cxx_lib_dir}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
     echo "rust_test: Running Rust integration tests"
     cargo test --manifest-path bindings/rust/standard/integration/Cargo.toml 
--features boringssl
 )}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tests/unit/s2n_async_offload_cb_test.c 
new/s2n-tls-1.7.10/tests/unit/s2n_async_offload_cb_test.c
--- old/s2n-tls-1.7.9/tests/unit/s2n_async_offload_cb_test.c    2026-09-01 
22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/tests/unit/s2n_async_offload_cb_test.c   2026-09-11 
02:02:31.000000000 +0200
@@ -45,6 +45,23 @@
     return data->result;
 }
 
+/* A perform that always fails. s2n_async_offload_op_perform() only advances 
the
+ * op to S2N_ASYNC_COMPLETE on success, so using this leaves the op stuck in
+ * S2N_ASYNC_INVOKED, reproducing the failed-perform state that broke 
teardown. */
+static S2N_RESULT s2n_async_offload_test_failing_perform(struct 
s2n_async_offload_op *op)
+{
+    RESULT_BAIL(S2N_ERR_VERIFY_SIGNATURE);
+}
+
+/* Frees the heap allocation owned by the op below, so this regression test 
also
+ * exercises the op_data_free path (and lets valgrind confirm no leak). */
+static S2N_RESULT s2n_async_offload_test_op_data_free(struct 
s2n_async_offload_op *op)
+{
+    RESULT_ENSURE_REF(op);
+    RESULT_GUARD_POSIX(s2n_free(&op->op_data.async_pkey_verify.signature));
+    return S2N_RESULT_OK;
+}
+
 static int s2n_test_handshake_async(struct s2n_connection *server_conn, struct 
s2n_connection *client_conn,
         struct s2n_async_offload_cb_test *data)
 {
@@ -100,23 +117,6 @@
         EXPECT_FAILURE_WITH_ERRNO(s2n_async_offload_op_perform(&test_op), 
S2N_ERR_INVALID_STATE);
     }
 
-    /* Test: s2n_async_offload_op_wipe refuses to wipe an in-flight op */
-    {
-        struct s2n_async_offload_op op = { 0 };
-
-        /* Wipe succeeds when not invoked */
-        op.async_state = S2N_ASYNC_NOT_INVOKED;
-        EXPECT_OK(s2n_async_offload_op_wipe(&op));
-
-        /* Wipe succeeds when complete */
-        op.async_state = S2N_ASYNC_COMPLETE;
-        EXPECT_OK(s2n_async_offload_op_wipe(&op));
-
-        /* Wipe fails when still in flight */
-        op.async_state = S2N_ASYNC_INVOKED;
-        EXPECT_ERROR_WITH_ERRNO(s2n_async_offload_op_wipe(&op), 
S2N_ERR_ASYNC_BLOCKED);
-    }
-
     /* clang-format off */
     struct s2n_async_offload_test_case {
         bool async_test;
@@ -239,5 +239,33 @@
         }
     }
 
+    /* Regression test: a failed s2n_async_offload_op_perform() leaves the op 
in
+     * S2N_ASYNC_INVOKED. s2n_connection_free() must still fully tear the
+     * connection down (returning S2N_SUCCESS) rather than short-circuiting on 
the
+     * in-flight op and leaking the connection. */
+    {
+        struct s2n_connection *conn = s2n_connection_new(S2N_SERVER);
+        EXPECT_NOT_NULL(conn);
+
+        struct s2n_async_offload_op *op = &conn->async_offload_op;
+        op->conn = conn;
+        op->type = S2N_ASYNC_OFFLOAD_PKEY_VERIFY;
+        op->perform = s2n_async_offload_test_failing_perform;
+        op->op_data_free = s2n_async_offload_test_op_data_free;
+
+        /* Give the op some owned data so teardown has something real to free. 
*/
+        uint8_t sig_bytes[] = "test-signature";
+        EXPECT_SUCCESS(s2n_alloc(&op->op_data.async_pkey_verify.signature, 
sizeof(sig_bytes)));
+
+        /* Drive the op to INVOKED, then fail perform. Because perform fails, 
the
+         * op stays INVOKED instead of advancing to COMPLETE. */
+        op->async_state = S2N_ASYNC_INVOKED;
+        EXPECT_FAILURE_WITH_ERRNO(s2n_async_offload_op_perform(op), 
S2N_ERR_VERIFY_SIGNATURE);
+        EXPECT_EQUAL(op->async_state, S2N_ASYNC_INVOKED);
+
+        /* The fix: teardown is not short-circuited by the in-flight op. */
+        EXPECT_SUCCESS(s2n_connection_free(conn));
+    }
+
     END_TEST();
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tests/unit/s2n_ecc_evp_test.c 
new/s2n-tls-1.7.10/tests/unit/s2n_ecc_evp_test.c
--- old/s2n-tls-1.7.9/tests/unit/s2n_ecc_evp_test.c     2026-09-01 
22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/tests/unit/s2n_ecc_evp_test.c    2026-09-11 
02:02:31.000000000 +0200
@@ -172,6 +172,44 @@
         }
     };
     {
+        /* Test s2n_ecc_evp_write_params_point fails cleanly when the output
+         * stuffer is too small to hold the point.
+         *
+         * This exercises the error path where the encoded point has been
+         * allocated but the stuffer write fails. The allocation must be freed
+         * on this path (see DEFER_CLEANUP in s2n_ecc_evp_write_params_point);
+         * leak detection (valgrind/ASAN) in CI verifies no leak occurs. */
+        for (size_t i = 0; i < s2n_all_supported_curves_list_len; i++) {
+            struct s2n_ecc_evp_params test_params = { 0 };
+            struct s2n_stuffer wire = { 0 };
+
+            /* Non-growable stuffer smaller than the curve's share_size, so the
+             * write of the encoded point cannot succeed. */
+            EXPECT_SUCCESS(s2n_stuffer_alloc(&wire, 
s2n_all_supported_curves_list[i]->share_size - 1));
+
+            test_params.negotiated_curve = s2n_all_supported_curves_list[i];
+            EXPECT_SUCCESS(s2n_ecc_evp_generate_ephemeral_key(&test_params));
+            EXPECT_NOT_NULL(test_params.evp_pkey);
+
+            /* The errno differs by build: the EVP path writes via
+             * s2n_stuffer_write_bytes, which reports the out-of-space 
condition
+             * as S2N_ERR_STUFFER_IS_FULL. The non-EVP path writes via
+             * s2n_stuffer_raw_write, which returns NULL on the same condition
+             * and is caught by POSIX_ENSURE_REF as S2N_ERR_NULL. */
+#if EVP_APIS_SUPPORTED
+            
EXPECT_FAILURE_WITH_ERRNO(s2n_ecc_evp_write_params_point(&test_params, &wire),
+                    S2N_ERR_STUFFER_IS_FULL);
+#else
+            
EXPECT_FAILURE_WITH_ERRNO(s2n_ecc_evp_write_params_point(&test_params, &wire),
+                    S2N_ERR_NULL);
+#endif
+
+            /* Clean up */
+            EXPECT_SUCCESS(s2n_ecc_evp_params_free(&test_params));
+            EXPECT_SUCCESS(s2n_stuffer_free(&wire));
+        }
+    };
+    {
         /* TEST s2n_ecc_evp_read_params_point for all supported curves */
         for (size_t i = 0; i < s2n_all_supported_curves_list_len; i++) {
             struct s2n_ecc_evp_params write_params = { 0 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tests/unit/s2n_fingerprint_ja4_test.c 
new/s2n-tls-1.7.10/tests/unit/s2n_fingerprint_ja4_test.c
--- old/s2n-tls-1.7.9/tests/unit/s2n_fingerprint_ja4_test.c     2026-09-01 
22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/tests/unit/s2n_fingerprint_ja4_test.c    2026-09-11 
02:02:31.000000000 +0200
@@ -635,6 +635,51 @@
             };
         };
 
+        /* Only the first S2N_JA4_LIST_LIMIT (99) ciphers and extensions are
+         * collected into the sorted list. Lists longer than the limit 
therefore
+         * produce the same hash as a list of exactly the limit, since the 
helpers
+         * generate a deterministic sequence and the extra entries are not 
included.
+         */
+        {
+            const uint16_t counts_over_limit[] = { 99, 100, 255 };
+
+            /* Ciphers */
+            {
+                uint8_t limit_hash[S2N_TEST_OUTPUT_SIZE] = { 0 };
+                uint32_t limit_hash_size = 0;
+                EXPECT_OK(s2n_test_ja4_hash_from_cipher_count(99,
+                        sizeof(limit_hash), limit_hash, &limit_hash_size));
+
+                for (size_t i = 0; i < s2n_array_len(counts_over_limit); i++) {
+                    uint8_t hash[S2N_TEST_OUTPUT_SIZE] = { 0 };
+                    uint32_t hash_size = 0;
+                    
EXPECT_OK(s2n_test_ja4_hash_from_cipher_count(counts_over_limit[i],
+                            sizeof(hash), hash, &hash_size));
+
+                    EXPECT_EQUAL(hash_size, limit_hash_size);
+                    EXPECT_BYTEARRAY_EQUAL(hash, limit_hash, limit_hash_size);
+                }
+            };
+
+            /* Extensions */
+            {
+                uint8_t limit_hash[S2N_TEST_OUTPUT_SIZE] = { 0 };
+                uint32_t limit_hash_size = 0;
+                EXPECT_OK(s2n_test_ja4_hash_from_extension_count(99,
+                        sizeof(limit_hash), limit_hash, &limit_hash_size));
+
+                for (size_t i = 0; i < s2n_array_len(counts_over_limit); i++) {
+                    uint8_t hash[S2N_TEST_OUTPUT_SIZE] = { 0 };
+                    uint32_t hash_size = 0;
+                    
EXPECT_OK(s2n_test_ja4_hash_from_extension_count(counts_over_limit[i],
+                            sizeof(hash), hash, &hash_size));
+
+                    EXPECT_EQUAL(hash_size, limit_hash_size);
+                    EXPECT_BYTEARRAY_EQUAL(hash, limit_hash, limit_hash_size);
+                }
+            };
+        };
+
         /* Test ALPN */
         {
             /* Test basic ALPN
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tls/s2n_async_offload.c 
new/s2n-tls-1.7.10/tls/s2n_async_offload.c
--- old/s2n-tls-1.7.9/tls/s2n_async_offload.c   2026-09-01 22:54:11.000000000 
+0200
+++ new/s2n-tls-1.7.10/tls/s2n_async_offload.c  2026-09-11 02:02:31.000000000 
+0200
@@ -64,10 +64,17 @@
     return S2N_SUCCESS;
 }
 
-S2N_RESULT s2n_async_offload_op_wipe(struct s2n_async_offload_op *op)
+/**
+ * Unconditionally releases the op's resources, regardless of async_state.
+ *
+ * This is the connection teardown path (s2n_connection_free / 
s2n_connection_wipe).
+ * It MUST always fully free the op, even if a failed 
s2n_async_offload_op_perform()
+ * left the op in S2N_ASYNC_INVOKED; otherwise the remainder of teardown is 
skipped
+ * and the connection leaks.
+ */
+S2N_RESULT s2n_async_offload_op_free(struct s2n_async_offload_op *op)
 {
     RESULT_ENSURE_REF(op);
-    RESULT_ENSURE(op->async_state != S2N_ASYNC_INVOKED, S2N_ERR_ASYNC_BLOCKED);
     if (op->op_data_free == NULL) {
         return S2N_RESULT_OK;
     }
@@ -80,6 +87,10 @@
 /**
  * MUST be called at the end of each handshake state handler that may invoke 
async_offload_cb
  * to clean up the op object for its next use.
+ *
+ * Unlike s2n_async_offload_op_free(), this is the handshake-path cleanup and 
refuses
+ * to free an op that is still in flight (S2N_ASYNC_INVOKED): s2n still holds
+ * references to its data, so wiping mid-negotiation would be a logic error.
  */
 S2N_RESULT s2n_async_offload_op_reset(struct s2n_async_offload_op *op)
 {
@@ -90,7 +101,7 @@
     }
 
     RESULT_ENSURE(op->async_state == S2N_ASYNC_COMPLETE, 
S2N_ERR_INVALID_STATE);
-    RESULT_GUARD(s2n_async_offload_op_wipe(op));
+    RESULT_GUARD(s2n_async_offload_op_free(op));
     return S2N_RESULT_OK;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tls/s2n_async_offload.h 
new/s2n-tls-1.7.10/tls/s2n_async_offload.h
--- old/s2n-tls-1.7.9/tls/s2n_async_offload.h   2026-09-01 22:54:11.000000000 
+0200
+++ new/s2n-tls-1.7.10/tls/s2n_async_offload.h  2026-09-11 02:02:31.000000000 
+0200
@@ -56,6 +56,6 @@
 };
 
 S2N_RESULT s2n_async_offload_cb_invoke(struct s2n_connection *conn, struct 
s2n_async_offload_op *op);
-S2N_RESULT s2n_async_offload_op_wipe(struct s2n_async_offload_op *op);
+S2N_RESULT s2n_async_offload_op_free(struct s2n_async_offload_op *op);
 S2N_RESULT s2n_async_offload_op_reset(struct s2n_async_offload_op *op);
 bool s2n_async_offload_op_is_in_allow_list(struct s2n_config *config, 
s2n_async_offload_op_type op_type);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tls/s2n_config.c 
new/s2n-tls-1.7.10/tls/s2n_config.c
--- old/s2n-tls-1.7.9/tls/s2n_config.c  2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/tls/s2n_config.c 2026-09-11 02:02:31.000000000 +0200
@@ -158,6 +158,8 @@
         return 0;
     }
     s2n_pkey_type cert_type = 
s2n_cert_chain_and_key_get_pkey_type(cert_key_pair);
+    POSIX_ENSURE(cert_type >= 0, S2N_ERR_CERT_TYPE_UNSUPPORTED);
+    POSIX_ENSURE(cert_type < S2N_CERT_TYPE_COUNT, 
S2N_ERR_CERT_TYPE_UNSUPPORTED);
     struct s2n_blob s2n_map_value = { 0 };
     bool key_found = false;
     POSIX_GUARD_RESULT(s2n_map_lookup(domain_name_to_cert_map, name, 
&s2n_map_value, &key_found));
@@ -545,6 +547,8 @@
     
POSIX_GUARD_RESULT(s2n_security_policy_validate_certificate_chain(config->security_policy,
 cert_key_pair));
 
     s2n_pkey_type cert_type = 
s2n_cert_chain_and_key_get_pkey_type(cert_key_pair);
+    POSIX_ENSURE(cert_type >= 0, S2N_ERR_CERT_TYPE_UNSUPPORTED);
+    POSIX_ENSURE(cert_type < S2N_CERT_TYPE_COUNT, 
S2N_ERR_CERT_TYPE_UNSUPPORTED);
     config->is_rsa_cert_configured |= (cert_type == S2N_PKEY_TYPE_RSA);
 
     /* Perform all fallible checks BEFORE inserting into the domain name map.
@@ -553,8 +557,6 @@
      * resulting in dangling pointers and a use-after-free during SNI lookup.
      */
     if (!config->default_certs_are_explicit) {
-        POSIX_ENSURE(cert_type >= 0, S2N_ERR_CERT_TYPE_UNSUPPORTED);
-        POSIX_ENSURE(cert_type < S2N_CERT_TYPE_COUNT, 
S2N_ERR_CERT_TYPE_UNSUPPORTED);
         if (config->default_certs_by_type.certs[cert_type] != NULL) {
             /* Because library-owned certificates are tracked and cleaned up 
via the
              * default_certs_by_type mapping, library-owned chains MUST be set 
as the default
@@ -744,6 +746,8 @@
     for (size_t i = 0; i < num_cert_key_pairs; i++) {
         POSIX_ENSURE_REF(cert_key_pairs[i]);
         s2n_pkey_type cert_type = 
s2n_cert_chain_and_key_get_pkey_type(cert_key_pairs[i]);
+        POSIX_ENSURE(cert_type >= 0, S2N_ERR_CERT_TYPE_UNSUPPORTED);
+        POSIX_ENSURE(cert_type < S2N_CERT_TYPE_COUNT, 
S2N_ERR_CERT_TYPE_UNSUPPORTED);
         S2N_ERROR_IF(new_defaults.certs[cert_type] != NULL, 
S2N_ERR_MULTIPLE_DEFAULT_CERTIFICATES_PER_AUTH_TYPE);
         new_defaults.certs[cert_type] = cert_key_pairs[i];
     }
@@ -751,6 +755,8 @@
     POSIX_GUARD(s2n_config_clear_default_certificates(config));
     for (size_t i = 0; i < num_cert_key_pairs; i++) {
         s2n_pkey_type cert_type = 
s2n_cert_chain_and_key_get_pkey_type(cert_key_pairs[i]);
+        POSIX_ENSURE(cert_type >= 0, S2N_ERR_CERT_TYPE_UNSUPPORTED);
+        POSIX_ENSURE(cert_type < S2N_CERT_TYPE_COUNT, 
S2N_ERR_CERT_TYPE_UNSUPPORTED);
         config->is_rsa_cert_configured |= (cert_type == S2N_PKEY_TYPE_RSA);
         config->default_certs_by_type.certs[cert_type] = cert_key_pairs[i];
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tls/s2n_connection.c 
new/s2n-tls-1.7.10/tls/s2n_connection.c
--- old/s2n-tls-1.7.9/tls/s2n_connection.c      2026-09-01 22:54:11.000000000 
+0200
+++ new/s2n-tls-1.7.10/tls/s2n_connection.c     2026-09-11 02:02:31.000000000 
+0200
@@ -281,7 +281,7 @@
     POSIX_GUARD(s2n_stuffer_free(&conn->handshake.io));
     POSIX_GUARD(s2n_stuffer_free(&conn->post_handshake.in));
     s2n_x509_validator_wipe(&conn->x509_validator);
-    POSIX_GUARD_RESULT(s2n_async_offload_op_wipe(&conn->async_offload_op));
+    POSIX_GUARD_RESULT(s2n_async_offload_op_free(&conn->async_offload_op));
     POSIX_GUARD(s2n_client_hello_free_raw_message(&conn->client_hello));
     POSIX_GUARD(s2n_free(&conn->application_protocols_overridden));
     POSIX_GUARD(s2n_free(&conn->cookie));
@@ -552,7 +552,7 @@
     POSIX_GUARD(s2n_stuffer_free(&conn->in));
 
     POSIX_GUARD_RESULT(s2n_psk_parameters_wipe(&conn->psk_params));
-    POSIX_GUARD_RESULT(s2n_async_offload_op_wipe(&conn->async_offload_op));
+    POSIX_GUARD_RESULT(s2n_async_offload_op_free(&conn->async_offload_op));
 
     /* Wipe the I/O-related info and restore the original socket if necessary 
*/
     POSIX_GUARD(s2n_connection_wipe_io(conn));
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tls/s2n_fingerprint_ja4.c 
new/s2n-tls-1.7.10/tls/s2n_fingerprint_ja4.c
--- old/s2n-tls-1.7.9/tls/s2n_fingerprint_ja4.c 2026-09-01 22:54:11.000000000 
+0200
+++ new/s2n-tls-1.7.10/tls/s2n_fingerprint_ja4.c        2026-09-11 
02:02:31.000000000 +0200
@@ -362,6 +362,7 @@
     RESULT_GUARD_POSIX(s2n_stuffer_init_written(&cipher_suites, 
&ch->cipher_suites));
 
     DEFER_CLEANUP(struct s2n_stuffer *iana_list = sort_space, 
s2n_stuffer_wipe_pointer);
+    size_t written_count = 0;
     while (s2n_stuffer_data_available(&cipher_suites)) {
         uint16_t iana = 0;
         RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&cipher_suites, &iana));
@@ -372,6 +373,14 @@
         if (s2n_fingerprint_is_grease_value(iana)) {
             continue;
         }
+        /* The count is capped at 99 (see s2n_fingerprint_ja4_count), so there
+         * is no need to collect more than S2N_JA4_LIST_LIMIT entries. This 
keeps
+         * the workspace within its pre-sized bounds.
+         */
+        if (written_count >= S2N_JA4_LIST_LIMIT) {
+            continue;
+        }
+        written_count++;
         RESULT_GUARD(s2n_stuffer_write_uint16_hex(iana_list, iana));
         RESULT_GUARD_POSIX(s2n_stuffer_write_char(iana_list, 
S2N_JA4_LIST_DIV));
     }
@@ -426,6 +435,7 @@
     RESULT_GUARD_POSIX(s2n_stuffer_init_written(&extensions, 
&ch->extensions.raw));
 
     DEFER_CLEANUP(struct s2n_stuffer *iana_list = sort_space, 
s2n_stuffer_wipe_pointer);
+    size_t written_count = 0;
     while (s2n_stuffer_data_available(&extensions)) {
         uint16_t iana = 0;
         RESULT_GUARD(s2n_fingerprint_parse_extension(&extensions, &iana));
@@ -451,6 +461,15 @@
         if (iana == TLS_EXTENSION_SERVER_NAME || iana == S2N_EXTENSION_ALPN) {
             continue;
         }
+        /* The count is capped at 99 (see s2n_fingerprint_ja4_count), so there
+         * is no need to collect more than S2N_JA4_LIST_LIMIT entries. This 
keeps
+         * the workspace within its pre-sized bounds. extensions_count is still
+         * incremented above for every extension.
+         */
+        if (written_count >= S2N_JA4_LIST_LIMIT) {
+            continue;
+        }
+        written_count++;
         RESULT_GUARD(s2n_stuffer_write_uint16_hex(iana_list, iana));
         RESULT_GUARD_POSIX(s2n_stuffer_write_char(iana_list, 
S2N_JA4_LIST_DIV));
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tls/s2n_psk.c 
new/s2n-tls-1.7.10/tls/s2n_psk.c
--- old/s2n-tls-1.7.9/tls/s2n_psk.c     2026-09-01 22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/tls/s2n_psk.c    2026-09-11 02:02:31.000000000 +0200
@@ -421,7 +421,7 @@
     POSIX_ENSURE_REF(binder_hash);
     POSIX_ENSURE_REF(output_binder);
 
-    DEFER_CLEANUP(struct s2n_tls13_keys psk_keys, s2n_tls13_keys_free);
+    DEFER_CLEANUP(struct s2n_tls13_keys psk_keys = { 0 }, s2n_tls13_keys_free);
     POSIX_GUARD(s2n_tls13_keys_init(&psk_keys, psk->hmac_alg));
     POSIX_ENSURE_EQ(binder_hash->size, psk_keys.size);
     POSIX_ENSURE_EQ(output_binder->size, psk_keys.size);
@@ -447,7 +447,7 @@
     POSIX_ENSURE_REF(psk);
     POSIX_ENSURE_REF(binder_to_verify);
 
-    DEFER_CLEANUP(struct s2n_tls13_keys psk_keys, s2n_tls13_keys_free);
+    DEFER_CLEANUP(struct s2n_tls13_keys psk_keys = { 0 }, s2n_tls13_keys_free);
     POSIX_GUARD(s2n_tls13_keys_init(&psk_keys, psk->hmac_alg));
     POSIX_ENSURE_EQ(binder_to_verify->size, psk_keys.size);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/s2n-tls-1.7.9/tls/s2n_server_cert_request.c 
new/s2n-tls-1.7.10/tls/s2n_server_cert_request.c
--- old/s2n-tls-1.7.9/tls/s2n_server_cert_request.c     2026-09-01 
22:54:11.000000000 +0200
+++ new/s2n-tls-1.7.10/tls/s2n_server_cert_request.c    2026-09-11 
02:02:31.000000000 +0200
@@ -161,7 +161,10 @@
             POSIX_ENSURE_REF(cert);
         }
         conn->handshake_params.our_chain_and_key = cert;
-        conn->handshake_params.client_cert_pkey_type = 
s2n_cert_chain_and_key_get_pkey_type(cert);
+        s2n_pkey_type cert_type = s2n_cert_chain_and_key_get_pkey_type(cert);
+        POSIX_ENSURE(cert_type >= 0, S2N_ERR_CERT_TYPE_UNSUPPORTED);
+        POSIX_ENSURE(cert_type < S2N_CERT_TYPE_COUNT, 
S2N_ERR_CERT_TYPE_UNSUPPORTED);
+        conn->handshake_params.client_cert_pkey_type = cert_type;
 
         POSIX_GUARD_RESULT(s2n_signature_algorithm_select(conn));
     }

Reply via email to