Out Of Bounds Write in Axum with Mutual Tls
Out Of Bounds Write in Axum with Mutual Tls
An out of bounds write occurs when a program writes data beyond the allocated boundaries of a buffer, corrupting adjacent memory. In Axum, this typically arises from unsafe handling of byte buffers or unchecked indexing when processing request payloads. When mutual TLS is enforced, the server performs client certificate verification before application logic runs. This handshake can introduce additional buffers to store certificate data, headers, or metadata. If these buffers are sized incorrectly or copied without length validation, the combined load of TLS processing and application logic can create an out of bounds condition.
Consider a scenario where an Axum handler reads a JSON payload into a fixed-size byte array to enforce size limits. With mutual TLS enabled, the connection state includes certificate metadata that may increase the effective size of incoming data seen by the handler. If the application uses unchecked offsets when writing into the buffer—such as iterating over bytes and writing based on parsed integers without verifying bounds—an attacker can supply crafted input that shifts the write position past the allocated memory. This can overwrite adjacent stack variables or security metadata, potentially altering control flow or bypassing checks that depend on those values.
Mutual TLS adds a layer of parsing complexity. The server must parse client certificates, verify signatures, and extract subject information. Each of these steps involves temporary buffers. If the implementation assumes certificate fields fit within expected ranges and writes extracted data into fixed structures without validation, an oversized common name or SAN list can trigger an out of bounds write. Because Axum relies on the underlying TLS library for certificate extraction, the vulnerability manifests at the intersection of application buffer management and TLS data handling. The risk is compounded when developers focus on transport security and assume the framework protects against memory safety issues, which it does not.
Real-world patterns include using copy_from_slice on slices derived from a larger buffer without checking the source length, or pushing items into a vector that is preallocated with a fixed capacity derived from TLS metadata. An attacker can manipulate the request to exploit the mismatch between expected and actual data size. This is not a theoretical concern; similar classes of vulnerabilities have been observed in systems where TLS metadata and application buffers share memory regions, leading to write primitives that bypass authentication or integrity checks.
Mutual Tls-Specific Remediation in Axum
Remediation focuses on strict boundary checks and avoiding assumptions about data sizes introduced by mutual TLS. Always validate lengths before copying data into fixed-size buffers. Use Rust's safe abstractions such as slices with explicit length checks or collections that grow as needed. When processing certificate metadata, treat extracted fields as untrusted input and enforce maximum lengths consistent with realistic certificates.
Below are concrete Axum examples with mutual TLS configured using rustls. The first example demonstrates safe handling of request bodies without relying on fixed buffers.
use axum::{routing::post, Router};
use std::net::SocketAddr;
use tls_rustls::RustlsConfig;
async fn safe_handler(
// Use a Vec or String for unbounded body content
body: String,
) -> String {
// Process body with explicit length checks
if body.len() > 1024 {
return String::from("payload too large");
}
// Safe: bounded iteration or slice operations with explicit checks
let trimmed = body.trim();
trimmed.to_string()
}
#[tokio::main]
async fn main() {
let config = RustlsConfig::from_pem_file(
"ca.crt",
"server.crt",
"server.key",
).expect("failed to load TLS config");
let app = Router::new()
.route("/submit", post(safe_handler))
.with_state(config);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
The second example shows how to validate certificate metadata safely. Do not assume the common name length; use iterators and conditionals to enforce limits.
use axum::extract::State;
use rustls::server::ClientCertVerified;
use std::sync::Arc;
struct ServerState {
max_common_name_len: usize,
}
async fn verify_client_cert(
State(state): State<Arc<ServerState>>
) -> ClientCertVerified {
// In a real handler, the certificate would be extracted from the request extensions
// This is a simplified illustration
let dummy_cn = "example.com";
if dummy_cn.len() > state.max_common_name_len {
// Reject or handle oversized fields
ClientCertVerified::assertion()
} else {
ClientCertVerified::assertion()
}
}
Additional practices include using tools like Miri to detect out of bounds accesses during testing, and avoiding get_unchecked unless absolutely necessary and guarded by rigorous validation. Regular dependency updates ensure TLS library vulnerabilities are addressed promptly.