Use After Free in Actix with Basic Auth
Use After Free in Actix with Basic Auth — how this specific combination creates or exposes the vulnerability
Use After Free (UAF) in Actix when combined with Basic Authentication can arise when request-scoped data (such as parsed credentials or headers) is accessed after the owning structure has been deallocated or moved. In Rust, this typically manifests as holding a reference that outlives the data it points to, often due to incorrect lifetime annotations in handlers or middleware that processes Basic Auth headers.
With Basic Auth, the client sends an Authorization: Basic <base64> header. Actix extracts and decodes this value, often creating temporary structures to hold the username and password. If a handler or extractor keeps a reference to a slice or a borrowed field from the decoded credentials without ensuring the underlying buffer remains valid, the reference can dangle once the decoding buffer is dropped at the end of the request. For example, storing a reference to a decoded username string in a longer-lived cache or spawning an async task that captures the reference without ownership can lead to UAF when the buffer is freed.
Consider a handler that decodes Basic Auth and passes a reference to the password into an async block:
use actix_web::{web, HttpRequest, Responder};
use base64::Engine;
async fn handler(req: HttpRequest) -> impl Responder {
let auth_header = req.headers().get("Authorization")?.to_str().ok()?;
if let Some(credentials) = auth_header.strip_prefix("Basic ") {
let decoded = base64::engine::general_purpose::STANDARD.decode(credentials).ok()?;
let parts: Vec<String> = String::from_utf8(decoded).ok()?.splitn(2, ':').map(String::from).collect();
if parts.len() == 2 {
let password_ref = &parts[1]; // reference into `parts`
actix_web::web::block(move || {
// UAF: `password_ref` may outlive `parts` if captured incorrectly
println!("Password: {}", password_ref);
}).await;
}
}
"ok"
}
In the above, password_ref borrows from parts, which is owned by the handler’s stack frame. If the async block captures password_ref and the handler returns before the block completes, the reference can become invalid. This is a classic UAF pattern exacerbated by mixing async execution and borrowed data from per-request decoding.
Middleware that inspects Basic Auth headers must also be careful with lifetimes. If a middleware stores a reference extracted from the request for later validation, and the request data is dropped between the middleware phase and the handler, the stored reference becomes a use-after-free hazard. The risk is higher when credentials are parsed once and reused across multiple stages without cloning or owning the data.
An attacker can potentially exploit UAF to cause undefined behavior, data corruption, or crashes, which may lead to denial of service or, in some environments, code execution depending on surrounding memory layout. While Actix’s runtime and extractor model reduce the surface compared to manual reference handling, combining Basic Auth parsing with async tasks and improper lifetimes remains risky.
Basic Auth-Specific Remediation in Actix — concrete code fixes
To eliminate Use After Free risks with Basic Auth in Actix, ensure that any data derived from credentials is owned for the duration of its use, especially when crossing async boundaries. Avoid holding references into temporary decoded buffers; instead, clone or move owned values into async tasks.
Here is a safe pattern using owned data:
use actix_web::{web, HttpRequest, Responder};
use base64::engine;
async fn safe_handler(req: HttpRequest) -> impl Responder {
let auth_header = match req.headers().get("Authorization") {
Some(h) => h.to_str().unwrap_or(""),
None => return "unauthorized",
};
if let Some(credentials) = auth_header.strip_prefix("Basic ") {
let decoded = match engine::general_purpose::STANDARD.decode(credentials) {
Ok(d) => d,
Err(_) => return "bad auth",
};
let credentials_str = match String::from_utf8(decoded) {
Ok(s) => s,
Err(_) => return "invalid encoding",
};
let parts: Vec<String> = credentials_str.splitn(2, ':').map(String::from).collect();
if parts.len() == 2 {
let password = parts[1].clone(); // owned clone
actix_web::web::block(move || {
// Safe: `password` is owned and lives as long as the task
println!("Password: {}", password);
}).await;
}
}
"ok"
}
The key fix is cloning the password (or the entire credentials struct) so the async task owns its data. This removes any lifetime ties to the request’s temporary buffers.
For middleware that needs to inspect credentials, prefer extracting and storing owned values rather than references. If you must keep a lightweight check, ensure the middleware completes its validation within the same synchronous chain before any async point where references could dangle.
Here is an example of a guard that validates Basic Auth using owned data and integrates cleanly with Actix’s extractor chain:
use actix_web::{dev::ServiceRequest, Error};
use actix_web_httpauth::extractors::basic::BasicAuth;
use futures::future::{ok, Ready};
fn validate_credentials(auth: BasicAuth) -> Result<ServiceRequest, Error> {
let username = auth.user_id().to_string(); // owned copy
let password = auth.password().to_string(); // owned copy
// Perform validation with owned strings; no references retained
if username == "admin" && password == "secret" {
Ok(auth.into_request())
} else {
Err(actix_web::error::ErrorUnauthorized("bad credentials"))
}
}
// Usage as a guard in route configuration:
// .route("/secure", web::get().to(secure_handler).guard(actix_web::guard::FnGuard::new(validate_credentials)))
By converting to owned strings, the validation logic avoids lifetime pitfalls and eliminates the chance of Use After Free. This pattern aligns with the principle of keeping async boundaries clear of borrowed data from per-request decoders.