# Rust Security Audit

> Rust code security audit module
> Applicable to: Actix-web, Axum, Rocket, Tonic (gRPC), Tauri, systems programming

## Identification Features

```rust
// Rust project identification
Cargo.toml, Cargo.lock
*.rs

// Web project structure
├── src/
│   ├── main.rs
│   ├── routes/
│   ├── handlers/
│   ├── models/
│   └── middleware/
├── Cargo.toml
└── .env
```

---

## One-Click Detection Commands

### unsafe Blocks

```bash
# unsafe keyword
grep -rn "unsafe\s*{" --include="*.rs"
grep -rn "unsafe\s*fn" --include="*.rs"
grep -rn "unsafe\s*impl" --include="*.rs"

# Raw pointer operations
grep -rn "\*const\s\|\*mut\s" --include="*.rs"
grep -rn "\.as_ptr\|\.as_mut_ptr" --include="*.rs"
```

### FFI Boundaries

```bash
# extern blocks
grep -rn "extern\s*\"C\"" --include="*.rs"
grep -rn "#\[no_mangle\]" --include="*.rs"

# libc calls
grep -rn "libc::" --include="*.rs"
```

### Deserialization

```bash
# Dangerous serde usage
grep -rn "deserialize_any\|#\[serde(tag\|typetag" --include="*.rs"
grep -rn "serde_json::from_\|bincode::deserialize\|rmp_serde" --include="*.rs"
```

### Command Execution

```bash
grep -rn "Command::new\|process::Command\|std::process" --include="*.rs"
grep -rn "\.arg\s*(\|\.args\s*(" --include="*.rs"
```

### SQL/Database

```bash
grep -rn "query!\|sqlx::query\|diesel::" --include="*.rs"
grep -rn "raw_sql\|execute\s*(" --include="*.rs"
grep -rn "format!.*SELECT\|format!.*INSERT\|format!.*UPDATE" --include="*.rs"
```

### Path Operations

```bash
grep -rn "PathBuf::from\|Path::new\|\.join\s*(" --include="*.rs"
grep -rn "std::fs::\|tokio::fs::" --include="*.rs"
```

---

## Rust-Specific Vulnerabilities

### 1. unsafe Block Vulnerabilities

```rust
// Unverified raw pointer dereference
unsafe {
    let ptr = user_input as *const u8;
    let value = *ptr;  // Pointer validity not verified!
}

// Buffer overflow
unsafe {
    let slice = std::slice::from_raw_parts(ptr, len);  // len may be out of bounds
}

// Type punning
unsafe {
    let data: u64 = std::mem::transmute(user_bytes);  // Alignment and size not verified
}

// use-after-free
unsafe {
    let ptr = Box::into_raw(boxed);
    drop(Box::from_raw(ptr));
    *ptr = 42;  // Use-after-free!
}

// Safe: validate and encapsulate
pub fn safe_slice<T>(ptr: *const T, len: usize) -> Option<&[T]> {
    if ptr.is_null() || len == 0 {
        return None;
    }
    // Verify alignment
    if (ptr as usize) % std::mem::align_of::<T>() != 0 {
        return None;
    }
    Some(unsafe { std::slice::from_raw_parts(ptr, len) })
}

// Search patterns
unsafe\s*\{|unsafe\s+fn|std::mem::transmute|\*ptr|from_raw_parts
```

### 2. Send/Sync Trait Forgery

```rust
// Dangerous: incorrect Send/Sync implementation causes data races
struct UnsafeCell<T>(*mut T);

unsafe impl<T> Send for UnsafeCell<T> {}  // Dangerous!
unsafe impl<T> Sync for UnsafeCell<T> {}  // Dangerous!

// Interior mutability misuse
use std::cell::RefCell;
// RefCell is not Sync, using it in multi-threaded context will panic or worse

// Safe: use correct synchronization primitives
use std::sync::{Arc, Mutex, RwLock};
use parking_lot::Mutex;

// Search patterns
unsafe\s+impl.*Send|unsafe\s+impl.*Sync
```

### 3. FFI Boundary Safety

