Expand description
High-level abstractions of *at(2)-and-related Linux syscalls to build race condition-free, thread-safe, symlink traversal attack-safe user APIs.
use sneak::{default_flags, openat2, Dir, OpenHow};
use libc::{RESOLVE_BENEATH, O_CREAT, O_WRONLY, O_RDONLY};
use std::io::Write;
let root = Dir::open(".")?;
// Open subdirectories with `openat2` adapters
let appdata = root.open_dirs_beneath(format!("application/data/{user_path}"))?;
// Open successive directories with chained `openat` calls
let sibling = root.open_dirs("../neighbor")?;
// Open files
let mut data = sibling.open_file("data.bin", O_CREAT | O_WRONLY, 0o655)?;
data.write_all(b"hello world!\n");
// Directly use openat2
let mut how = OpenHow::zeroed();
how.flags = O_RDONLY | O_CREAT;
how.mode = 0o777;
how.resolve = RESOLVE_BENEATH;
let dirfd = openat2(dirfd, "subfolder", &how)?;§Motivation
While building filesystem-abstracting APIs, you can easily run into race conditions: classic system calls, as exposed by Rust’s filesystem library, often do not provide sufficient protections in multi-threaded or multi-process applications.
In more complex applications, especially if they run as root, you risk exposing yourself to
time-of-check time-of-use (TOCTOU) race conditions, which can culminate to privilege escalation
vulnerabilities. Up until recently, std::fs::remove_dir_all was sensitive to this attack
vector.
Unfortunately, avoiding these race conditions is not an easy task. You need to directly
interact with specialized system calls, handle different operating systems and unsafe code.
This library aims to provide a safe, easy to use yet ultra flexible API which doesn’t hide away
any implementation details.
§Do I need to use sneak?
If your application accesses and modifies a filesystem tree at the same time as another thread or another process, especially if one of these processes runs as root, you should use sneak or any similar library.
use sneak::Dir;
let base_dir = Dir::open(BASE_DIR)?;
println!("uid({})", base_dir.fstat()?.uid());You can use it within your application to secure your filesystem interactions:
use sneak::Dir;
#[post("/files/upload")]
fn upload(request: &Request, data: Vec<u8>) -> anyhow::Result<()> {
let user_dir: PathBuf = directory_of_user(request.user_id);
let user_dir = Dir::open(&user_dir)?;
// if another application has access to these files at the same time
// as our API, we can avoid race conditions with sneak:
let mut data_file = user_dir.open_file(format!("user_data/{}/data.bin", request.user_id), libc::WRONLY)?;
// set correct file permissions
data_file.fchown(request.user_uid, request.user_gid)?;
// write the data
data_file.write_all(&data)?;
Ok(())
}§Async support
A crate like this cannot support async without being runtime-specific. Though, using it as part
of your async codebase should be easy: just wrap the syscall-calling operations in your
runtime’s spawn_blocking function. This includes all methods on Dir, as well as its
Drop implementation.
use std::path::PathBuf;
use std::io;
use sneak::Dir;
use tokio::task::spawn_blocking;
use tokio::fs::File;
/// Example with Tokio.
async fn open_file_async(base_dir: PathBuf, filepath: PathBuf) -> io::Result<File> {
spawn_blocking(move || {
let file = Dir::open(&base_dir)?.open_file(&filepath)?;
Ok(File::from_std(file))
}).await.expect("I/O task not to panic")
}§OS Support
This crate exclusively supports Linux. Some methods use the openat2 syscall, which is only
supported by Linux 5.6+. You may check for openat2 compatibility with [openat2_compatible].
§Prior art
The openat crate is more widely used and exposes a few
more methods, but lacks some flexibility I personally needed.
§License
This software is dual-licensed under the MIT license and the Apache-2.0 license.
Structs§
- Dir
- A owned reference to an opened directory. This reference is automatically cleaned up on drop.
- DirStream
- Dirent
- Metadata
- File or directory metadata. This is analogous to the standard library’s
Metadata. - OpenHow
- Arguments the behavior of the
openat2syscall.
Functions§
- default_
flags - Returns the default flags used by
Dir. In the majority cases, these flags should be used. - openat2
- Wrapper around the
openat2syscall.