On 2024-06-12, a critical vulnerability—CVE-2025-24898—was disclosed for the rust-openssl crate, a popular set of OpenSSL bindings for the Rust programming language. This vulnerability can lead to a use after free scenario in the ssl::select_next_proto function, potentially causing server crashes or, worse, the leakage of sensitive memory content from a server to a client.

In this article, we’ll break down what caused the bug, show how it could be exploited, and let you know how to fix it in your code. We’ll also show code snippets for illustration. This post uses simplified language to make sure all Rust developers, regardless of deep expertise, can understand the issue.

What is rust-openssl and ssl::select_next_proto?

rust-openssl provides safe (and some unsafe) Rust bindings to the famous OpenSSL C library for cryptography. Many web servers and secure client apps in Rust depend on it.

The function ssl::select_next_proto is part of the protocol negotiation process, specifically for ALPN (Application-Layer Protocol Negotiation), which is used to determine which protocol (like HTTP/1.1 or HTTP/2) will be used over a secure connection.

This function is typically used as a callback with SslContextBuilder::set_alpn_select_callback.

The Vulnerability

Affected Versions:
All versions before openssl crate .10.70.

What Went Wrong?

The signature of ssl::select_next_proto allowed it to produce a reference to memory inside the server argument. However, the lifetime of this reference was incorrectly bound only to the client argument.

If the buffer for server is created inside the callback and goes out of scope after the callback returns, but the reference is assumed to live as long as the client buffer (which might be longer), then BOOM! — a use-after-free. That means the server could crash or, even worse, hand out random memory (that could include secrets) to the connecting client.

Code Example – Vulnerable

use openssl::ssl::{SslContextBuilder, AlpnError};

// This callback is vulnerable if server is constructed inside the callback
ctx.set_alpn_select_callback(|_ssl, client| {
    // BAD: local variable, dies after this function returns
    let server_protocols = vec![b"http/1.1", b"h2"];
    match openssl::ssl::select_next_proto(server_protocols.as_slice(), client) {
        Some(selected) => Ok(selected.to_vec()),
        None => Err(AlpnError::NOACK)
    }
});

*The selected reference may outlive server_protocols and point to freed memory afterward.*

Denial of Service: The server could crash (panic) if the memory gets messed up.

- Information Leak: Arbitrary memory contents (previous secrets, session data, etc.) could be sent to the client.

Simplified Exploit Proof-of-Concept

Given the memory safety guarantees of Rust, this exploit tends to panic/crash rather than execute arbitrary code, but information disclosure is possible if serialized arbitrary memory is sent to the client. Here’s how an attacker could trigger the bug:

// Simulate the unsafe callback usage with short-lived buffer
fn vulnerable_select_callback(client: &[u8]) -> Option<Vec<u8>> {
    let server_protocols = vec![b"http/1.1", b"h2"];
    // BAD: select_next_proto returns reference tied only to client
    let proto = openssl::ssl::select_next_proto(server_protocols.as_slice(), client)?;
    Some(proto.to_vec())
}

An attacker sending malformed or carefully crafted ALPN negotiation data could receive a response comprised of arbitrary memory from your server.

The function ssl::select_next_proto received a new, corrected signature.

- Now, the returned reference has a lifetime bound to both server and client arguments, making it impossible for a reference to outlive the data it refers to.

From the changelog

> "Fix the signature of ssl::select_next_proto to properly constrain the output buffer's lifetime to that of both input buffers."

Patched Usage

// Safe: server buffer lives as long as required
let server_protocols = vec![b"http/1.1", b"h2"];
ctx.set_alpn_select_callback(move |_ssl, client| {
    match openssl::ssl::select_next_proto(server_protocols.as_slice(), client) {
        Some(selected) => Ok(selected.to_vec()), // selected cannot outlive server_protocols
        None => Err(AlpnError::NOACK)
    }
});

Or, better, make server_protocols live at least as long as the context or callback.

Review Lifetimes:

- Ensure that all buffers’ lifetimes line up as intended. Rust's borrow checker now helps enforce this for you!

References

- Original RustOpenSSL Advisory
- CVE Database Entry for CVE-2025-24898 *(link updates as CVE goes public)*
- Release notes — openssl .10.70
- ALPN in OpenSSL and Rust
- Upgrading Dependencies in Cargo

Conclusion

If you're using rust-openssl and especially if you’re implementing custom ALPN logic, you must upgrade to at least .10.70 and check your use of select_next_proto. Failing to do so could leave your server open to (accidental or targeted) memory disclosure or denial-of-service attacks.

Stay safe, Rustaceans! 🦀

*Have you patched your stack? Have you checked callback buffer lifetimes? Comment below with questions or experiences!*


*This article is exclusive to this channel and meant for simple, direct sharing within the Rust community. Feel free to share and cite with credit.*

Timeline

Published on: 02/03/2025 18:15:43 UTC
Last modified on: 02/11/2025 08:15:32 UTC