priority: critical
Security & Vulnerability Management
CVE disclosure · cargo-audit · cargo-deny · Fuzzing · Unsafe code review · Security testing
Vulnerability Management
- •
cargo-audit: Run on every commit and in CI; fail build on known vulnerabilities
bashcargo audit cargo audit --deny warnings # Fail on advisories
- •
cargo-deny: Detect vulnerable, unmaintained, and unlicensed dependencies
bashcargo deny check advisories cargo deny check bans cargo deny check sources
- •
CVE Disclosure Policy:
- •Report to maintainers privately before public disclosure
- •Allow 90 days for patch before public CVE announcement
- •Document CVE in
SECURITY.mdwith patch version and workarounds - •Never commit exploits; reference by CVE ID and mitigation
- •Include timeline: discovery → disclosure → patch release
Cargo.lock Management
toml
# Cargo.toml [dependencies] serde = "=1.0.150" # Pin critical deps to known safe versions serde_json = "=1.0.89" # Run lock file checks in CI # .github/workflows/security.yml - name: Check lock file run: cargo tree --locked # Ensure lock matches Cargo.toml
Fuzzing with cargo-fuzz
- •Setup:
cargo install cargo-fuzz && cargo fuzz init - •Fuzz targets:
fuzz/fuzz_targets/directory; one target per public API surface - •Continuous fuzzing: Run in CI with timeout limits (e.g., 60 seconds per commit)
- •Crash reproduction: Save failing inputs; add regression tests
Fuzzing Example
rust
// fuzz/fuzz_targets/parse_fuzzer.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
use my_crate::parse;
fuzz_target!(|data: &[u8]| {
// Parse should never crash or panic on arbitrary input
let _ = parse(data);
});
bash
# Run fuzzing cargo +nightly fuzz run parse_fuzzer -- -max_len=1024 -timeout=5 # Minimize failing input cargo +nightly fuzz cmin parse_fuzzer
Unsafe Code Review
- •
SAFETY comments: EVERY
unsafeblock MUST have a// SAFETY:comment explaining:- •What invariant is being relied upon
- •Why calling code maintains that invariant
- •What could go wrong if invariant is violated
- •
Isolation: Unsafe code in dedicated modules; public API must be safe
- •
Review checklist:
- •All pointers are valid (not null, not dangling)
- •Pointer dereferencing is properly aligned
- •No use-after-free (object still alive)
- •No double-free (only freed once)
- •No data races (proper synchronization)
- •Type safety maintained across FFI boundaries
Unsafe Code Example
rust
/// SAFETY: This function assumes the pointer points to valid, initialized memory
/// for at least `len` bytes. Caller MUST ensure pointer is:
/// - Non-null and properly aligned
/// - Pointing to valid, initialized T values
/// - Valid for reads of at least `len * sizeof(T)` bytes
/// - No aliasing mutable references exist to this data
unsafe fn copy_from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
std::slice::from_raw_parts(ptr, len).to_vec()
}
// Calling code MUST validate preconditions
let data = unsafe {
// SAFETY: C function guarantees ptr is valid, len bytes
copy_from_raw(c_buffer, buffer_len)
};
Security Testing
- •
No panics on untrusted input: All public functions should return
Result, never panic on bad input - •
Test adversarial inputs:
- •Empty data
- •Maximum size data
- •Null/invalid pointers (for FFI)
- •Concurrency stress tests (data races)
- •
Input validation: Validate all untrusted input at boundaries (FFI, network, files)
- •
Property-based testing: Use
proptestfor fuzz-like testing in normal tests
Security Test Example
rust
#[cfg(test)]
mod security_tests {
use proptest::proptest;
#[test]
fn parse_never_panics_on_arbitrary_input() {
// Test with random bytes
for _ in 0..1000 {
let random_bytes = rand::random::<Vec<u8>>();
let result = my_crate::parse(&random_bytes);
// Should return Err, never panic
assert!(result.is_ok() || result.is_err());
}
}
#[test]
fn handles_max_size_input() {
let huge_input = vec![0u8; 1024 * 1024 * 100]; // 100MB
let result = my_crate::parse(&huge_input);
assert!(result.is_ok() || result.is_err()); // Never panic
}
proptest! {
#[test]
fn parse_property_never_panics(input in ".*") {
let _ = my_crate::parse(input.as_bytes());
}
}
}
Dependency Management
- •Minimize dependencies: Each dependency is a potential vulnerability surface
- •Evaluate before adding: Check security history, maintenance status, license
- •Keep updated: Regular
cargo updatebut within version constraints - •Audit on CI: Every PR should pass
cargo auditandcargo deny
Deny Configuration
toml
# deny.toml
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "warn"
notice = "warn"
ignore = []
[bans]
multiple-versions = "warn"
wildcards = "warn"
allow = []
deny = [
{ name = "openssl", version = "<0.10" },
{ name = "parking_lot", version = "*" } # Use std instead
]
[sources]
unknown-registry = "warn"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
Anti-Patterns
- •No SAFETY comments: Unsafe code without explanation is unreviewable and unmaintainable
- •Unsafe in public API: Safe wrapper should be enforced at public boundary
- •Ignoring cargo-audit warnings: Known vulns should block builds immediately
- •Panicking on untrusted input:
unwrap()on parsed/network data will crash in production - •No fuzzing of parsers: Parsers are attack surface; fuzzing essential
- •Mixing unsafe and concurrency: Unsafe + threading = subtle data races; minimize overlap
- •Outdated dependencies: Stale deps accumulate vulns; regular updates required
- •No bounds checking on FFI pointers: Dereferencing unchecked FFI pointers is UB
Security Checklist for Release
- •
cargo auditpasses (no known vulns) - •
cargo deny checkpasses (licenses, sources) - • All unsafe blocks have SAFETY comments
- • Fuzzing targets pass (no crashes)
- •
cargo test --releasepasses (including security tests) - • No panics on arbitrary input
- • Dependencies are reviewed for security
- • SECURITY.md updated with any CVE fixes