AkurAI Build
Menu

AkurAI-Build

public

Latest 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(())
}