Counterexamples in Safe Rust
Attack patterns that break memory safety in Safe Rust.
The Rust programming language is a prominent candidate for replacing C and C++ in the pursuit of memory safety. However, Rust’s safety guarantees do not universally extend to all third-party code. This project illustrates that such guarantees can be compromised entirely within safe Rust through a series of counterexamples.
Our research demonstrates that Safe Rust is not immune to attack patterns that break memory safety. Below is an example from our study:
use std::fs::OpenOptions;
use std::io::{Seek, SeekFrom, Write};
fn write_oob(vector: &Vec<i32>, index: usize, element: i32) {
// Get the pointer to the index location
let buffer_ptr = vector.as_ptr();
let ind = buffer_ptr.wrapping_add(index);
// Open the /proc/self/mem file for writing
let mut file = OpenOptions::new()
.write(true)
.open("/proc/self/mem")
.unwrap();
// Seek to the memory address of index
file.seek(SeekFrom::Start(ind as u64)).unwrap();
// Write the provided element into the calculated index position
file.write_all(&element.to_ne_bytes()).unwrap();
// Resize the vector to read the updated memory
let vec_ptr: *const usize = vector as *const Vec<i32> as *const usize;
let capacity_ptr: *const usize = vec_ptr.wrapping_add(2);
file.seek(SeekFrom::Start(capacity_ptr as u64)).unwrap();
let num = index + 1;
file.write_all(&num.to_ne_bytes()).unwrap();
// Print the updated vector element
println!("I have {:?}", vector[index]);
}
The above example uses proc/self/mem
to read and write out of bounds in safe Rust. We present five such attack patterns in the paper, the summary of the results can be seen in the table below:
Code Example | CWE | Filesystem Access | Command Execution | Compiler Unsoundness | Build-time Effects | Environment Variables | Miri | Verus | Prusti-Dev | Flux | Rudra |
---|---|---|---|---|---|---|---|---|---|---|---|
/proc/self/mem-1 | CWE-123 | ✓ | ⚠️ | ⚠️ | ⚠️ | ✗ | ✗ | ||||
/proc/self/mem-2 | CWE-125,787,119,124 | ✓ | ⚠️ | ✗ | ⚠️ | ⚠️ | ✗ | ||||
GDB sudo | CWE-123 | ✓ | ✗ | — | ⚠️ | — | ✗ | ||||
Static Lifetime | CWE-416,825 | ✓ | ✓ | ⚠️ | ⚠️ | ✓ | ✓ | ||||
Cargo Wrapper | CWE-426 | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ||
Path ls | CWE-426 | ✓ | ✓ | ⚠️ | ✗ | ✗ | ✗ | ✗ | |||
Dangling Nightly Lifetime | CWE-416,825 | ✓ | ✓ | ⚠️ | ✓ | ⚠️ | ✓ | ||||
Trait Upcasting | CWE-704,476,843 | ✓ | ✓ | ⚠️ | ⚠️ | ⚠️ | ✗ | ||||
Large Array Initialization | CWE-665 | ✓ | ✗ | ✗ | ⚠️ | ⚠️ | ✗ |
This table provides a comprehensive evaluation of tools on different attack patterns described in the paper and two additional examples. It includes the corresponding CWEs, related attack patterns, and the output from existing verification and analysis tools.
We also evaluate real world Rust code for these attack patterns and our findings for that are presented in the table below:
Attack Pattern | Top 500 Crates | Random 500 Crates | RustSec | rustdecimal |
---|---|---|---|---|
Filesystem Access | 3 | 4 | 1 | ✓ |
Command Execution | 16 | 13 | 2 | ✓ |
Compiler Unsoundness | 0 | 0 | 1* | ✗ |
Build-time Effects | 0 | 0 | 1 | ✗ |
Environment Variables | 8 | 3 | 0 | ✓ |
This table shows the frequency of the attack patterns considered in examples identified within the top 500 crates, a random set of 500 crates, vulnerabilities listed in RustSec, and the rustdecimal
supply-chain attack.*
Below is one example from a real-world rust crate. bvm-0.0.20
allows execution of arbitrary commands through the generated execuatable.
pub fn create_shim(
environment: &impl Environment,
binaries_cache_dir: &Path,
command_name: &CommandName,
) -> Result<(), ErrBox> {
let file_path = get_shim_path(binaries_cache_dir, command_name);
environment.write_file_text(
&file_path,
&format!(
r#"#!/bin/sh
exe_path=$(bvm resolve {})
"$exe_path" "$@""#,
command_name.as_str()
),
)?;
Command::new("chmod")
.args(&["+x", file_path.to_string_lossy().to_string()])
.output()?;
Ok(())
}