Integer Overflow in Axum
How Integer Overflow Manifests in Axum
Integer overflow in Axum applications typically occurs when handling user-controlled numeric inputs that are used in arithmetic operations without proper validation. Since Axum is built on top of Hyper and Tokio, it inherits Rust's default integer behavior where overflows in debug builds panic, but in release builds they wrap silently—creating a dangerous inconsistency.
The most common attack vector involves API endpoints that accept numeric parameters for pagination, quantity calculations, or size limits. Consider an e-commerce endpoint that calculates total price:
use axum::{routing::get, Router};
async fn calculate_total(
Query(params): Query<HashMap<String, String>>,
) -> String {
let price: i32 = params.get("price").unwrap_or("0").parse().unwrap();
let quantity: i32 = params.get("quantity").unwrap_or("1").parse().unwrap();
let total = price * quantity; // Overflow risk!
format!("Total: ${}", total)
}
let app = Router::new().route("/calculate", get(calculate_total));If an attacker supplies price=2147483647 (max i32) and quantity=2, the multiplication overflows to -2 in release builds, potentially causing incorrect billing logic or downstream calculation errors.
Another Axum-specific scenario involves stream processing limits. When handling file uploads or chunked responses:
use axum::{extract::Multipart, http::StatusCode};
async fn upload_file(mut multipart: Multipart) -> Result<String, StatusCode> {
while let Some(field) = multipart.next_field().await? {
let data = field.bytes().await?;
let chunk_size = data.len() as u32; // Overflow if file > 4GB
// Process chunk
}
Ok("Upload complete".to_string())
}
let app = Router::new().route("/upload", post(upload_file));Here, casting data.len() to u32 can overflow for files larger than 4GB, causing incorrect chunk size calculations that might lead to buffer overflows in downstream processing or denial of service.
Rate limiting implementations in Axum also commonly suffer from integer overflow when calculating token buckets or sliding windows:
use std::time::Duration;
async fn rate_limited(
State(rate_limiter): State<RateLimiter>,
Path(user_id): Path<String>,
) -> String {
let requests = rate_limiter.get_requests(user_id).await;
let window = Duration::from_secs(3600);
let remaining = 1000 - requests; // Could underflow if requests > 1000
if remaining < 0 { // This check fails due to unsigned underflow
return "Rate limit exceeded".to_string();
}
rate_limiter.record_request(user_id).await;
"Request processed".to_string()
}The subtraction 1000 - requests can underflow if requests exceeds 1000, resulting in a very large positive number that bypasses the rate limit check entirely.
Axum-Specific Detection
Detecting integer overflow in Axum applications requires both static analysis and runtime monitoring. Static analysis tools like cargo-audit can catch some overflow vulnerabilities, but they miss context-specific issues in request handling logic.
middleBrick's API security scanner specifically tests for integer overflow vulnerabilities in Axum endpoints by sending boundary-value inputs and monitoring for abnormal behavior. The scanner tests parameters at critical boundaries:
POST /calculate HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
price=2147483647&quantity=2
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
----WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="large.bin"
Content-Type: application/octet-stream
[4GB+ of data]
POST /rate_limit HTTP/1.1
Host: example.com
Content-Type: application/json
{"user_id": "attacker", "requests": "2147483648"}
The scanner monitors for several indicators of integer overflow: unexpected negative values in calculations that should always be positive, wrapping behavior in counters, and incorrect type casting that leads to data truncation.
For Axum applications using Tokio's async primitives, middleBrick also tests for overflow in concurrent request counters and connection pooling limits:
use tokio::sync::mpsc;
async fn concurrent_test(
Data<Sender<i32>>: Data<Sender<i32>>,
) -> String {
let mut counter: i32 = 0;
for _ in 0..1000 {
tokio::spawn(async move {
counter += 1; // Race condition + potential overflow
sender.send(counter).await.unwrap();
});
}
"Testing complete".to_string()
}
let (sender, _receiver) = mpsc::channel(100);
let app = Router::new().route("/concurrent", post(concurrent_test.with_state(sender)));middleBrick's continuous monitoring in the Pro tier can automatically detect when integer overflow vulnerabilities are introduced through code changes, comparing security scores across deployments and alerting when critical vulnerabilities appear.
Axum-Specific Remediation
Remediating integer overflow in Axum applications requires a defense-in-depth approach using Rust's type system and validation patterns. The first line of defense is input validation using Axum's extractors with custom validation:
use axum::{extract::Query, http::StatusCode, response::IntoResponse};
use serde::Deserialize;
#[derive(Deserialize)]
struct CalculateParams {
price: i64,
quantity: u32,
}
impl std::str::FromStr for CalculateParams {
type Err = StatusCode;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let params: HashMap<String, String> = serde_urlencoded::from_str(s).unwrap();
let price = params.get("price")
.ok_or(StatusCode::BAD_REQUEST)?
.parse::<i64>()
.map_err(|_| StatusCode::BAD_REQUEST)?;
let quantity = params.get("quantity")
.ok_or(StatusCode::BAD_REQUEST)?
.parse::<u32>()
.map_err(|_| StatusCode::BAD_REQUEST)?;
// Check for potential overflow before multiplication
if price != 0 && quantity != 0 &&
price.abs() > i64::MAX / quantity as i64 {
return Err(StatusCode::PAYLOAD_TOO_LARGE);
}
Ok(CalculateParams { price, quantity })
}
}
async fn calculate_total(Query(params): Query<CalculateParams>)
-> Result<String, StatusCode> {
let total = params.price * params.quantity as i64;
Ok(format!("Total: ${}", total))
}
let app = Router::new().route("/calculate", get(calculate_total));This approach validates inputs before they reach business logic, preventing overflow at the boundary. For calculations that might exceed i64 range, use Rust's built-in checked arithmetic:
async fn safe_calculation(
Query(params): Query<HashMap<String, String>>,
) -> String {
let price: i128 = params.get("price")
.unwrap_or("0")
.parse()
.unwrap_or(0);
let quantity: i128 = params.get("quantity")
.unwrap_or("1")
.parse()
.unwrap_or(1);
// Use checked multiplication to prevent overflow
match price.checked_mul(quantity) {
Some(total) => format!("Total: ${}", total),
None => "Calculation overflow - input values too large".to_string(),
}
}
let app = Router::new().route("/safe", get(safe_calculation));For stream processing and file uploads, use Rust's saturating arithmetic and explicit bounds checking:
use axum::{extract::Multipart, http::StatusCode};
use tokio::io::{AsyncReadExt, Take};
async fn safe_upload(mut multipart: Multipart)
-> Result<String, StatusCode> {
while let Some(field) = multipart.next_field().await? {
let content_length = field.content_length().unwrap_or(0);
// Enforce maximum file size using Tokio's Take reader
if content_length > 1_073_741_824 { // 1GB limit
return Err(StatusCode::PAYLOAD_TOO_LARGE);
}
let mut limited_reader = field.take(1_073_741_824);
let mut buffer = Vec::new();
limited_reader.read_to_end(&mut buffer).await?;
// Process buffer safely
}
Ok("Upload complete".to_string())
}
let app = Router::new().route("/safe_upload", post(safe_upload));For rate limiting with Axum, use atomic operations with proper overflow handling:
use std::sync::atomic::{AtomicI32, Ordering};
struct RateLimiter {
counters: Mutex<HashMap<String, AtomicI32>>,
limit: i32,
}
impl RateLimiter {
async fn record_request(&self, user_id: &str) -> bool {
let mut counters = self.counters.lock().await;
let counter = counters
.entry(user_id.to_string())
.or_insert(AtomicI32::new(0));
// Use fetch_add with overflow check
let current = counter.load(Ordering::Relaxed);
if current >= self.limit {
return false;
}
counter.fetch_add(1, Ordering::Relaxed);
true
}
}
async fn rate_limited_endpoint(
State(rate_limiter): State<RateLimiter>,
Path(user_id): Path<String>,
) -> String {
if rate_limiter.record_request(&user_id).await {
"Request processed".to_string()
} else {
"Rate limit exceeded".to_string()
}
}
let rate_limiter = RateLimiter {
counters: Mutex::new(HashMap::new()),
limit: 1000,
};
let app = Router::new()
.route("/rate_limit/:user_id", get(rate_limited_endpoint).with_state(rate_limiter));These Axum-specific remediation patterns ensure that integer operations are safe, validated, and resistant to overflow attacks while maintaining the performance characteristics expected from Rust web applications.