Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/core/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,32 @@ pub fn check_seccomp() -> DiagnosticResult {
}
}

/// Check if project root is on overlayfs (e.g., inside Docker)
///
/// Nested overlay mounts are unsupported by the Linux kernel, so tach
/// must fall back to fork-only isolation when running inside a container
/// that uses the overlay2 storage driver.
pub fn check_overlay_filesystem(project_root: &std::path::Path) -> DiagnosticResult {
if crate::isolation::is_overlayfs(project_root) {
DiagnosticResult::warn(
"Overlay FS",
"Project root is on overlayfs (Docker detected)",
)
.with_details(
"Nested overlay mounts are unsupported by the Linux kernel.\n\
Tach will automatically fall back to fork-only isolation.\n\
This is safe but disables filesystem write protection.",
)
.with_remediation(Remediation {
command: Some("Run natively or use --no-isolation to suppress this check".to_string()),
docs_url: None,
explanation: "Overlay isolation requires ext4/btrfs/xfs host filesystem".to_string(),
})
} else {
DiagnosticResult::pass("Overlay FS", "Native filesystem — full isolation available")
}
}

/// Check jemalloc allocator status
pub fn check_jemalloc() -> DiagnosticResult {
match crate::allocator::verify_jemalloc_active() {
Expand Down Expand Up @@ -839,6 +865,7 @@ pub fn run_diagnostics() -> DiagnosticReport {
check_userfaultfd(),
check_landlock(),
check_seccomp(),
check_overlay_filesystem(&std::env::current_dir().unwrap_or_default()),
check_jemalloc(),
check_ptrace_capability(),
check_python(),
Expand Down Expand Up @@ -892,6 +919,9 @@ pub fn run_and_print_diagnose() -> bool {
let seccomp_result = check_seccomp();
print_diagnose_line(" Seccomp", &seccomp_result);

let overlay_result = check_overlay_filesystem(&std::env::current_dir().unwrap_or_default());
print_diagnose_line(" Overlay FS", &overlay_result);

let jemalloc_result = check_jemalloc();
print_diagnose_line(" Jemalloc", &jemalloc_result);
eprintln!();
Expand Down
2 changes: 1 addition & 1 deletion src/isolation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub mod sandbox;
pub mod snapshot;

// Re-export main functions from namespace for backward compatibility
pub use namespace::setup_filesystem;
pub use namespace::{is_overlayfs, setup_filesystem};

// Re-export calibration for Zygote warm-up
pub use calibration::TlsCalibration;
Expand Down
91 changes: 61 additions & 30 deletions src/isolation/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

/// overlayfs filesystem magic number from the Linux kernel
const OVERLAYFS_SUPER_MAGIC: i64 = 0x794c7630;

/// Detect if the given path resides on an overlayfs filesystem.
///
/// Used to prevent nested overlay mounts which the Linux kernel does not
/// support — this is the root cause of isolation failures in Docker
/// containers where the storage driver is overlay2.
pub fn is_overlayfs(path: &Path) -> bool {
let mut buf: libc::statfs = unsafe { std::mem::zeroed() };
let c_path = match std::ffi::CString::new(path.to_string_lossy().as_bytes()) {
Ok(p) => p,
Err(_) => return false,
};
let rc = unsafe { libc::statfs(c_path.as_ptr(), &mut buf) };
if rc != 0 {
return false;
}
buf.f_type == OVERLAYFS_SUPER_MAGIC
}

/// Set up complete isolation for a worker (Iron Dome)
///
/// CRITICAL SEQUENCE:
Expand All @@ -30,6 +51,14 @@ pub fn setup_filesystem(worker_id: u32, project_root: &Path) -> Result<()> {
return Ok(());
}

let overlay_disabled = is_overlayfs(project_root);
if overlay_disabled {
eprintln!(
"[tach:isolation] Project root is on overlayfs (Docker detected). \
Overlay mounts disabled — using fork-only isolation."
);
}

// 1. Create new mount AND network namespaces
unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWNET)
.context("unshare(CLONE_NEWNS | CLONE_NEWNET) failed - requires CAP_SYS_ADMIN")?;
Expand Down Expand Up @@ -86,38 +115,40 @@ pub fn setup_filesystem(worker_id: u32, project_root: &Path) -> Result<()> {
fs::create_dir_all(&proj_upper)?;
fs::create_dir_all(&proj_work)?;

// 8. Overlay /tmp (writable zone #1)
let tmp_overlay_opts = format!(
"lowerdir=/tmp,upperdir={},workdir={}",
tmp_upper.display(),
tmp_work.display()
);

mount::<str, str, str, str>(
Some("overlay"),
"/tmp",
Some("overlay"),
MsFlags::empty(),
Some(&tmp_overlay_opts),
)
.context("Failed to mount overlay on /tmp")?;
if !overlay_disabled {
// 8. Overlay /tmp (writable zone #1)
let tmp_overlay_opts = format!(
"lowerdir=/tmp,upperdir={},workdir={}",
tmp_upper.display(),
tmp_work.display()
);

// 9. Overlay project root (writable zone #2)
let proj_overlay_opts = format!(
"lowerdir={},upperdir={},workdir={}",
project_root.display(),
proj_upper.display(),
proj_work.display()
);
mount::<str, str, str, str>(
Some("overlay"),
"/tmp",
Some("overlay"),
MsFlags::empty(),
Some(&tmp_overlay_opts),
)
.context("Failed to mount overlay on /tmp")?;

// 9. Overlay project root (writable zone #2)
let proj_overlay_opts = format!(
"lowerdir={},upperdir={},workdir={}",
project_root.display(),
proj_upper.display(),
proj_work.display()
);

mount::<str, Path, str, str>(
Some("overlay"),
project_root,
Some("overlay"),
MsFlags::empty(),
Some(&proj_overlay_opts),
)
.context("Failed to mount overlay on project root")?;
mount::<str, Path, str, str>(
Some("overlay"),
project_root,
Some("overlay"),
MsFlags::empty(),
Some(&proj_overlay_opts),
)
.context("Failed to mount overlay on project root")?;
}

Ok(())
}
Expand Down