
- Remove Rust formatting check from CI workflow since formatting is now applied - Standardize import ordering and organization throughout codebase - Fix indentation, spacing, and line breaks for consistency - Clean up trailing whitespace and formatting inconsistencies - Apply rustfmt to all Rust source files including checkpoint, sandbox, commands, and test modules This establishes a consistent code style baseline for the project.
248 lines
7.5 KiB
Rust
248 lines
7.5 KiB
Rust
//! Integration tests for process isolation in sandbox
|
|
use crate::sandbox::common::*;
|
|
use crate::skip_if_unsupported;
|
|
use claudia_lib::sandbox::executor::SandboxExecutor;
|
|
use gaol::profile::{AddressPattern, Operation, PathPattern, Profile};
|
|
use serial_test::serial;
|
|
use tempfile::TempDir;
|
|
|
|
/// Test that process spawning is always forbidden
|
|
#[test]
|
|
#[serial]
|
|
fn test_process_spawn_forbidden() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test project
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
|
|
// Create profile with various permissions (process spawn should still be blocked)
|
|
let operations = vec![
|
|
Operation::FileReadAll(PathPattern::Subpath(test_fs.project_path.clone())),
|
|
Operation::NetworkOutbound(AddressPattern::All),
|
|
];
|
|
|
|
let profile = match Profile::new(operations) {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
eprintln!("Failed to create profile - operation not supported");
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Create test binary that tries to spawn a process
|
|
let test_code = test_code::spawn_process();
|
|
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let binary_path = create_test_binary("test_spawn", test_code, binary_dir.path())
|
|
.expect("Failed to create test binary");
|
|
|
|
// Execute in sandbox
|
|
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
|
|
match executor.execute_sandboxed_spawn(
|
|
&binary_path.to_string_lossy(),
|
|
&[],
|
|
&test_fs.project_path,
|
|
) {
|
|
Ok(mut child) => {
|
|
let status = child.wait().expect("Failed to wait for child");
|
|
// Process spawning might not be blocked on all platforms
|
|
if status.success() {
|
|
eprintln!("WARNING: Process spawning was not blocked");
|
|
// macOS sandbox might have limitations
|
|
if std::env::consts::OS != "linux" {
|
|
eprintln!(
|
|
"Process spawning might not be fully blocked on {}",
|
|
std::env::consts::OS
|
|
);
|
|
} else {
|
|
panic!("Process spawning should be blocked on Linux");
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test that fork is blocked
|
|
#[test]
|
|
#[serial]
|
|
#[cfg(unix)]
|
|
fn test_fork_forbidden() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test project
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
|
|
// Create minimal profile
|
|
let operations = vec![Operation::FileReadAll(PathPattern::Subpath(
|
|
test_fs.project_path.clone(),
|
|
))];
|
|
|
|
let profile = match Profile::new(operations) {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
eprintln!("Failed to create profile - operation not supported");
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Create test binary that tries to fork
|
|
let test_code = test_code::fork_process();
|
|
|
|
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let binary_path = create_test_binary_with_deps(
|
|
"test_fork",
|
|
test_code,
|
|
binary_dir.path(),
|
|
&[("libc", "0.2")],
|
|
)
|
|
.expect("Failed to create test binary");
|
|
|
|
// Execute in sandbox
|
|
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
|
|
match executor.execute_sandboxed_spawn(
|
|
&binary_path.to_string_lossy(),
|
|
&[],
|
|
&test_fs.project_path,
|
|
) {
|
|
Ok(mut child) => {
|
|
let status = child.wait().expect("Failed to wait for child");
|
|
// Fork might not be blocked on all platforms
|
|
if status.success() {
|
|
eprintln!("WARNING: Fork was not blocked (platform limitation)");
|
|
if std::env::consts::OS == "linux" {
|
|
panic!("Fork should be blocked on Linux");
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test that exec is blocked
|
|
#[test]
|
|
#[serial]
|
|
#[cfg(unix)]
|
|
fn test_exec_forbidden() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test project
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
|
|
// Create minimal profile
|
|
let operations = vec![Operation::FileReadAll(PathPattern::Subpath(
|
|
test_fs.project_path.clone(),
|
|
))];
|
|
|
|
let profile = match Profile::new(operations) {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
eprintln!("Failed to create profile - operation not supported");
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Create test binary that tries to exec
|
|
let test_code = test_code::exec_process();
|
|
|
|
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let binary_path = create_test_binary_with_deps(
|
|
"test_exec",
|
|
test_code,
|
|
binary_dir.path(),
|
|
&[("libc", "0.2")],
|
|
)
|
|
.expect("Failed to create test binary");
|
|
|
|
// Execute in sandbox
|
|
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
|
|
match executor.execute_sandboxed_spawn(
|
|
&binary_path.to_string_lossy(),
|
|
&[],
|
|
&test_fs.project_path,
|
|
) {
|
|
Ok(mut child) => {
|
|
let status = child.wait().expect("Failed to wait for child");
|
|
// Exec might not be blocked on all platforms
|
|
if status.success() {
|
|
eprintln!("WARNING: Exec was not blocked (platform limitation)");
|
|
if std::env::consts::OS == "linux" {
|
|
panic!("Exec should be blocked on Linux");
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test thread creation is allowed
|
|
#[test]
|
|
#[serial]
|
|
fn test_thread_creation_allowed() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test project
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
|
|
// Create minimal profile
|
|
let operations = vec![Operation::FileReadAll(PathPattern::Subpath(
|
|
test_fs.project_path.clone(),
|
|
))];
|
|
|
|
let profile = match Profile::new(operations) {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
eprintln!("Failed to create profile - operation not supported");
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Create test binary that creates threads
|
|
let test_code = r#"
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
fn main() {
|
|
let handle = thread::spawn(|| {
|
|
thread::sleep(Duration::from_millis(100));
|
|
42
|
|
});
|
|
|
|
match handle.join() {
|
|
Ok(value) => {
|
|
println!("SUCCESS: Thread returned {}", value);
|
|
}
|
|
Err(_) => {
|
|
eprintln!("FAILURE: Thread panicked");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
"#;
|
|
|
|
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
|
let binary_path = create_test_binary("test_thread", test_code, binary_dir.path())
|
|
.expect("Failed to create test binary");
|
|
|
|
// Execute in sandbox
|
|
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
|
|
match executor.execute_sandboxed_spawn(
|
|
&binary_path.to_string_lossy(),
|
|
&[],
|
|
&test_fs.project_path,
|
|
) {
|
|
Ok(mut child) => {
|
|
let status = child.wait().expect("Failed to wait for child");
|
|
assert!(status.success(), "Thread creation should be allowed");
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
|
}
|
|
}
|
|
}
|