```rust
// Unverified C string
extern "C" {
    fn c_function(s: *const c_char);
}

unsafe {
    let ptr = user_input.as_ptr() as *const c_char;
    c_function(ptr);  // May not be null-terminated!
}

// Panic in callback function
#[no_mangle]
pub extern "C" fn callback() {
    panic!("oops");  // Panic across FFI boundary is UB!
}

// Safe: use CString and handle panics
use std::ffi::CString;
use std::panic::catch_unwind;

let c_string = CString::new(user_input)?;
unsafe { c_function(c_string.as_ptr()) };

#[no_mangle]
pub extern "C" fn safe_callback() -> i32 {
    match catch_unwind(|| {
        // Actual logic
        0
    }) {
        Ok(result) => result,
        Err(_) => -1,  // Error code
    }
}

// Search patterns
extern\s*"C"|#\[no_mangle\]|as\s*\*const\s*c_char
```

### 4. Integer Overflow

```rust
// Integer overflow does not panic in release mode (wraparound)
let user_size: u32 = get_user_input();
let total = user_size * 4;  // May overflow!
let buf = vec![0u8; total as usize];

// Type cast truncation
let big: u64 = get_big_number();
let small: u32 = big as u32;  // Truncation!

// Safe: use checked/saturating operations
let total = user_size.checked_mul(4).ok_or("overflow")?;
let small: u32 = big.try_into().map_err(|_| "overflow")?;

// Search patterns
as\s+u(8|16|32|64|size)|as\s+i(8|16|32|64|size)
```

### 5. Command Execution

```rust
// Dangerous: user input directly as command
use std::process::Command;

let output = Command::new(user_program)
    .arg(user_arg)
    .output()?;

// Shell execution
Command::new("sh")
    .arg("-c")
    .arg(format!("ls {}", user_path))  // Command injection!
    .output()?;

// Safe: whitelist + argument separation
let allowed = ["ls", "cat", "grep"];
if !allowed.contains(&user_program.as_str()) {
    return Err("Command not allowed");
}

Command::new("ls")
    .arg("-la")
    .arg(&user_path)  // Passed as separate argument, not shell-interpreted
    .output()?;

// Search patterns
Command::new\s*\(.*user input|\.arg\s*\(.*format!
```

### 6. SQL Injection

```rust
// SQLx string formatting
let query = format!("SELECT * FROM users WHERE name = '{}'", user_name);
sqlx::query(&query).fetch_all(&pool).await?;

// Diesel raw SQL
diesel::sql_query(format!("SELECT * FROM users WHERE id = {}", user_id))
    .load::<User>(&conn)?;

// Safe: use parameterized queries
// SQLx
sqlx::query("SELECT * FROM users WHERE name = $1")
    .bind(&user_name)
    .fetch_all(&pool)
    .await?;

// SQLx macro (compile-time checking)
sqlx::query!("SELECT * FROM users WHERE name = $1", user_name)
    .fetch_all(&pool)
    .await?;

// Diesel
users.filter(name.eq(&user_name)).load::<User>(&conn)?;

// Search patterns
format!.*SELECT|format!.*INSERT|format!.*UPDATE|format!.*DELETE
sql_query\s*\(.*format!
```

### 7. Path Traversal

```rust
// Dangerous: path concatenation
use std::path::PathBuf;

let mut path = PathBuf::from("/data/uploads");
path.push(user_filename);  // "../../../etc/passwd" !
let content = std::fs::read(&path)?;

// join is equally dangerous
let path = base_dir.join(user_filename);

// Safe: canonicalize and validate
let full_path = base_dir.join(&user_filename).canonicalize()?;
if !full_path.starts_with(&base_dir) {
    return Err("Path traversal detected");
}

// Or extract only the filename
let safe_name = Path::new(&user_filename)
    .file_name()
    .ok_or("Invalid filename")?;
let path = base_dir.join(safe_name);

// Search patterns
PathBuf::from.*user input|\.push\s*\(.*user input|\.join\s*\(.*user input
```

### 8. Deserialization

