VYPR
Critical severity9.8NVD Advisory· Published Apr 24, 2026· Updated Apr 28, 2026

CVE-2026-41676

CVE-2026-41676

Description

rust-openssl provides OpenSSL bindings for the Rust programming language. From 0.9.27 to before 0.10.78, Deriver::derive (and PkeyCtxRef::derive) sets len = buf.len() and passes it as the in/out length to EVP_PKEY_derive, relying on OpenSSL to honor it. On OpenSSL 1.1.x, X25519, X448, DH and HKDF-extract ignore the incoming *keylen, unconditionally writing the full shared secret (32/56/prime-size bytes). A caller passing a short slice gets a heap/stack overflow from safe code. OpenSSL 3.x providers do check, so this only impacts older OpenSSL. This vulnerability is fixed in 0.10.78.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
opensslcrates.io
>= 0.9.27, < 0.10.780.10.78

Affected products

1

Patches

1
09b425e5f59a

Check derive output buffer length on OpenSSL 1.1.x (#2606)

https://github.com/rust-openssl/rust-opensslAlex GaynorApr 19, 2026via ghsa
2 files changed · +114 1
  • openssl/src/derive.rs+40 0 modified
    @@ -131,6 +131,26 @@ impl<'a> Deriver<'a> {
         /// Returns the number of bytes written.
         #[corresponds(EVP_PKEY_derive)]
         pub fn derive(&mut self, buf: &mut [u8]) -> Result<usize, ErrorStack> {
    +        // See the matching comment in `PkeyCtxRef::derive`. On 1.1.x some
    +        // pmeths ignore *keylen and write the full natural output
    +        // (X25519/X448), while others (default ECDH) deliberately truncate.
    +        // Derive into a temp buffer when the probed size exceeds the
    +        // caller's buffer to prevent OOB writes while preserving the
    +        // truncation semantics.
    +        #[cfg(any(all(ossl110, not(ossl300)), libressl))]
    +        {
    +            let required = self.len()?;
    +            if required != usize::MAX && buf.len() < required {
    +                let mut temp = vec![0u8; required];
    +                let mut len = required;
    +                unsafe {
    +                    cvt(ffi::EVP_PKEY_derive(self.0, temp.as_mut_ptr(), &mut len))?;
    +                }
    +                let copy_len = buf.len().min(len);
    +                buf[..copy_len].copy_from_slice(&temp[..copy_len]);
    +                return Ok(copy_len);
    +            }
    +        }
             let mut len = buf.len();
             unsafe {
                 cvt(ffi::EVP_PKEY_derive(
    @@ -195,6 +215,26 @@ mod test {
             assert!(!shared.is_empty());
         }
     
    +    #[test]
    +    #[cfg(any(ossl111, libressl370))]
    +    fn derive_undersized_buffer() {
    +        // Without the temp-buffer fallback in this crate, X25519 on 1.1.x
    +        // would OOB into a 4-byte buffer because it ignores *keylen.
    +        // On 1.1.x / LibreSSL the fallback kicks in and we return the
    +        // truncated prefix. On 3.0+ the provider rejects undersized
    +        // buffers before any write happens, so the call errors out.
    +        let pkey = PKey::generate_x25519().unwrap();
    +        let pkey2 = PKey::generate_x25519().unwrap();
    +        let mut deriver = Deriver::new(&pkey).unwrap();
    +        deriver.set_peer(&pkey2).unwrap();
    +        let mut buf = [0u8; 4];
    +        let result = deriver.derive(&mut buf);
    +        #[cfg(any(all(ossl110, not(ossl300)), libressl))]
    +        assert_eq!(result.unwrap(), 4);
    +        #[cfg(all(ossl300, not(libressl)))]
    +        assert!(result.is_err());
    +    }
    +
         #[test]
         #[cfg(ossl300)]
         fn test_ec_key_derive_ex() {
    
  • openssl/src/pkey_ctx.rs+74 1 modified
    @@ -820,7 +820,57 @@ impl<T> PkeyCtxRef<T> {
         ///
         /// If `buf` is set to `None`, an upper bound on the number of bytes required for the buffer will be returned.
         #[corresponds(EVP_PKEY_derive)]
    -    pub fn derive(&mut self, buf: Option<&mut [u8]>) -> Result<usize, ErrorStack> {
    +    #[allow(unused_mut)]
    +    pub fn derive(&mut self, mut buf: Option<&mut [u8]>) -> Result<usize, ErrorStack> {
    +        // On OpenSSL 1.1.x some pmeths ignore *keylen and unconditionally
    +        // write the full natural output size (X25519, X448, HKDF-extract),
    +        // which can overflow a shorter caller-provided buffer. Others honor
    +        // *keylen by truncating the output (notably the default ECDH
    +        // EVP_PKEY_EC pmeth, where the OpenSSL source explicitly documents
    +        // that *keylen below the natural size "is not an error, the result
    +        // is truncated").
    +        //
    +        // We can't distinguish those two groups from the probe alone, so
    +        // when the probe reports a natural size larger than the caller's
    +        // buffer, derive into a temporary buffer of the probed size and
    +        // copy the leading bytes out. This prevents the OOB write for the
    +        // ignore-*keylen group and produces the same bytes for the
    +        // honor-*keylen group (ECDH_compute_key copies leading bytes of
    +        // the shared secret either way).
    +        //
    +        // Some pmeths (HKDF extract-and-expand and expand-only on 1.1.x)
    +        // don't support the NULL-out probe and fail it with an empty error
    +        // stack; those honor *keylen during derivation, so clear the
    +        // errors and proceed with the direct path. usize::MAX is a
    +        // sentinel some pmeths use when *keylen is caller-chosen.
    +        //
    +        // 3.0+ providers check the buffer size themselves, so this whole
    +        // dance is cfg-gated to 1.1.x and LibreSSL.
    +        #[cfg(any(all(ossl110, not(ossl300)), libressl))]
    +        {
    +            if let Some(b) = buf.as_deref_mut() {
    +                let mut required = 0;
    +                let probe_ok = unsafe {
    +                    ffi::EVP_PKEY_derive(self.as_ptr(), ptr::null_mut(), &mut required) == 1
    +                };
    +                if !probe_ok {
    +                    let _ = ErrorStack::get();
    +                } else if required != usize::MAX && b.len() < required {
    +                    let mut temp = vec![0u8; required];
    +                    let mut len = required;
    +                    unsafe {
    +                        cvt(ffi::EVP_PKEY_derive(
    +                            self.as_ptr(),
    +                            temp.as_mut_ptr(),
    +                            &mut len,
    +                        ))?;
    +                    }
    +                    let copy_len = b.len().min(len);
    +                    b[..copy_len].copy_from_slice(&temp[..copy_len]);
    +                    return Ok(copy_len);
    +                }
    +            }
    +        }
             let mut len = buf.as_ref().map_or(0, |b| b.len());
             unsafe {
                 cvt(ffi::EVP_PKEY_derive(
    @@ -1042,6 +1092,29 @@ mod test {
             ctx.derive_to_vec(&mut buf).unwrap();
         }
     
    +    #[test]
    +    #[cfg(any(ossl111, libressl370))]
    +    fn derive_undersized_buffer() {
    +        // Without the temp-buffer fallback in this crate, X25519 on 1.1.x
    +        // would OOB into a 4-byte buffer because it ignores *keylen.
    +        // On 1.1.x / LibreSSL the fallback kicks in and we return the
    +        // truncated prefix. On 3.0+ the provider rejects undersized
    +        // buffers before any write happens, so the call errors out.
    +        let key1 = PKey::generate_x25519().unwrap();
    +        let key2 = PKey::generate_x25519().unwrap();
    +
    +        let mut ctx = PkeyCtx::new(&key1).unwrap();
    +        ctx.derive_init().unwrap();
    +        ctx.derive_set_peer(&key2).unwrap();
    +
    +        let mut buf = [0u8; 4];
    +        let result = ctx.derive(Some(&mut buf));
    +        #[cfg(any(all(ossl110, not(ossl300)), libressl))]
    +        assert_eq!(result.unwrap(), 4);
    +        #[cfg(all(ossl300, not(libressl)))]
    +        assert!(result.is_err());
    +    }
    +
         #[test]
         #[cfg(not(any(boringssl, awslc)))]
         fn cmac_keygen() {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.