Menu
AkurAI-Build
publicLatest change 81edd4e00048081c39422d9fa2f6530e53ea3cc5 - fsguard: one implementation of the hardened filesystem checks by Olafur Bui
//! Shared hardened filesystem helpers.
//!
//! One implementation of symlink-free opens and descriptor/path identity
//! pinning, used by the artifact, database, and server layers so future
//! fixes to these security-critical checks cannot diverge (architecture
//! review 10, finding 6).
use std::{
fs::{self, File, OpenOptions},
path::Path,
};
use anyhow::{Context, Result, ensure};
/// Open an existing file without following symlinks and require it to be
/// a regular file. `write` additionally requests write access.
pub(crate) fn open_nofollow(path: &Path, write: bool) -> Result<File> {
let mut options = OpenOptions::new();
options.read(true).write(write);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC);
}
let file = options
.open(path)
.with_context(|| format!("open {} without following symlinks", path.display()))?;
ensure!(
file.metadata()?.is_file(),
"{} is not a regular file",
path.display()
);
Ok(file)
}
/// Re-check that `path` still names exactly the open descriptor `file`
/// (same device and inode, still a regular non-symlink file), closing the
/// verify-then-use window against path swaps.
pub(crate) fn ensure_path_matches_file(path: &Path, file: &File, label: &str) -> Result<()> {
let path_metadata = fs::symlink_metadata(path)
.with_context(|| format!("inspect {label} {}", path.display()))?;
ensure!(
path_metadata.is_file() && !path_metadata.file_type().is_symlink(),
"{label} path changed while in use: {}",
path.display()
);
let file_metadata = file.metadata()?;
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
ensure!(
path_metadata.dev() == file_metadata.dev()
&& path_metadata.ino() == file_metadata.ino(),
"{label} path changed while in use: {}",
path.display()
);
}
#[cfg(not(unix))]
let _ = file_metadata;
Ok(())
}