#![cfg(target_os = "linux")]
use crate::PathBoundary;
use std::fs;
use std::path::PathBuf;
fn get_proc_self_root() -> PathBuf {
PathBuf::from("/proc/self/root")
}
fn get_self_pid() -> u32 {
std::process::id()
}
#[test]
fn regression_normal_paths_unaffected() {
let temp = tempfile::tempdir().unwrap();
let host_dir: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let subdir = host_dir.strict_join("subdir").unwrap();
subdir.create_dir().unwrap();
let file = host_dir.strict_join("subdir/file.txt").unwrap();
file.write("test content").unwrap();
let content = file.read_to_string().unwrap();
assert_eq!(content, "test content");
}
#[test]
fn regression_symlinks_in_normal_boundaries() {
let temp = tempfile::tempdir().unwrap();
let base = temp.path();
let data_dir = base.join("data");
let config_dir = base.join("config");
fs::create_dir_all(&data_dir).unwrap();
fs::create_dir_all(&config_dir).unwrap();
fs::write(data_dir.join("file.txt"), "data").unwrap();
let link = config_dir.join("data_link");
std::os::unix::fs::symlink(&data_dir, &link).unwrap();
let test_dir: PathBoundary = PathBoundary::try_new(base).unwrap();
match test_dir.strict_join("config/data_link/file.txt") {
Ok(path) => {
assert!(path.strictpath_starts_with(test_dir.interop_path()));
}
Err(e) => {
panic!("Internal symlink following failed: {:?}", e);
}
}
}
#[test]
fn regression_escaping_symlinks_rejected() {
let temp = tempfile::tempdir().unwrap();
let base = temp.path();
let restricted_dir = base.join("restricted");
let outside_dir = base.join("outside");
fs::create_dir_all(&restricted_dir).unwrap();
fs::create_dir_all(&outside_dir).unwrap();
fs::write(outside_dir.join("secret.txt"), "secret").unwrap();
let escape_link = restricted_dir.join("escape");
std::os::unix::fs::symlink(&outside_dir, &escape_link).unwrap();
let sandbox: PathBoundary = PathBoundary::try_new(&restricted_dir).unwrap();
use crate::StrictPathError;
let result = sandbox.strict_join("escape/secret.txt");
match result {
Err(StrictPathError::PathEscapesBoundary { .. }) => {
}
Ok(path) => {
panic!("Symlink escape was allowed: {}", path.strictpath_display());
}
Err(e) => {
eprintln!("Symlink escape test error: {:?}", e);
}
}
}
#[test]
fn edge_case_proc_self_with_components() {
let proc_self_root = get_proc_self_root();
if let Ok(container_dir) = PathBoundary::<()>::try_new(&proc_self_root) {
use crate::StrictPathError;
match container_dir.strict_join("..") {
Err(StrictPathError::PathEscapesBoundary { .. }) => {
}
Ok(path) => {
let path_str = path.strictpath_display().to_string();
assert!(
path_str.starts_with("/proc/self/root"),
"Parent traversal escaped namespace: {}",
path_str
);
}
Err(e) => {
eprintln!("Parent traversal test: {:?}", e);
}
}
}
}
#[test]
fn edge_case_empty_segments_in_proc_namespace() {
let proc_self_root = get_proc_self_root();
if let Ok(container_dir) = PathBoundary::<()>::try_new(&proc_self_root) {
let tricky_patterns = [
"etc//passwd", "etc/./passwd", "./etc/passwd", "etc/", ];
for pattern in tricky_patterns {
if let Ok(path) = container_dir.strict_join(pattern) {
assert!(
path.strictpath_starts_with(container_dir.interop_path()),
"Pattern '{}' escaped: {}",
pattern,
path.strictpath_display()
);
}
}
}
}
#[test]
fn edge_case_long_paths_in_proc_namespace() {
let proc_self_root = get_proc_self_root();
if let Ok(container_dir) = PathBoundary::<()>::try_new(&proc_self_root) {
let long_component = "a".repeat(64);
let long_path = format!(
"{}/{}/{}/{}",
long_component, long_component, long_component, long_component
);
match container_dir.strict_join(&long_path) {
Ok(path) => {
assert!(
path.strictpath_starts_with(container_dir.interop_path()),
"Long path escaped namespace"
);
}
Err(_) => {
}
}
}
}
#[test]
fn edge_case_unicode_in_proc_namespace() {
let proc_self_root = get_proc_self_root();
if let Ok(container_dir) = PathBoundary::<()>::try_new(&proc_self_root) {
let unicode_patterns = [
"文件/测试.txt",
"файл/тест.txt",
"αρχείο/δοκιμή.txt",
"ファイル/テスト.txt",
];
for pattern in unicode_patterns {
if let Ok(path) = container_dir.strict_join(pattern) {
assert!(
path.strictpath_starts_with(container_dir.interop_path()),
"Unicode pattern '{}' escaped",
pattern
);
}
}
}
}
#[test]
fn multicontainer_different_namespaces_isolated() {
let proc_self_root = get_proc_self_root();
let pid = get_self_pid();
let proc_pid_root = format!("/proc/{}/root", pid);
let container_dir1 = PathBoundary::<()>::try_new(&proc_self_root);
let container_dir2 = PathBoundary::<()>::try_new(&proc_pid_root);
if let (Ok(b1), Ok(b2)) = (container_dir1, container_dir2) {
let b1_path = b1.interop_path().to_string_lossy();
let b2_path = b2.interop_path().to_string_lossy();
assert!(b1_path.starts_with("/proc/"));
assert!(b2_path.starts_with("/proc/"));
assert_ne!(b1_path, "/");
assert_ne!(b2_path, "/");
}
}
#[test]
fn doc_test_issue_44_scenario() {
let proc_self_root = get_proc_self_root();
if let Ok(container_dir) = PathBoundary::<()>::try_new(&proc_self_root) {
let boundary_str = container_dir.interop_path().to_string_lossy().to_string();
assert_ne!(
boundary_str, "/",
"BUG REGRESSION: PathBoundary at /proc/self/root incorrectly resolved to /"
);
assert!(
boundary_str.starts_with("/proc/self/root"),
"BUG REGRESSION: PathBoundary lost /proc/self/root prefix, got: {}",
boundary_str
);
}
}