Memory Leak in Actix with Mutual Tls
Memory Leak in Actix with Mutual Tls — how this specific combination creates or exposes the vulnerability
A memory leak in an Actix web service using mutual TLS (mTLS) typically arises when TLS session state and application-level objects are retained beyond their intended lifetime. mTLS requires both client and server to present valid certificates, which increases the number of runtime objects (certificate pools, SSL contexts, peer certificate data, and authenticated identity information). When connections are reused via keep-alive or when the server processes many short-lived handshakes, references held in Actix actors, request extensions, or async task closures can prevent Rust’s drop from reclaiming memory. Common patterns that contribute include storing per-request data in Data<T> or HttpRequest::extensions without cleaning up, spawning futures that capture large certificates or buffers by value, and failing to release resources in disconnect or timeout handlers.
In the context of an automated security scan, these issues can manifest as steady growth in process memory over repeated mTLS handshakes and requests. The scan’s unauthenticated checks may not directly measure heap growth, but they can detect insecure defaults and missing hardening steps that exacerbate leaks, such as missing idle-timeout configuration on TLS acceptors or missing limits on concurrent handshake buffers. For example, a server that does not cap the size of client certificate chains or does not enforce reasonable limits on concurrent TLS sessions can experience higher memory pressure under sustained load. The LLM/AI Security checks may also flag insecure code patterns that contribute to retention, such as logging full client certificates or chaining futures that hold references across await points without explicit cleanup.
Because mTLS adds cryptographic material and per-connection state, the impact of a leak is amplified compared to plaintext HTTP. Each accepted connection with a client certificate may allocate additional structures for certificate verification and identity mapping. If these are stored in long-lived containers or global configuration, the cumulative effect can degrade performance and increase the attack surface for resource-exhaustion scenarios. Security findings from scans in this area often prioritize detection of missing cleanup in handlers, missing timeouts on TLS acceptors, and improper use of Actix actors that hold buffers across messages.
Mutual Tls-Specific Remediation in Actix — concrete code fixes
To mitigate memory leaks with mTLS in Actix, focus on limiting retained state, cleaning up extensions, and configuring timeouts. Below are concrete, idiomatic examples that you can adapt to your service.
1. Proper cleanup of request extensions and Data
Avoid storing large or long-lived objects in HttpRequest::extensions. If you must store per-request metadata, ensure it is removed at the end of the request lifecycle. Use actix_web::dev::Payload and guards to clean up resources deterministically.
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, middleware::Logger};
use std::sync::Arc;
struct MyState {
cert_pool: Arc,
}
async fn index(req: HttpRequest) -> HttpResponse {
// If you stored something in extensions, remove it when done
if let Some(cert_info) = req.extensions().get::() {
// process cert_info
println!("Peer cert info: {}", cert_info);
}
// Ensure no heavy objects linger in extensions
HttpResponse::Ok().body("ok")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.app_data(web::Data::new(MyState { cert_pool: Arc::new(load_cert_pool()) }))
.route("/", web::get().to(index))
})
.bind_rustls("127.0.0.1:8443", create_server_config())?
.run()
.await
}
fn load_cert_pool() -> rustls::RootCertStore {
let mut root_store = rustls::RootCertStore::empty();
// load CA certs for verifying client certs
root_store
.add(&rustls::Certificate(read_file("ca.crt")))
.expect("failed to add CA");
root_store
}
fn create_server_config() -> rustls::ServerConfig {
let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_client_cert_verifier(Arc::new(MyClientVerifier))
.with_single_cert(load_cert_chain(), load_private_key())
.expect("bad cert/key");
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
config
}
struct MyClientVerifier;
impl rustls::server::ClientCertVerifier for MyClientVerifier {
fn client_auth_mandatory(&self) -> bool { true }
fn verify_client_cert(
&self,
end_entities: &[rustls::Certificate],
_intermediates: &[rustls::Certificate],
_server_auth: &rustls::ServerCertVerifier,
_sni: webpki::DNSNameRef,
) -> Result {
// Validate end_entities and avoid retaining references to certs beyond this call
if end_entities.is_empty() {
return Err(rustls::client_authn::ClientAuthError::InvalidCertificate);
}
// Perform validation; do not store end_entities in static or long-lived structures
Ok(())
}
}
2. Configure timeouts and limit concurrent handshake state
Set reasonable limits on TLS acceptor buffers and idle timeouts to prevent unbounded retention of cryptographic buffers. With Actix, you can tune the underlying actix-rs and native-tls/rustls configuration to close idle or stalled connections promptly.
use actix_web::HttpServer;
use std::time::Duration;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.route("/", web::get().to(|| async { HttpResponse::Ok().body("secure") }))
})
.bind_rustls(
"127.0.0.1:8443",
create_server_config(),
)?
.worker_threads(2)
.keep_alive(Some(Duration::from_secs(30)))
.max_http_version10(100) // limit concurrent connections to control memory
.run()
.await
}
3. Avoid capturing large buffers in async closures
When spawning tasks or creating futures inside handlers, do not move large certificate vectors or buffers by value. Use lightweight references and ensure any owned copies are intentionally scoped and dropped.
use actix_web::web;
use std::sync::Arc;
async fn handle_with_buffer(
payload: web::Payload,
cert_ref: Arc,
) -> Result {
// Use cert_ref by reference where possible; clone only if necessary and scoped
let cert_clone = Arc::clone(&cert_ref);
actix_web::rt::spawn(async move {
// process cert_clone within the task scope
drop(cert_clone); // ensure timely drop if not needed beyond this scope
});
Ok(HttpResponse::Ok().finish())
}
4. Monitor and test under load
Run load tests with mTLS enabled while observing memory usage. Use tools to confirm that memory stabilizes across repeated handshakes. If leaks are suspected, inspect handler code for retained extensions, Data, or Arc references that are never released.