Skip to content
1 change: 0 additions & 1 deletion crates/security-engine/tests/windows_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

#[cfg(windows)]
#[test]
fn test_get_vessel_credentials_consistency() {
Expand Down
17 changes: 17 additions & 0 deletions crates/vessel/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ async fn main() -> Result<()> {
}
};

tokio::spawn(async move {
if let Ok(_) = tokio::signal::ctrl_c().await {
log::info!("Received Ctrl-C, initiating shutdown sequence...");
sanitation::restore_system_defaults();
std::process::exit(0);
}
});

struct ShutdownGuard;
impl Drop for ShutdownGuard {
fn drop(&mut self) {
sanitation::restore_system_defaults();
}
}
let _shutdown_guard = ShutdownGuard;

sanitation::clean_high_risk_directories();

// Create secondary desktop before security initialization so that
Expand All @@ -132,6 +148,7 @@ async fn main() -> Result<()> {
Commands::Stop => {
log::info!("Stopping Vessel agent...");
// TODO: Clean up isolated desktop sessions and guest applications
sanitation::restore_system_defaults();
}
Commands::Credentials(args) => {
match args.command {
Expand Down
120 changes: 117 additions & 3 deletions crates/vessel/src/sanitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ mod windows_impl {
type DWORD = u32;
type WCHAR = u16;

type PVOID = *mut std::ffi::c_void;

const MAXIMUM_ALLOWED: DWORD = 0x02000000;
const SPI_GETFOREGROUNDLOCKTIMEOUT: DWORD = 0x2000;
const SPI_SETFOREGROUNDLOCKTIMEOUT: DWORD = 0x2001;
const SPIF_UPDATEINIFILE: DWORD = 0x01;
const SPIF_SENDCHANGE: DWORD = 0x02;

type DESKTOPENUMPROCW =
Option<unsafe extern "system" fn(lpszDesktop: *mut WCHAR, lParam: LPARAM) -> BOOL>;
Option<unsafe extern "system" fn(lpsz_desktop: *mut WCHAR, l_param: LPARAM) -> BOOL>;
type WINSTAENUMPROCW =
Option<unsafe extern "system" fn(lpszWindowStation: *mut WCHAR, lParam: LPARAM) -> BOOL>;
Option<unsafe extern "system" fn(lpsz_window_station: *mut WCHAR, l_param: LPARAM) -> BOOL>;

#[link(name = "user32")]
extern "system" {
fn GetProcessWindowStation() -> HWINSTA;
fn EnumWindowStationsW(lpEnumFunc: WINSTAENUMPROCW, lParam: LPARAM) -> BOOL;
fn EnumDesktopsW(hwinsta: HWINSTA, lpEnumFunc: DESKTOPENUMPROCW, lParam: LPARAM) -> BOOL;
fn OpenWindowStationW(
Expand All @@ -34,6 +39,19 @@ mod windows_impl {
dwDesiredAccess: DWORD,
) -> HDESK;
fn CloseDesktop(hDesktop: HDESK) -> BOOL;
fn SystemParametersInfoW(
uiAction: DWORD,
uiParam: DWORD,
pvParam: PVOID,
fWinIni: DWORD,
) -> BOOL;
}

static ORIGINAL_FOREGROUND_LOCK_TIMEOUT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
static TIMEOUT_CAPTURED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);

fn get_timeout_backup_path() -> std::path::PathBuf {
std::env::temp_dir().join("vessel_foreground_timeout.txt")
}

unsafe extern "system" fn enum_desktops_callback(
Expand Down Expand Up @@ -90,6 +108,30 @@ mod windows_impl {
let count = AtomicUsize::new(0);

unsafe {
let mut timeout: DWORD = 0;
let res = SystemParametersInfoW(
SPI_GETFOREGROUNDLOCKTIMEOUT,
0,
&mut timeout as *mut _ as PVOID,
0,
);
if res != 0 {
ORIGINAL_FOREGROUND_LOCK_TIMEOUT.store(timeout as usize, Ordering::SeqCst);
TIMEOUT_CAPTURED.store(true, Ordering::SeqCst);
let _ = std::fs::write(get_timeout_backup_path(), timeout.to_string());
log::info!("Sanitation: Captured original ForegroundLockTimeout: {} ms", timeout);

let new_timeout: DWORD = 0; // 0 allows focus stealing, preventing taskbar flash
SystemParametersInfoW(
SPI_SETFOREGROUNDLOCKTIMEOUT,
0,
new_timeout as usize as PVOID,
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE,
);
} else {
log::warn!("Sanitation: Failed to capture original ForegroundLockTimeout");
}

EnumWindowStationsW(
Some(enum_window_stations_callback),
&count as *const _ as LPARAM,
Expand All @@ -103,6 +145,60 @@ mod windows_impl {

Ok(())
}

pub fn restore_timeout() {
let mut timeout_to_restore = None;

if TIMEOUT_CAPTURED.load(Ordering::SeqCst) {
timeout_to_restore = Some(ORIGINAL_FOREGROUND_LOCK_TIMEOUT.load(Ordering::SeqCst) as DWORD);
} else {
// Try to read from backup file in case this is a separate process (e.g. vessel stop)
if let Ok(content) = std::fs::read_to_string(get_timeout_backup_path()) {
if let Ok(parsed) = content.trim().parse::<DWORD>() {
timeout_to_restore = Some(parsed);
}
}
}

if let Some(timeout) = timeout_to_restore {
unsafe {
SystemParametersInfoW(
SPI_SETFOREGROUNDLOCKTIMEOUT,
0,
timeout as usize as PVOID,
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE,
);
}
let _ = std::fs::remove_file(get_timeout_backup_path());
log::info!("Sanitation: Restored original ForegroundLockTimeout: {} ms", timeout);
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering;

#[test]
fn test_get_timeout_backup_path() {
let path = get_timeout_backup_path();
assert!(path.to_string_lossy().contains("vessel_foreground_timeout.txt"));
}

#[test]
fn test_timeout_captured_initial_state() {
// Because this is global state, we can't assume it's strictly false if other tests run,
// but we can test we can read it.
let _ = TIMEOUT_CAPTURED.load(Ordering::SeqCst);
}
}
}

pub fn restore_system_defaults() {
#[cfg(windows)]
{
windows_impl::restore_timeout();
}
}

pub fn sanitize_vessel_desktops() -> anyhow::Result<()> {
Expand Down Expand Up @@ -207,3 +303,21 @@ pub fn clean_high_risk_directories() {
wipe_dir(&target);
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_restore_system_defaults_does_not_panic() {
// This should run smoothly and not panic on any platform.
restore_system_defaults();
}

#[test]
fn test_sanitize_vessel_desktops_does_not_panic() {
// This should not panic
let res = sanitize_vessel_desktops();
assert!(res.is_ok());
}
}
Loading