flawopen.com/Command Injection/Rust

Command Injection in Rust

Critical CWE-78 Draft — pending review
ELI5

Imagine a vending machine. Safe execution is pressing button B4. Command injection is when someone glues a small explosive to the coin that activates all dispenser motors at once.

Key terms on this page
std::process::Command
Rust's standard library process builder that passes arguments directly to OS syscalls without a shell.
sh -c wrapper in Rust
Explicitly calling Command::new("sh").arg("-c") with formatted strings.

What's happening

Rust's memory safety guarantees do not prevent logic bugs. std::process::Command::new() is safe by default, but if developers explicitly call Command::new("sh").arg("-c").arg(format!("...", user_input)), command injection occurs.

Real-world impact

Command line utilities and microservices in Rust have suffered remote code execution when executing git subcommands or wrapping ffmpeg via shell invocations.

RustSec Advisory Database.

Vulnerable vs. fixed

VULNERABLE
// Spawning shell explicitly with format! string
use std::process::Command;

fn run_backup(folder: &str) {
    // Attacker input: "data; rm -rf /"
    let cmd = format!("tar -czf backup.tar.gz {}", folder);
    Command::new("sh")
        .arg("-c")
        .arg(&cmd)
        .status()
        .expect("failed");
}
FIXED
// Calling binary directly with discrete .arg() calls
use std::process::Command;

fn run_backup(folder: &str) {
    // folder is passed as a literal argument to tar, not shell
    Command::new("tar")
        .arg("-czf")
        .arg("backup.tar.gz")
        .arg(folder)
        .status()
        .expect("failed");
}

Why the fix works

Calling Command::new("tar").arg(...) uses the kernel's execvp system call. The arguments are passed as separate memory buffers without shell parsing.

Gotchas

Memory safety != Semantic safety

Rust prevents memory corruption (buffer overflows, use-after-free), but command injection is a semantic logic bug that Rust's type system cannot prevent if you explicitly invoke a shell.

Common misconceptions

"Rust's borrow checker prevents injection"

The borrow checker ensures lifetimes and memory safety; it has no concept of shell syntax or command boundaries.

How to check if you're affected

grep -rn 'Command::new("sh")' --include="*.rs" . grep -rn 'Command::new("bash")' --include="*.rs" .
Run cargo clippy and audit all process execution call sites.

Prevention checklist

FAQ

Is .arg() safe from argument injection?

It is safe from shell injection, but if the binary supports dangerous flags (like -e in curl), you must prevent user inputs from starting with a hyphen (-).

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)
Also see: SQL Injection XSS Path Traversal Insecure Deserialization