Implement fail-closed ignored workspace routing

This commit is contained in:
ms2k 2026-07-16 13:03:04 +09:00
commit 95104fa4e7

View file

@ -0,0 +1,99 @@
use std::fs::File;
use std::io;
use std::os::fd::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use rustix::fs::{Mode, OFlags};
use rustix::mount::{MountFlags, UnmountFlags, mount_bind, mount_remount, unmount};
use crate::backing::BackingRoot;
#[derive(Clone, Debug)]
pub struct GitSentryConfiguration {
pub mounted_workspace: PathBuf,
pub host_git_common_directory: PathBuf,
pub transient_hooks: PathBuf,
}
pub struct GitSentry {
config_target: File,
hook_target: File,
}
impl GitSentry {
/// # Errors
/// Refuses linked or missing sources and targets and incomplete protective mounts.
pub fn install(configuration: &GitSentryConfiguration) -> io::Result<Self> {
let _workspace = BackingRoot::open(&configuration.mounted_workspace)?;
let common = BackingRoot::open(&configuration.host_git_common_directory)?;
let transient = BackingRoot::open(&configuration.transient_hooks)?;
let hook_source = transient.duplicate()?;
let hook_target = common.open_entry(
Path::new("hooks"),
OFlags::RDONLY | OFlags::DIRECTORY,
Mode::empty(),
)?;
descriptor_bind(&hook_source, &hook_target)?;
let hook_target = common.open_entry(
Path::new("hooks"),
OFlags::RDONLY | OFlags::DIRECTORY,
Mode::empty(),
)?;
let config_source =
common.open_entry(Path::new("config"), OFlags::RDONLY, Mode::empty())?;
let config_target =
common.open_entry(Path::new("config"), OFlags::RDONLY, Mode::empty())?;
descriptor_bind(&config_source, &config_target)?;
let config_target =
common.open_entry(Path::new("config"), OFlags::RDONLY, Mode::empty())?;
if let Err(error) = descriptor_remount_read_only(&config_target) {
let _ = descriptor_unmount(&hook_target);
return Err(error);
}
Ok(Self {
config_target,
hook_target,
})
}
/// # Errors
/// Returns an error unless both exact protective mountpoints are detached.
pub fn unmount(self) -> io::Result<()> {
let config = descriptor_unmount(&self.config_target);
let hooks = descriptor_unmount(&self.hook_target);
config.and(hooks)
}
}
fn descriptor_bind(source: &File, target: &File) -> io::Result<()> {
Ok(mount_bind(
proc_fd(source.as_raw_fd()),
proc_fd(target.as_raw_fd()),
)?)
}
fn descriptor_remount_read_only(target: &File) -> io::Result<()> {
let result = mount_remount(
proc_fd(target.as_raw_fd()),
MountFlags::BIND
| MountFlags::RDONLY
| MountFlags::NOSUID
| MountFlags::NODEV
| MountFlags::NOEXEC,
"",
);
if result.is_err() {
let _ = unmount(proc_fd(target.as_raw_fd()), UnmountFlags::DETACH);
}
Ok(result?)
}
fn descriptor_unmount(target: &File) -> io::Result<()> {
Ok(unmount(proc_fd(target.as_raw_fd()), UnmountFlags::DETACH)?)
}
fn proc_fd(fd: RawFd) -> PathBuf {
PathBuf::from(format!("/proc/self/fd/{fd}"))
}