Double Free in Actix with Basic Auth
Double Free in Actix with Basic Auth — how this specific combination creates or exposes the vulnerability
A Double Free occurs when a program attempts to free the same dynamically allocated memory twice. In Actix-based services that use Basic Authentication, this risk can emerge when request handling logic and framework-managed objects interact incorrectly. Actix-web relies on Rust’s ownership model to manage memory safely, but unsafe patterns—especially when integrating authentication state—can bypass those guarantees.
When Basic Auth is enabled, middleware or extractor code may clone or duplicate references to credential buffers, headers, or parsed identity objects. If both the framework and custom handlers invoke deallocation routines (e.g., via drop, explicit Box::from_raw, or FFI boundaries) on the same underlying memory, a double free occurs. This typically happens when:
- Authentication state is stored in an
Arc<Mutex<...>>and cloned across tasks; one task explicitly deallocates while the framework also attempts cleanup. - Custom extractors parse the Authorization header into a raw pointer or C-compatible structure, and both the extractor and downstream code attempt to free it.
- Interoperability with C libraries (e.g., for credential validation) passes ownership incorrectly, leading to mismatched deallocation functions.
The result is memory corruption: use-after-free, heap metadata corruption, or process crashes. Because the attack surface includes unauthenticated scans, middleBrick can detect anomalies in how authentication headers are processed—such as missing validation or unusual pointer lifetimes—that may indicate exposure to double-free conditions.
To assess this, middleBrick runs security checks including Input Validation and Unsafe Consumption, testing how the service handles malformed or repeated Basic Auth headers. Findings highlight deviations from safe Rust practices, such as improper Drop implementations or raw pointer usage in authentication logic.
Basic Auth-Specific Remediation in Actix — concrete code fixes
Remediation focuses on ensuring single ownership and avoiding manual memory management when handling Basic Auth data. Use high-level Actix extractors and types that enforce Rust’s safety guarantees, and avoid converting headers into raw pointers unless strictly necessary—and if so, ensure ownership is clearly isolated.
Safe Basic Auth extractor example:
use actix_web::{web, HttpRequest, HttpResponse, Result};
use base64::Engine;
async fn auth_middleware(req: HttpRequest) -> Result<()> {
if let Some(auth_header) = req.headers().get("Authorization") {
let auth_str = auth_header.to_str().map_err(|_| actix_web::error::ErrorUnauthorized("Invalid header"))?;
if auth_str.starts_with("Basic ") {
let encoded = auth_str.trim_start_matches("Basic ");
// Decode using a safe engine; no raw pointers
let decoded = base64::engine::general_purpose::STANDARD.decode(encoded)?;
let credentials = String::from_utf8(decoded).map_err(|_| actix_web::error::ErrorUnauthorized("Invalid UTF-8"))?;
// Process credentials without transferring ownership to unsafe blocks
validate_credentials(&credentials);
}
}
Ok(())
}
fn validate_credentials(creds: &str) {
// Validate against a safe in-memory store; no manual deallocation
}
Avoiding double-free with Arc and Mutex:
When sharing authentication state across actors, wrap data in Arc<Mutex<T>> and never call Box::from_raw or drop on an Arc-managed pointer. The framework handles cleanup; custom code should only read and lock.
use std::sync::{Arc, Mutex};
use actix_web::{web, HttpResponse};
struct AuthState {
allowed_users: Vec,
}
async fn handler(data: web::Data<Arc<Mutex<AuthState>>>) -> HttpResponse {
let guard = data.lock().unwrap();
// Read-only access; no deallocation triggered here
if guard.allowed_users.contains("alice") {
HttpResponse::Ok().finish()
} else {
HttpResponse::Unauthorized().finish()
}
}
If integrating with C libraries for validation, use CString to transfer strings without taking ownership of the underlying buffer, and ensure the C side does not free memory owned by Rust.
use std::ffi::CString;
use libc;
extern "C" {
fn validate_user(username: *const libc::c_char) -> libc::c_int;
}
fn safe_validate(username: &str) -> bool {
let c_username = CString::new(username).expect("CString::new failed");
unsafe { validate_user(c_username.as_ptr()) == 1 }
}
These patterns eliminate manual free calls and keep ownership boundaries clear, reducing the risk of double free. middleBrick’s checks for Authentication, Property Authorization, and Unsafe Consumption can verify that no unsafe blocks are misused in credential handling paths.