Skip to content
Open
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
79 changes: 77 additions & 2 deletions crates/openshell-driver-vm/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ impl VmDriver {
.env(openshell_core::sandbox_env::SANDBOX, &sandbox.name)
.env(
openshell_core::sandbox_env::SSH_SOCKET_PATH,
state_dir.join("ssh.sock"),
sandbox_socket_dir(&sandbox.id).join("ssh.sock"),
)
.env(
openshell_core::sandbox_env::PROXY_TLS_DIR,
Expand Down Expand Up @@ -1086,17 +1086,26 @@ impl VmDriver {
return Err(Status::internal(format!("create state dir failed: {err}")));
}

if let Err(err) = create_sandbox_socket_dir(&sandbox.id).await {
let mut registry = self.registry.lock().await;
registry.remove(&sandbox.id);
let _ = tokio::fs::remove_dir_all(&state_dir).await;
return Err(Status::internal(format!("create socket dir failed: {err}")));
}

if let Err(err) = self.ensure_extension_state_dirs(&state_dir).await {
let mut registry = self.registry.lock().await;
registry.remove(&sandbox.id);
let _ = tokio::fs::remove_dir_all(&state_dir).await;
remove_sandbox_socket_dir(&sandbox.id).await;
return Err(err);
}

if let Err(err) = write_sandbox_request(&state_dir, sandbox).await {
let mut registry = self.registry.lock().await;
registry.remove(&sandbox.id);
let _ = tokio::fs::remove_dir_all(&state_dir).await;
remove_sandbox_socket_dir(&sandbox.id).await;
return Err(Status::internal(format!(
"write sandbox start metadata failed: {err}"
)));
Expand Down Expand Up @@ -1172,6 +1181,7 @@ impl VmDriver {
if overlay_preparation == OverlayPreparation::Fresh {
let _ = tokio::fs::remove_dir_all(&state_dir).await;
}
remove_sandbox_socket_dir(&sandbox_id).await;
return;
}

Expand Down Expand Up @@ -1443,7 +1453,10 @@ impl VmDriver {
}

let console_output = state_dir.join("rootfs-console.log");
let control_socket = state_dir.join(VM_CONTROL_SOCKET);
create_sandbox_socket_dir(&sandbox.id)
.await
.map_err(|err| Status::internal(format!("create socket dir failed: {err}")))?;
let control_socket = sandbox_socket_dir(&sandbox.id).join(VM_CONTROL_SOCKET);
let session_id = launch_authentication.supervisor.session_id;
let channel_tls = generate_sandbox_tls_material(session_id)
.map_err(|error| Status::internal(error.to_string()))?;
Expand Down Expand Up @@ -1968,6 +1981,7 @@ impl VmDriver {
}

remove_sandbox_state_dir(&self.config.state_dir, &state_dir).await?;
remove_sandbox_socket_dir(&record_id).await;

{
let mut registry = self.registry.lock().await;
Expand Down Expand Up @@ -2589,6 +2603,7 @@ impl VmDriver {

if remove_state {
let _ = tokio::fs::remove_dir_all(state_dir).await;
remove_sandbox_socket_dir(sandbox_id).await;
}
self.publish_platform_event(
sandbox_id.to_string(),
Expand Down Expand Up @@ -5601,6 +5616,45 @@ async fn restrict_owner_only_dir(_path: &Path) -> Result<(), std::io::Error> {
Ok(())
}

/// Socket directory under `/tmp` for a sandbox's Unix domain sockets
/// (`control.sock`, `ssh.sock`). Hardcoded `/tmp` rather than
/// `std::env::temp_dir()` because macOS `TMPDIR` resolves to
/// `/var/folders/…/T/` (~51 chars), which would re-exceed the 104-byte
/// `sun_path` limit that this relocation exists to fix.
fn sandbox_socket_dir(sandbox_id: &str) -> PathBuf {
PathBuf::from("/tmp/openshell/sandboxes").join(sandbox_id)
}

async fn ensure_socket_parent_dir() -> Result<(), std::io::Error> {
let parent = Path::new("/tmp/openshell/sandboxes");
match tokio::fs::create_dir_all(parent).await {
Ok(()) => {
#[cfg(unix)]
{
tokio::fs::set_permissions(
Path::new("/tmp/openshell"),
fs::Permissions::from_mode(0o1777),
)
.await?;
tokio::fs::set_permissions(parent, fs::Permissions::from_mode(0o1777)).await?;
}
Ok(())
}
Err(e) => Err(e),
}
}

async fn create_sandbox_socket_dir(sandbox_id: &str) -> Result<PathBuf, std::io::Error> {
ensure_socket_parent_dir().await?;
let dir = sandbox_socket_dir(sandbox_id);
create_private_dir_all(&dir).await?;
Ok(dir)
}

async fn remove_sandbox_socket_dir(sandbox_id: &str) {
let _ = tokio::fs::remove_dir_all(sandbox_socket_dir(sandbox_id)).await;
}

#[allow(clippy::result_large_err)]
fn sandbox_state_dir(root: &Path, sandbox_id: &str) -> Result<PathBuf, Status> {
validate_sandbox_id(sandbox_id)?;
Expand Down Expand Up @@ -8430,6 +8484,27 @@ mod tests {
let _ = std::fs::remove_dir_all(root);
}

#[test]
fn sandbox_socket_dir_fits_macos_sun_path() {
let uuid = "3eb2ad45-bead-4c2e-bd10-1a4a7f3a2721";
let control = sandbox_socket_dir(uuid).join(VM_CONTROL_SOCKET);
let ssh = sandbox_socket_dir(uuid).join("ssh.sock");
assert!(
control.as_os_str().len() < 104,
"control socket path exceeds macOS sun_path: {}",
control.display()
);
assert!(
ssh.as_os_str().len() < 104,
"ssh socket path exceeds macOS sun_path: {}",
ssh.display()
);
assert_eq!(
sandbox_socket_dir(uuid),
PathBuf::from("/tmp/openshell/sandboxes/3eb2ad45-bead-4c2e-bd10-1a4a7f3a2721")
);
}

#[test]
fn sandbox_state_dir_rejects_path_unsafe_ids() {
let err = sandbox_state_dir(Path::new("/tmp/openshell-vm"), "../escape")
Expand Down
Loading