Stop sandboxing remote shells
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
This commit is contained in:
+80
-15
@@ -2518,6 +2518,9 @@ fn write_persistent_server_config(dir: &tempfile::TempDir, port: u16) -> std::pa
|
||||
let mut raw = fs::read_to_string(&config).unwrap();
|
||||
// The base writer hardcodes `persist_sessions = false`; flip it on.
|
||||
raw = raw.replace("persist_sessions = false", "persist_sessions = true");
|
||||
// Persistence tests attach the sessions they need explicitly. Leaving the
|
||||
// base prewarm on here creates a detached default holder in every test.
|
||||
raw = raw.replace("prewarm_sessions = [\"default\"]", "prewarm_sessions = []");
|
||||
fs::write(&config, raw).unwrap();
|
||||
config
|
||||
}
|
||||
@@ -2553,6 +2556,45 @@ fn kill_holder(sessions_dir: &Path, session: &str) {
|
||||
.status();
|
||||
}
|
||||
|
||||
/// Create a holder the way older Dosh builds did for implicit sessions, so
|
||||
/// startup migration can be tested without depending on the new spawn policy.
|
||||
fn spawn_legacy_holder(sessions_dir: &Path, session: &str) {
|
||||
let server = env!("CARGO_BIN_EXE_dosh-server");
|
||||
let runtime_dir = dosh::persist::ensure_runtime_dir(sessions_dir, session).unwrap();
|
||||
let mut launcher = Command::new(server)
|
||||
.arg("hold")
|
||||
.arg("--runtime-dir")
|
||||
.arg(&runtime_dir)
|
||||
.arg("--session")
|
||||
.arg(session)
|
||||
.arg("--shell")
|
||||
.arg("/bin/sh")
|
||||
.arg("--cols")
|
||||
.arg("80")
|
||||
.arg("--rows")
|
||||
.arg("24")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
||||
while std::time::Instant::now() < deadline {
|
||||
if holder_shell_pid(sessions_dir, session).is_some()
|
||||
&& runtime_dir.join("holder.sock").exists()
|
||||
{
|
||||
let _ = launcher.wait();
|
||||
return;
|
||||
}
|
||||
if let Some(status) = launcher.try_wait().unwrap() {
|
||||
panic!("legacy holder launcher exited before ready: {status}");
|
||||
}
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
let _ = launcher.kill();
|
||||
let _ = launcher.wait();
|
||||
panic!("legacy holder did not become ready");
|
||||
}
|
||||
|
||||
/// Send one encrypted Input line and give the shell a moment to act.
|
||||
fn type_line(socket: &UdpSocket, port: u16, ok: &AttachOk, key: &[u8; 32], seq: u64, line: &str) {
|
||||
let input = Input {
|
||||
@@ -2690,7 +2732,7 @@ fn session_survives_server_restart_same_shell_and_screen() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_session_survives_server_restart_for_same_ticket_holder() {
|
||||
fn implicit_session_does_not_create_restart_holder() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let sessions_dir = dir.path().join("sessions");
|
||||
let port = free_udp_port();
|
||||
@@ -2713,20 +2755,14 @@ fn implicit_session_survives_server_restart_for_same_ticket_holder() {
|
||||
|
||||
let shell_pid_before = holder_shell_pid(&sessions_dir, &session);
|
||||
assert!(
|
||||
shell_pid_before.is_some(),
|
||||
"implicit sessions should get a persistent holder when persistence is enabled"
|
||||
shell_pid_before.is_none(),
|
||||
"implicit sessions must not get persistent holders when persistence is enabled"
|
||||
);
|
||||
|
||||
let _ = server.kill();
|
||||
let _ = server.wait();
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
|
||||
let pid = shell_pid_before.unwrap();
|
||||
assert!(
|
||||
unsafe { libc::kill(pid, 0) } == 0,
|
||||
"implicit session shell pid {pid} must survive server restart"
|
||||
);
|
||||
|
||||
let mut server = start_server(&dir, &config);
|
||||
let (socket2, bootstrap2, ok2) = direct_attach_session(&config, port, "read-write", &session);
|
||||
let key2 = bootstrap2.session_key;
|
||||
@@ -2743,15 +2779,44 @@ fn implicit_session_survives_server_restart_for_same_ticket_holder() {
|
||||
|
||||
let _ = server.kill();
|
||||
let _ = server.wait();
|
||||
kill_holder(&sessions_dir, &session);
|
||||
|
||||
assert_eq!(
|
||||
shell_pid_after, shell_pid_before,
|
||||
"implicit session should re-adopt the same shell pid"
|
||||
assert!(
|
||||
shell_pid_after.is_none(),
|
||||
"implicit restart attach must stay ephemeral"
|
||||
);
|
||||
assert!(
|
||||
post.contains("IMPLICIT_MARK=implicit_restart_42"),
|
||||
"implicit session state must survive restart, got {post:?}"
|
||||
post.contains("IMPLICIT_MARK=") && !post.contains("implicit_restart_42"),
|
||||
"implicit session must start fresh after server restart, got {post:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_discards_legacy_implicit_holders() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let sessions_dir = dir.path().join("sessions");
|
||||
let port = free_udp_port();
|
||||
let config = write_persistent_server_config(&dir, port);
|
||||
let session = protocol::generate_implicit_session_name();
|
||||
|
||||
spawn_legacy_holder(&sessions_dir, &session);
|
||||
let shell_pid = holder_shell_pid(&sessions_dir, &session).expect("legacy holder shell pid");
|
||||
assert!(
|
||||
unsafe { libc::kill(shell_pid, 0) } == 0,
|
||||
"legacy holder shell must be live before startup"
|
||||
);
|
||||
|
||||
let mut server = start_server(&dir, &config);
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
|
||||
let _ = server.kill();
|
||||
let _ = server.wait();
|
||||
assert!(
|
||||
holder_shell_pid(&sessions_dir, &session).is_none(),
|
||||
"startup must remove old implicit holder metadata"
|
||||
);
|
||||
assert!(
|
||||
unsafe { libc::kill(shell_pid, 0) } != 0,
|
||||
"startup must shut down old implicit holder shell"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,29 @@ fn release_scripts_skip_stale_artifacts_when_auto_discovering() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn systemd_sandbox_allows_netlink_for_terminal_tools() {
|
||||
fn systemd_service_does_not_sandbox_remote_shells() {
|
||||
let service = include_str!("../packaging/systemd/dosh-server.service");
|
||||
let install = include_str!("../install.sh");
|
||||
for raw in [service, install] {
|
||||
assert!(raw.contains("KillMode=process"));
|
||||
for directive in [
|
||||
"NoNewPrivileges=",
|
||||
"PrivateTmp=",
|
||||
"ProtectSystem=",
|
||||
"ProtectHome=",
|
||||
"RestrictAddressFamilies=",
|
||||
"RestrictRealtime=",
|
||||
"LockPersonality=",
|
||||
"MemoryDenyWriteExecute=",
|
||||
] {
|
||||
assert!(
|
||||
!raw.contains(directive),
|
||||
"dosh sessions are user shells; systemd sandbox directive {directive} changes terminal/app behavior"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
raw.contains("RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK"),
|
||||
"dosh sessions must allow AF_NETLINK so tools like btop can enumerate network interfaces"
|
||||
!raw.contains("ReadWritePaths="),
|
||||
"remote shells must not be restricted to dosh config/data paths"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user