Out Of Bounds Read in Actix
How Out Of Bounds Read Manifests in Actix
Out Of Bounds Read vulnerabilities in Actix applications typically occur when unsafe memory access patterns allow reading beyond allocated buffer boundaries. In Actix, these vulnerabilities often manifest through improper handling of HTTP request data, particularly in middleware, request parsing, and body processing.
A common Actix-specific pattern involves request body extraction where developers use Bytes::copy_from_slice without validating the actual content length. Consider this vulnerable middleware:
use actix_web::{dev::Service, web, Error, HttpResponse, HttpMessage};
pub struct BodyLoggingMiddleware;
impl Service<web::ServiceRequest, Response = web::ServiceResponse> for BodyLoggingMiddleware
where
S: Service<web::ServiceRequest, Response = web::ServiceResponse>,
{
type Error = Error;
type Future = actix_web::dev::ServiceResponseFuture;
type Config = ();
type InitError = ();
fn new_service(&self, _: ()) -> Result<Self::Config, Self::InitError> {
Ok(())
}
fn call(&mut self, mut req: web::ServiceRequest) -> Self::Future {
let body = req.extract_body().await;
if let Ok(body) = body {
let bytes = body.as_ref();
let header_value = bytes.get(0..10); // Potential OOB read if body < 10 bytes
if let Some(header) = header_value {
// Process header
}
}
req.into_future()
}
}
The vulnerability here is subtle: bytes.get(0..10) creates a slice that might request 10 bytes, but if the actual body is smaller, Rust's slice indexing would normally panic. However, when combined with unsafe code or FFI boundaries, this can lead to actual out-of-bounds reads.
Another Actix-specific vector is in custom extractors that process multipart form data. Developers might incorrectly assume multipart boundaries are always properly formatted:
use actix_multipart::Multipart;
use actix_web::{web, Error, HttpResponse};
async fn upload(mut payload: Multipart) -> Result<HttpResponse, Error> {
while let Ok(Some(mut field)) = payload.try_next().await {
let content_type = field.content_disposition().unwrap();
let filename = content_type.get_filename().unwrap();
let mut body = web::BytesMut::new();
while let Ok(Some(chunk)) = field.next().await {
body.extend_from_slice(&chunk); // No bounds checking on chunk size
}
// Vulnerable: assuming body has at least 4 bytes for header
let header = &body[0..4]; // OOB read if body < 4 bytes
process_header(header);
}
Ok(HttpResponse::Ok().finish())
}
The Actix framework's async nature can also introduce timing-related OOB reads when request processing spans multiple async boundaries without proper state validation.
Actix-Specific Detection
Detecting Out Of Bounds Read vulnerabilities in Actix applications requires both static analysis and runtime testing. The most effective approach combines automated scanning with manual code review of Actix-specific patterns.
Static analysis should focus on Actix's request handling patterns. Look for these red flags in your codebase:
// Vulnerable patterns to search for:
// 1. Direct indexing without length checks
let slice = &bytes[0..10];
// 2. unwrap() on potentially empty data
let filename = content_type.get_filename().unwrap();
// 3. Unsafe blocks with pointer arithmetic
unsafe {
let ptr = bytes.as_ptr().add(offset);
*ptr = value;
}
// 4. Direct memory access in extractors
impl<'a> FromRequest<'a> for MyType {
fn from_request(req: &'a HttpRequest, payload: &'a mut JsonPayload) -> Self::Future {
async move {
// Check for OOB patterns here
}
}
}
Runtime testing with middleBrick provides Actix-specific detection by sending boundary-case requests designed to trigger OOB reads. The scanner tests:
- Zero-byte bodies with extractors expecting fixed-size headers
- Malformed multipart boundaries
- Request bodies with unexpected EOF
- Header fields with truncated values
middleBrick's Actix-specific checks include scanning for unsafe code patterns and testing middleware that processes request bodies. The scanner identifies vulnerabilities by monitoring for memory access violations and unexpected application behavior during test execution.
For comprehensive coverage, integrate middleBrick into your CI/CD pipeline:
# GitHub Action workflow
name: API Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick Scan
run: |
npm install -g middlebrick
middlebrick scan http://localhost:8080/api/ --output json
Actix-Specific Remediation
Remediating Out Of Bounds Read vulnerabilities in Actix requires adopting safe coding patterns and leveraging Actix's built-in safety features. The key principle is always validating bounds before accessing data.
For request body processing, use Actix's safe extraction methods:
use actix_web::{web, Error, HttpResponse};
use bytes::BytesMut;
async fn safe_upload(mut payload: web::Payload) -> Result<HttpResponse, Error> {
let mut body = BytesMut::new();
while let Some(chunk) = payload.next().await {
let chunk = chunk?;
// Safe: extend only appends, never reads out of bounds
body.extend_from_slice(&chunk);
}
// Safe header extraction with bounds checking
let header = if body.len() >= 4 {
&body[0..4]
} else {
return Err(actix_web::error::ErrorBadRequest("Invalid header"));
};
process_header(header);
Ok(HttpResponse::Ok().finish())
}
Implement custom extractors with proper validation:
use actix_web::{FromRequest, HttpRequest, dev::ServiceRequest};
use bytes::Bytes;
pub struct SafeHeaderExtractor;
impl FromRequest for SafeHeaderExtractor {
type Config = ();
type Error = actix_web::Error;
type Future = futures::future::Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut actix_web::dev::Payload) -> Self::Future {
let headers = req.headers();
let header_value = headers.get("X-Special-Header");
// Safe: check existence before accessing
if let Some(value) = header_value {
if value.len() >= 4 {
return futures::future::ready(Ok(SafeHeaderExtractor));
}
}
futures::future::ready(Err(actix_web::error::ErrorBadRequest("Invalid header")))
}
}
For multipart processing, use Actix's safe APIs:
use actix_multipart::Multipart;
use actix_web::{web, Error, HttpResponse};
async fn safe_multipart(mut payload: Multipart) -> Result<HttpResponse, Error> {
while let Ok(Some(mut field)) = payload.try_next().await {
let content_type = field.content_disposition().ok_or_else(||
actix_web::error::ErrorBadRequest("Missing content disposition")
)?;
// Safe: check filename existence
let filename = content_type.get_filename().ok_or_else(||
actix_web::error::ErrorBadRequest("Missing filename")
)?;
let mut body = web::BytesMut::new();
while let Ok(Some(chunk)) = field.next().await {
body.extend_from_slice(&chunk);
}
// Safe: validate body length before processing
if body.len() < 4 {
return Err(actix_web::error::ErrorBadRequest("Body too small"));
}
process_file(filename, &body);
}
Ok(HttpResponse::Ok().finish())
}
Always validate input sizes and use Rust's slice methods that return Option rather than panicking:
// Safe pattern - returns None if out of bounds
let header = bytes.get(0..4);
match header {
Some(valid_header) => process_header(valid_header),
None => return Err(actix_web::error::ErrorBadRequest("Invalid header")),
}