```rust
// serde deserialize_any (type confusion)
#[derive(Deserialize)]
#[serde(tag = "type")]
enum Message {
    Admin(AdminCommand),  // Attacker can specify type = "Admin"
    User(UserCommand),
}

// typetag (polymorphic deserialization)
#[typetag::serde]
trait Command: Send + Sync {
    fn execute(&self);
}
// Attacker can deserialize any type that implements Command

// bincode from untrusted source
let data: UntrustedData = bincode::deserialize(&user_bytes)?;

// Safe: restrict types
#[derive(Deserialize)]
struct SafeMessage {
    content: String,
    // Only simple types, no enum/trait objects
}

// Safe: validate deserialized data
let data: UserInput = serde_json::from_str(&input)?;
validate_user_input(&data)?;

// Search patterns
deserialize_any|#\[serde\(tag|typetag::serde|bincode::deserialize
```

### 9. Regular Expression DoS (ReDoS)

```rust
// Dangerous: catastrophic backtracking
use regex::Regex;

let re = Regex::new(&user_pattern)?;  // User can craft malicious regex

// Default regex crate has protections, but they can be disabled
let re = RegexBuilder::new(pattern)
    .size_limit(0)  // Disables limit!
    .build()?;

// Safe: use default limits + timeout
let re = Regex::new(pattern)?;  // Has default size limit

// Or use fancy-regex's Regex::set_size_limit

// Search patterns
Regex::new.*user input|RegexBuilder.*size_limit\s*\(\s*0
```

---

## Web Framework-Specific Vulnerabilities

### Actix-web

```rust
// Overly permissive CORS
use actix_cors::Cors;

let cors = Cors::permissive();  // Allows all!

// CSRF protection not enabled
// Actix does not enable CSRF by default

// Safe: restrict CORS
let cors = Cors::default()
    .allowed_origin("https://myapp.com")
    .allowed_methods(vec!["GET", "POST"])
    .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
    .max_age(3600);

// Search patterns
Cors::permissive|allow_any_origin
```

### Axum

```rust
// Unprotected routes
Router::new()
    .route("/admin", get(admin_handler))  // No middleware!

// Unvalidated extractors
async fn handler(Path(id): Path<String>) -> impl IntoResponse {
    // id is not validated
}

// Safe: use middleware and validation
use axum::middleware;
use validator::Validate;

#[derive(Deserialize, Validate)]
struct Input {
    #[validate(length(min = 1, max = 100))]
    name: String,
}

Router::new()
    .route("/admin", get(admin_handler))
    .layer(middleware::from_fn(auth_middleware))

// Search patterns
Router::new\(\)(?!.*layer)|Path\(.*\):\s*Path<String>
```

### Rocket

```rust
// Data protection not enabled
#[get("/secret")]
fn secret() -> &'static str { "secret" }  // No authentication!

// Safe: use request guards
#[get("/secret")]
fn secret(user: AuthenticatedUser) -> &'static str { "secret" }

// Search patterns
#\[get\(|#\[post\(|#\[put\(|#\[delete\((?!.*Guard|.*Request)
```

### Tonic (gRPC)

```rust
// TLS not enabled
Server::builder()
    .add_service(my_service)
    .serve("[::]:50051".parse()?)?;  // Plaintext!

// Reflection service exposed
tonic_reflection::server::Builder::configure()
    .register_encoded_file_descriptor_set(DESCRIPTOR)
    .build()?;

// No message size limit
// Default is 4MB, but may not be secure enough

// Safe: enable TLS + limit message size
Server::builder()
    .tls_config(tls_config)?
    .max_frame_size(1024 * 1024)  // 1MB
    .add_service(my_service)
    .serve_with_shutdown(addr, signal);

// Search patterns
tonic.*Server::builder(?!.*tls_config)|tonic_reflection
```

---

## Tauri (Desktop Application) Security

```rust
// Commands expose too much
#[tauri::command]
fn read_file(path: String) -> String {
    std::fs::read_to_string(path).unwrap()  // Arbitrary file read!
}

// IPC not validated
#[tauri::command]
fn execute(cmd: String) -> String {
    // ...
}

// Safe: least privilege + whitelist
#[tauri::command]
fn read_allowed_file(name: String) -> Result<String, String> {
    let allowed = ["config.json", "data.txt"];
    if !allowed.contains(&name.as_str()) {
        return Err("Not allowed".into());
    }
    let path = app_dir.join(name);
    std::fs::read_to_string(path).map_err(|e| e.to_string())
}

// tauri.conf.json permission restrictions
{
  "tauri": {
    "allowlist": {
      "fs": {
        "all": false,
        "readFile": true,
        "scope": ["$APP/*"]
      }
    }
  }
}

// Search patterns
#\[tauri::command\]|allowlist.*all.*true
```

