From 5e840ed4ce8097f0c53abdc3c5b2a25d052611fd Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 12 Aug 2026 00:56:41 +0000 Subject: Add native rootfs finalization --- src/files.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/files.rs (limited to 'src/files.rs') diff --git a/src/files.rs b/src/files.rs new file mode 100644 index 0000000..f49bd5e --- /dev/null +++ b/src/files.rs @@ -0,0 +1,52 @@ +use std::path::{Component, Path, PathBuf}; + +use anyhow::{bail, Result}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RootfsPath(PathBuf); + +impl RootfsPath { + pub fn new(path: impl AsRef) -> Result { + let path = path.as_ref(); + if path.as_os_str().is_empty() || path.is_absolute() { + bail!("rootfs path must be a non-empty relative path: {}", path.display()); + } + + let mut normalized = PathBuf::new(); + for component in path.components() { + match component { + Component::Normal(component) => normalized.push(component), + Component::CurDir => {} + Component::ParentDir | Component::RootDir | Component::Prefix(_) => { + bail!("rootfs path cannot escape the rootfs: {}", path.display()); + } + } + } + Ok(Self(normalized)) + } + + pub fn as_path(&self) -> &Path { + &self.0 + } +} + +pub fn relative_symlink_is_safe(link_parent: &Path, target: &Path) -> bool { + if target.is_absolute() { + return false; + } + + let mut depth = link_parent.components().count(); + for component in target.components() { + match component { + Component::Normal(_) | Component::CurDir => {} + Component::ParentDir => { + if depth == 0 { + return false; + } + depth -= 1; + } + Component::RootDir | Component::Prefix(_) => return false, + } + } + true +} -- cgit v1.2.3