-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Summary
This issue tracks the development of hardened_std, a security-hardened replacement for Rust's standard library, specifically designed for NVRC's minimal init use case in confidential VMs.
Status: Planning
Motivation
NVRC runs as PID 1 in ephemeral confidential VMs with NVIDIA GPUs. As the init process, it has full system access, making security paramount. The standard library provides too many capabilities and too much attack surface for our constrained environment.
Key drivers:
- Reduced attack surface - Less compiled code = fewer potential vulnerabilities
- Enforced security constraints - Whitelist-only access to filesystem, processes, and sockets
- no_std compatibility - Enable smaller binary size and pure syscall interface
- Fail-fast philosophy - In an ephemeral VM with fresh filesystem, if something exists that shouldn't, it's an error
Design Principles
1. Fresh Filesystem Assumption
NVRC boots in an ephemeral VM with a fresh filesystem. No defensive "remove if exists" logic needed:
// Traditional approach (hidden bugs):
if path.exists() { remove_file(path)?; }
create_file(path)?;
// hardened_std approach (fail-fast):
create_file(path)?; // Fails if exists - something is wrong!2. Whitelist-Only Access
All operations restricted to explicitly allowed paths/binaries:
const ALLOWED_PATHS: &[&str] = &["/proc/sys/", "/dev/", "/var/run/", "/sys/"];
const ALLOWED_BINARIES: &[&str] = &[
"/bin/nvidia-persistenced",
"/bin/nvidia-smi",
"/sbin/modprobe",
"/usr/bin/kata-agent",
];3. Static Arguments Only
Process arguments must be &'static str - prevents runtime injection:
// Allowed:
Command::new("/sbin/modprobe").args(&["nvidia"]).spawn()?;
// Not allowed (won't compile with strict mode):
Command::new("/bin/sh").args(&["-c", &user_input]).spawn()?;4. Size Limits
Write operations have enforced size limits (100 bytes sufficient for sysctl values).
Proposed Modules
| Module | Description |
|---|---|
hardened_std::fs |
File operations with path whitelisting, size limits |
hardened_std::process |
Process execution with binary whitelisting |
hardened_std::os::unix::net |
Unix sockets with path whitelisting |
hardened_std::panic |
VM power-off on panic |
hardened_std::collections |
Stack-allocated HashMap |
hardened_std::kernlog |
Direct syslog via libc |
hardened_std::os::fd |
AsFd/AsRawFd traits |
no_std Transition Roadmap
Planned
-
hardened_std::fs- File operations with path whitelisting -
hardened_std::process- Process execution with binary whitelisting -
hardened_std::os::unix::net- Unix sockets with path whitelisting - Replace
std::sync::Oncewithonce_cell(no_std compatible) -
hardened_std::fs::exists()- Replacestd::path::Path::exists() -
hardened_std::panic- Panic hook and power_off for VM shutdown - Implement
std::os::fd::AsFdfor hardened types - Full
#![no_std]main.rs with panic handler and global allocator - Replace
nixcrate with direct libc syscalls - Replace
kernlogcrate with hardened_std::kernlog - Replace
rlimitcrate with direct libc calls - Remove
anyhowdependency (use hardened_std::error) - Audit all dependencies for no_std compatibility
Technical Details
no_std Binary Structure
#![no_std]
#![no_main]
extern crate alloc;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
// Write to stderr, sync, power off
hardened_std::panic::power_off()
}
#[global_allocator]
static ALLOCATOR: LibcAllocator = LibcAllocator;
#[no_mangle]
pub extern "C" fn main() -> isize { /* ... */ }Build Configuration
Requires nightly Rust with -Zbuild-std:
# .cargo/config.toml
[target.x86_64-unknown-linux-musl]
build-std = ["core", "alloc"]
rustflags = ["-C", "panic=abort"]Dependencies no_std Status
| Crate | no_std? | Action |
|---|---|---|
log |
Yes | Keep (default-features = false) |
once_cell |
Yes | Keep (features = ["alloc"]) |
libc |
Yes | Keep |
cfg-if |
Yes | Keep |
anyhow |
Yes | Replace with hardened_std::error |
nix |
No | Replace with direct libc |
kernlog |
No | Replace with hardened_std::kernlog |
rlimit |
No | Replace with direct libc |
Security Benefits
- Compile-time enforcement - Invalid paths/binaries won't compile
- Runtime validation - Extra layer for dynamic values
- Fail-fast - Unexpected state causes immediate failure
- Minimal surface - Only implement what's actually needed
- No hidden behavior - Direct syscalls, no std abstractions
Open Questions
- Best approach for incremental migration vs. big-bang switch?
- How to handle test dependencies that need
std? - Should the whitelist be configurable via features?
References
- ARCHITECTURE.md - libc-free Rust section
- Creating a Freestanding Rust Binary
- The Rust no_std Book