---

## Common Crate Security Issues

### Unsafe Crates

| Crate | Issue | Alternative |
|-------|-------|-------------|
| `chrono` | Historical UB issues | `time` |
| `yaml-rust` | Unmaintained | `serde_yaml` |
| `openssl` | Complexity | `rustls` |
| `ring` (old) | Needs update | Latest `ring` |

### Dependency Audit

```bash
# cargo-audit
cargo install cargo-audit
cargo audit

# cargo-deny
cargo install cargo-deny
cargo deny check

# cargo-geiger (unsafe statistics)
cargo install cargo-geiger
cargo geiger
```

---

## Audit Checklist

```
unsafe code:
- [ ] Search all unsafe blocks
- [ ] Verify raw pointer usage
- [ ] Check transmute
- [ ] Verify Send/Sync implementations

FFI:
- [ ] Check extern "C" boundaries
- [ ] Verify C string handling
- [ ] Check panic across FFI

Web Frameworks:
- [ ] Check CORS configuration
- [ ] Verify authentication middleware
- [ ] Check path handling
- [ ] Verify input validation

Data Handling:
- [ ] Check SQL query construction
- [ ] Verify deserialization usage
- [ ] Check integer conversions
- [ ] Verify regular expressions
```

---

## Audit Regex

```regex
# unsafe
unsafe\s*\{|unsafe\s+fn|unsafe\s+impl

# FFI
extern\s*"C"|#\[no_mangle\]|as\s*\*const\s*c_char

# Memory operations
std::mem::transmute|from_raw_parts|Box::from_raw

# SQL Injection
format!.*(SELECT|INSERT|UPDATE|DELETE)

# Command Execution
Command::new.*variable|\.arg.*format!

# Path Traversal
PathBuf::from.*variable|\.join\s*\(.*variable

# Deserialization
deserialize_any|typetag|bincode::deserialize

# Web Security
Cors::permissive|allow_any_origin
```

---

## Tool Recommendations

```bash
# cargo-audit (CVE check)
cargo audit

# cargo-deny (dependency policy)
cargo deny check

# cargo-geiger (unsafe statistics)
cargo geiger

# Clippy (lint)
cargo clippy -- -W clippy::all -W clippy::pedantic

# Miri (UB detection, tests only)
cargo +nightly miri test

# rust-analyzer (IDE integration)
```

---

## CSRF Security (CWE-352)

### Dangerous Patterns

```rust
// Actix-web - No CSRF protection
#[post("/api/transfer")]
async fn transfer(req: web::Json<TransferRequest>) -> impl Responder {
    // State-changing operation without CSRF protection
    do_transfer(&req.to, req.amount).await
}
```

### Safe Configuration

```rust
// Actix-web + actix-csrf
use actix_csrf::CsrfMiddleware;
use actix_web::{web, App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(CsrfMiddleware::new(
                b"32-byte-long-secret-key-here!!!!"
            ))
            .route("/api/transfer", web::post().to(transfer))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

// Axum - using tower middleware
use axum_csrf::{CsrfConfig, CsrfLayer, CsrfToken};

let config = CsrfConfig::default();
let app = Router::new()
    .route("/api/transfer", post(transfer))
    .layer(CsrfLayer::new(config));

async fn transfer(token: CsrfToken, Json(req): Json<TransferRequest>) -> impl IntoResponse {
    // Token is automatically validated
    do_transfer(&req.to, req.amount).await
}
```

### Detection Commands

```bash
# Find POST/PUT/DELETE routes
rg -n "#\[post\]|#\[put\]|#\[delete\]|\.post\(|\.put\(|\.delete\(" --glob "*.rs"

# Find CSRF related
rg -n "csrf|CsrfMiddleware|CsrfLayer" --glob "*.rs" --glob "Cargo.toml"
```

---

## File Upload Security (CWE-434)

### Dangerous Patterns

```rust
// Unvalidated file upload
#[post("/upload")]
async fn upload(mut payload: Multipart) -> impl Responder {
    while let Some(field) = payload.try_next().await.unwrap() {
        let filename = field.content_disposition().get_filename().unwrap();
        let filepath = format!("./uploads/{}", filename);  // Path traversal
        let mut f = File::create(filepath).await.unwrap();
        while let Some(chunk) = field.try_next().await.unwrap() {
            f.write_all(&chunk).await.unwrap();
        }
    }
    HttpResponse::Ok()
}
```

### Safe Configuration

```rust
use actix_multipart::Multipart;
use sanitize_filename::sanitize;
use infer;

const ALLOWED_TYPES: &[&str] = &["image/jpeg", "image/png", "image/gif"];
const MAX_SIZE: usize = 5 * 1024 * 1024;  // 5MB

#[post("/upload")]
async fn upload(mut payload: Multipart) -> Result<HttpResponse, Error> {
    while let Some(field) = payload.try_next().await? {
        // 1. Get safe filename
        let original_name = field.content_disposition()
            .get_filename()
            .unwrap_or("unknown");
        let safe_name = sanitize(original_name);

        // 2. Validate extension
        let ext = std::path::Path::new(&safe_name)
            .extension()
            .and_then(|e| e.to_str())
            .unwrap_or("");
        if !["jpg", "jpeg", "png", "gif"].contains(&ext.to_lowercase().as_str()) {
            return Ok(HttpResponse::BadRequest().body("Invalid extension"));
        }

        // 3. Read content and validate size
        let mut data = Vec::new();
        while let Some(chunk) = field.try_next().await? {
            if data.len() + chunk.len() > MAX_SIZE {
                return Ok(HttpResponse::BadRequest().body("File too large"));
            }
            data.extend_from_slice(&chunk);
        }

        // 4. Validate actual file type
        let kind = infer::get(&data);
        match kind {
            Some(t) if ALLOWED_TYPES.contains(&t.mime_type()) => {},
            _ => return Ok(HttpResponse::BadRequest().body("Invalid file type")),
        }

        // 5. Generate safe path
        let unique_name = format!("{}_{}", Uuid::new_v4(), safe_name);
        let filepath = std::path::Path::new("./uploads").join(&unique_name);

        // 6. Save file
        let mut f = File::create(filepath).await?;
        f.write_all(&data).await?;
    }

    Ok(HttpResponse::Ok().body("Uploaded"))
}
```

---

## Hardcoded Credentials (CWE-798)

### Dangerous Patterns

```rust
// Hardcoded secret key
const API_KEY: &str = "sk-1234567890abcdef";
const DB_PASSWORD: &str = "admin123";

fn connect_db() -> Connection {
    let url = format!("postgres://user:{}@localhost/db", DB_PASSWORD);  // Dangerous
    Connection::connect(&url).unwrap()
}

// JWT secret hardcoded
let encoding_key = EncodingKey::from_secret(b"my-secret-key");  // Dangerous
```

### Safe Configuration

```rust
use std::env;
use secrecy::{Secret, ExposeSecret};

struct Config {
    api_key: Secret<String>,
    db_password: Secret<String>,
}

impl Config {
    fn from_env() -> Result<Self, env::VarError> {
        Ok(Self {
            api_key: Secret::new(env::var("API_KEY")?),
            db_password: Secret::new(env::var("DB_PASSWORD")?),
        })
    }
}

fn connect_db(config: &Config) -> Connection {
    let url = format!(
        "postgres://user:{}@localhost/db",
        config.db_password.expose_secret()
    );
    Connection::connect(&url).unwrap()
}
```

### Detection Commands

```bash
# Find hardcoded secrets
rg -n "password\s*[:=]|secret\s*[:=]|api_key\s*[:=]|token\s*[:=]" --glob "*.rs" | grep -v "env::\|std::env"

# Find hardcoded strings
rg -n 'const.*:.*&str.*=.*"[^"]{8,}"' --glob "*.rs"

# Find insecure JWT
rg -n "from_secret\(b\"" --glob "*.rs"
```

---

**Version**: 1.1
**Updated**: 2026-02-04
**Vulnerability Types Covered**: 17+
