//! Windows-native helpers for privileged process launch and token inspection. use serde::Serialize; /// Target identity used for a privileged process launch. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum LaunchIdentity { /// Run as the current process token without UAC elevation. CurrentProcess, /// Run as the active interactive user. CurrentUser, /// Run as an elevated administrator. Admin, /// Run as `NT AUTHORITY\SYSTEM`. System, /// Run as `NT SERVICE\TrustedInstaller`. TrustedInstaller, } /// Privilege policy applied before launching a process. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum PrivilegeMode { /// Keep the duplicated token privileges unchanged. Default, /// Attempt to enable all privileges present on the token. EnableAll, /// Attempt to disable all privileges present on the duplicated token. DisableAll, } /// Token integrity level used for status reports and optional launch shaping. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum TokenIntegrity { /// Untrusted integrity. Untrusted, /// Low integrity. Low, /// Medium integrity. Medium, /// Medium-plus integrity. MediumPlus, /// High integrity. High, /// System integrity. System, /// Unknown or unmapped integrity. Unknown, } /// Process priority class used during launch. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum ProcessPriority { /// Idle priority. Idle, /// Below normal priority. BelowNormal, /// Normal priority. Normal, /// Above normal priority. AboveNormal, /// High priority. High, /// Realtime priority. Realtime, } /// Window presentation mode used for a launch request. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum ShowWindowMode { /// Let Windows decide. Default, /// Hide the new window. Hidden, /// Show a normal window. Normal, /// Show a minimized window. Minimized, /// Show a maximized window. Maximized, } /// Stable status report for the current process token and privileged-launch capabilities. #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct TokenStatus { /// Whether the current process is elevated. pub is_elevated: bool, /// Whether the token belongs to an administrators group member. pub is_admin_member: bool, /// Current integrity level. pub integrity: TokenIntegrity, /// Current Windows user name when available. pub current_user: Option, /// Current process session identifier when available. pub session_id: Option, /// Active console session identifier when available. pub active_session_id: Option, /// Whether the current process can directly launch as administrator. pub can_admin: bool, /// Whether an active interactive user token can be used. pub can_current_user: bool, /// Whether the current process can directly launch as `SYSTEM`. pub can_system: bool, /// Whether the current process can directly launch as `TrustedInstaller`. pub can_trustedinstaller: bool, /// Whether the `TrustedInstaller` service exists. pub trustedinstaller_installed: bool, /// Whether the `TrustedInstaller` service is already running. pub trustedinstaller_running: bool, } /// High-level launch request consumed by `msudo`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct LaunchRequest { /// Program path or command name. pub program: String, /// Command arguments excluding the program itself. pub args: Vec, /// Current working directory for the launched process. pub current_directory: Option, /// Optional stdout redirection path used by short-lived relays. pub stdout_path: Option, /// Optional stderr redirection path used by short-lived relays. pub stderr_path: Option, /// Target identity. pub identity: LaunchIdentity, /// Privilege policy. pub privileges: PrivilegeMode, /// Optional integrity shaping request. pub integrity: Option, /// Process priority class. pub priority: ProcessPriority, /// Window presentation mode. pub show_window: ShowWindowMode, /// Optional target session id. pub session: Option, /// Whether the caller explicitly wants a new window. pub new_window: bool, /// Whether the launched process should take over the caller's current console. pub same_console: bool, /// Whether to wait for completion and capture the exit code. pub wait: bool, } /// Result returned after a privileged launch attempt. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] pub struct LaunchResult { /// Spawned process identifier when available. pub pid: Option, /// Child exit code when `wait` was requested and the process exited normally. pub exit_code: Option, /// Identity used for the launched process. pub identity: LaunchIdentity, } #[cfg(not(windows))] /// Reports the current process token status. pub fn current_token_status() -> Result { Err(WindowsSupportError::Unsupported( "msudo is only supported on Windows".to_string(), )) } #[cfg(not(windows))] /// Relaunches the current executable with UAC elevation. pub fn elevate_current_process( _arguments: &[String], _wait: bool, _show_window: ShowWindowMode, ) -> Result { Err(WindowsSupportError::Unsupported( "msudo is only supported on Windows".to_string(), )) } #[cfg(not(windows))] /// Reattaches the current process to the parent's console so foreground launches can share it. pub fn attach_parent_console(_parent_pid: u32) -> Result<(), WindowsSupportError> { Err(WindowsSupportError::Unsupported( "msudo is only supported on Windows".to_string(), )) } #[cfg(not(windows))] /// Launches a process with the requested identity. pub fn launch_request(_request: &LaunchRequest) -> Result { Err(WindowsSupportError::Unsupported( "msudo is only supported on Windows".to_string(), )) } #[cfg(windows)] #[allow(unsafe_code)] mod imp { use std::ffi::{OsStr, c_void}; use std::fs::{File, OpenOptions}; use std::mem::size_of; use std::os::windows::ffi::OsStrExt; use std::os::windows::fs::OpenOptionsExt; use std::os::windows::io::AsRawHandle; use std::ptr::{null, null_mut}; use std::thread; use std::time::{Duration, Instant}; use windows_sys::Win32::Foundation::{ CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_ACCESS_DENIED, ERROR_NOT_ALL_ASSIGNED, FALSE, GetLastError, HANDLE, INVALID_HANDLE_VALUE, TRUE, WAIT_FAILED, WAIT_OBJECT_0, }; use windows_sys::Win32::Security::{ AdjustTokenPrivileges, CheckTokenMembership, CreateWellKnownSid, DuplicateTokenEx, GetTokenInformation, ImpersonateLoggedOnUser, LookupAccountSidW, LookupPrivilegeValueW, RevertToSelf, SE_PRIVILEGE_ENABLED, SecurityImpersonation, SetTokenInformation, TOKEN_ACCESS_MASK, TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_PRIVILEGES, TOKEN_ADJUST_SESSIONID, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_ELEVATION, TOKEN_IMPERSONATE, TOKEN_MANDATORY_LABEL, TOKEN_PRIVILEGES, TOKEN_QUERY, TokenElevation, TokenIntegrityLevel, TokenSessionId, WinBuiltinAdministratorsSid, WinLocalSystemSid, }; use windows_sys::Win32::System::Console::{ AttachConsole, FreeConsole, GetStdHandle, STD_ERROR_HANDLE, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, SetConsoleCtrlHandler, }; use windows_sys::Win32::System::Environment::{ CreateEnvironmentBlock, DestroyEnvironmentBlock, }; use windows_sys::Win32::System::RemoteDesktop::{ ProcessIdToSessionId, WTSGetActiveConsoleSessionId, WTSQueryUserToken, }; use windows_sys::Win32::System::Services::{ CloseServiceHandle, OpenSCManagerW, OpenServiceW, QueryServiceStatusEx, SC_HANDLE, SC_MANAGER_CONNECT, SC_STATUS_PROCESS_INFO, SERVICE_QUERY_STATUS, SERVICE_RUNNING, SERVICE_START, SERVICE_START_PENDING, SERVICE_STATUS_PROCESS, StartServiceW, }; use windows_sys::Win32::System::SystemServices::SE_GROUP_INTEGRITY; use windows_sys::Win32::System::Threading::{ ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, CREATE_NEW_CONSOLE, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, CreateProcessAsUserW, CreateProcessW, GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, OpenProcess, OpenProcessToken, PROCESS_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, REALTIME_PRIORITY_CLASS, STARTF_USESHOWWINDOW, STARTF_USESTDHANDLES, STARTUPINFOW, WaitForSingleObject, }; use windows_sys::Win32::UI::Shell::{ SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW, ShellExecuteExW, }; use crate::WindowsSupportError; use crate::sudo::{ LaunchIdentity, LaunchRequest, LaunchResult, PrivilegeMode, ProcessPriority, ShowWindowMode, TokenIntegrity, TokenStatus, }; const SECURITY_MAX_SID_SIZE: usize = 68; const START_TIMEOUT: Duration = Duration::from_secs(10); const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000; const SW_HIDE_VALUE: u16 = 0; const SW_NORMAL_VALUE: u16 = 1; const SW_SHOWMINIMIZED_VALUE: u16 = 2; const SW_SHOWMAXIMIZED_VALUE: u16 = 3; const SW_SHOWDEFAULT_VALUE: u16 = 10; const SECURITY_MANDATORY_UNTRUSTED_RID: u32 = 0x0000_0000; const SECURITY_MANDATORY_LOW_RID: u32 = 0x0000_1000; const SECURITY_MANDATORY_MEDIUM_RID: u32 = 0x0000_2000; const SECURITY_MANDATORY_MEDIUM_PLUS_RID: u32 = SECURITY_MANDATORY_MEDIUM_RID + 0x100; const SECURITY_MANDATORY_HIGH_RID: u32 = 0x0000_3000; const SECURITY_MANDATORY_SYSTEM_RID: u32 = 0x0000_4000; #[derive(Debug)] struct OwnedHandle(HANDLE); impl OwnedHandle { fn new(raw: HANDLE) -> Result { if raw.is_null() || std::ptr::eq(raw, INVALID_HANDLE_VALUE) { Err(last_error("acquire handle")) } else { Ok(Self(raw)) } } const fn raw(&self) -> HANDLE { self.0 } } impl Drop for OwnedHandle { fn drop(&mut self) { let _ = unsafe { CloseHandle(self.0) }; } } #[derive(Debug)] struct ImpersonationGuard { _token: OwnedHandle, } impl ImpersonationGuard { fn as_system() -> Result { Self::as_system_with_privileges(&[]) } fn as_system_with_privileges(privileges: &[&str]) -> Result { let token = open_system_impersonation_token()?; for privilege in privileges { enable_named_privilege(token.raw(), privilege)?; } #[allow(unsafe_code)] let impersonated = unsafe { ImpersonateLoggedOnUser(token.raw()) }; if impersonated == FALSE { return Err(last_error("ImpersonateLoggedOnUser")); } Ok(Self { _token: token }) } } impl Drop for ImpersonationGuard { fn drop(&mut self) { let _ = unsafe { RevertToSelf() }; } } #[derive(Debug)] struct OwnedServiceHandle(SC_HANDLE); impl OwnedServiceHandle { fn new(raw: SC_HANDLE, action: &'static str) -> Result { if raw.is_null() { Err(last_error(action)) } else { Ok(Self(raw)) } } const fn raw(&self) -> SC_HANDLE { self.0 } } impl Drop for OwnedServiceHandle { fn drop(&mut self) { let _ = unsafe { CloseServiceHandle(self.0) }; } } #[derive(Debug)] struct OwnedEnvironmentBlock(*mut c_void); impl OwnedEnvironmentBlock { fn for_token(token: HANDLE, inherit: bool) -> Result { let mut block = null_mut(); #[allow(unsafe_code)] let created = unsafe { CreateEnvironmentBlock(&raw mut block, token, i32::from(inherit)) }; if created == FALSE { return Err(last_error("CreateEnvironmentBlock")); } Ok(Self(block)) } const fn raw(&self) -> *const c_void { self.0.cast::() } } impl Drop for OwnedEnvironmentBlock { fn drop(&mut self) { #[allow(unsafe_code)] let _ = unsafe { DestroyEnvironmentBlock(self.0.cast::()) }; } } #[derive(Debug)] struct InheritedStdHandles { input: OwnedHandle, output: OwnedHandle, error: OwnedHandle, _stdout_file: Option, _stderr_file: Option, } #[derive(Debug)] struct ConsoleCtrlGuard { ignore_ctrl_c: bool, } impl ConsoleCtrlGuard { fn ignore(ignore_ctrl_c: bool) -> Result { if !ignore_ctrl_c { return Ok(Self { ignore_ctrl_c }); } #[allow(unsafe_code)] let installed = unsafe { SetConsoleCtrlHandler(None, 1) }; if installed == FALSE { return Err(last_error("SetConsoleCtrlHandler")); } Ok(Self { ignore_ctrl_c }) } } impl Drop for ConsoleCtrlGuard { fn drop(&mut self) { if self.ignore_ctrl_c { #[allow(unsafe_code)] let _ = unsafe { SetConsoleCtrlHandler(None, 0) }; } } } /// Reports the current process token status and privileged-launch capabilities. /// Query the current process token state and launch capabilities. /// /// # Errors /// /// Returns an error when the current process token cannot be inspected. pub fn current_token_status() -> Result { let token = open_current_token(TOKEN_QUERY)?; let is_elevated = token_elevation(token.raw())?; let is_admin_member = current_token_is_admin_member()?; let integrity = token_integrity(token.raw())?; let current_user = token_user_name(token.raw())?; let session_id = current_process_session_id(); let active_session_id = active_console_session_id(); let (trustedinstaller_installed, trustedinstaller_running) = trustedinstaller_presence()?; Ok(TokenStatus { is_elevated, is_admin_member, integrity, current_user, session_id, active_session_id, can_admin: true, can_current_user: active_session_id.is_some(), can_system: is_elevated, can_trustedinstaller: is_elevated && trustedinstaller_installed, trustedinstaller_installed, trustedinstaller_running, }) } /// Relaunches the current executable through the Windows `runas` verb. /// Relaunch the current executable with `runas` so it can continue privileged work. /// /// # Errors /// /// Returns an error when the elevated helper cannot be started or waited on. /// /// # Panics /// /// Panics only if a Win32 structure size unexpectedly stops fitting in `u32`. pub fn elevate_current_process( arguments: &[String], wait: bool, show_window: ShowWindowMode, ) -> Result { let executable = std::env::current_exe().map_err(|error| { WindowsSupportError::Process(format!("failed to locate current executable: {error}")) })?; let executable_wide = wide_from_os(executable.as_os_str()); let verb = wide("runas"); let parameters = quote_command_line(arguments.iter().map(String::as_str)); let parameters_wide = wide(¶meters); let mut execute = SHELLEXECUTEINFOW { cbSize: u32::try_from(size_of::()) .expect("shell execute info size always fits in u32"), fMask: SEE_MASK_NOCLOSEPROCESS, lpVerb: verb.as_ptr(), lpFile: executable_wide.as_ptr(), lpParameters: parameters_wide.as_ptr(), nShow: i32::from(show_window_value(show_window)), ..SHELLEXECUTEINFOW::default() }; #[allow(unsafe_code)] if unsafe { ShellExecuteExW(&raw mut execute) } == FALSE { return Err(last_error("ShellExecuteExW")); } let process = if execute.hProcess.is_null() { None } else { Some(OwnedHandle::new(execute.hProcess)?) }; let (pid, exit_code) = if let Some(process) = process.as_ref() { let pid = process_id_from_handle(process.raw())?; let exit_code = if wait { Some(wait_for_exit_code(process.raw())?) } else { None }; (Some(pid), exit_code) } else { (None, None) }; Ok(LaunchResult { pid, exit_code, identity: LaunchIdentity::Admin, }) } /// Reattach the current process to a caller-owned console before starting a foreground child. /// /// # Errors /// /// Returns an error when the console cannot be rebound to the requested parent process. pub fn attach_parent_console(parent_pid: u32) -> Result<(), WindowsSupportError> { if parent_pid == 0 { return Err(WindowsSupportError::Process( "same-console relay requires a non-zero parent console pid".to_string(), )); } #[allow(unsafe_code)] let _ = unsafe { FreeConsole() }; #[allow(unsafe_code)] let attached = unsafe { AttachConsole(parent_pid) }; if attached == FALSE { return Err(last_error("AttachConsole")); } Ok(()) } /// Launches a process with the requested identity in the current elevated context. /// Launch a process using the requested identity and token shaping options. /// /// # Errors /// /// Returns an error when token acquisition, mutation, or process creation fails. pub fn launch_request(request: &LaunchRequest) -> Result { match request.identity { LaunchIdentity::CurrentUser => launch_with_active_user(request), LaunchIdentity::Admin if admin_launch_requires_duplicate_token(request) => { launch_with_duplicate_identity(request, LaunchIdentity::Admin) } LaunchIdentity::CurrentProcess | LaunchIdentity::Admin => { launch_with_current_identity(request) } LaunchIdentity::System => { launch_with_duplicate_identity(request, LaunchIdentity::System) } LaunchIdentity::TrustedInstaller => { launch_with_duplicate_identity(request, LaunchIdentity::TrustedInstaller) } } } const fn admin_launch_requires_duplicate_token(request: &LaunchRequest) -> bool { request.session.is_some() || request.integrity.is_some() || !matches!(request.privileges, PrivilegeMode::Default) } fn launch_with_current_identity( request: &LaunchRequest, ) -> Result { let desktop = interactive_desktop(request); let mut startup = build_startup_info(request.show_window, desktop.as_deref()); let std_handles = inherited_standard_handles(request)?; apply_standard_handles(&mut startup, std_handles.as_ref()); let mut process_info = PROCESS_INFORMATION::default(); let application = application_name_wide(&request.program); let command_line_text = build_process_command_line(&request.program, &request.args); let mut command_line = wide(&command_line_text); let mut environment = build_environment_block(); let current_directory = request.current_directory.as_deref().map(wide); let creation_flags = creation_flags(request); #[allow(unsafe_code)] let created = unsafe { CreateProcessW( application.as_ref().map_or(null(), Vec::as_ptr), command_line.as_mut_ptr(), null(), null(), inherit_handles_flag(std_handles.as_ref()), creation_flags, environment.as_mut_ptr().cast::(), current_directory.as_ref().map_or(null(), Vec::as_ptr), &raw mut startup, &raw mut process_info, ) }; if created == FALSE { return Err(last_error("CreateProcessW")); } let thread = OwnedHandle::new(process_info.hThread)?; drop(thread); let process = OwnedHandle::new(process_info.hProcess)?; let exit_code = if request.wait { Some(wait_for_exit_code(process.raw())?) } else { None }; Ok(LaunchResult { pid: Some(process_info.dwProcessId), exit_code, identity: request.identity, }) } fn launch_with_duplicate_identity( request: &LaunchRequest, identity: LaunchIdentity, ) -> Result { let status = current_token_status()?; if !status.is_elevated { return Err(WindowsSupportError::Unsupported(format!( "{identity:?} launch requires an elevated administrator context" ))); } enable_token_discovery_privileges()?; let token = duplicate_primary_token(identity, None)?; let _impersonation = ImpersonationGuard::as_system_with_privileges(&[ "SeTcbPrivilege", "SeAssignPrimaryTokenPrivilege", "SeIncreaseQuotaPrivilege", "SeImpersonatePrivilege", "SeDebugPrivilege", ])?; if let Some(session_id) = resolve_target_session_id(request.session) { set_token_session_id(token.raw(), session_id)?; } apply_token_options(&token, request.privileges, request.integrity)?; launch_with_token_as_user(request, identity, &token) } fn duplicate_primary_token( identity: LaunchIdentity, session: Option, ) -> Result { let source = match identity { LaunchIdentity::Admin | LaunchIdentity::CurrentProcess => open_current_token( TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT, )?, LaunchIdentity::CurrentUser => open_active_user_process_token(session)?, LaunchIdentity::System => open_system_process_token()?, LaunchIdentity::TrustedInstaller => open_trustedinstaller_process_token()?, }; let desired_access: TOKEN_ACCESS_MASK = TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | TOKEN_ADJUST_PRIVILEGES; let mut duplicate: HANDLE = null_mut(); #[allow(unsafe_code)] let duplicated = unsafe { DuplicateTokenEx( source.raw(), desired_access, null(), SecurityImpersonation, windows_sys::Win32::Security::TokenPrimary, &raw mut duplicate, ) }; if duplicated == FALSE { return Err(last_error("DuplicateTokenEx")); } OwnedHandle::new(duplicate) } fn apply_token_options( token: &OwnedHandle, privileges: PrivilegeMode, integrity: Option, ) -> Result<(), WindowsSupportError> { match privileges { PrivilegeMode::Default => {} PrivilegeMode::EnableAll => enable_token_privileges(token.raw())?, PrivilegeMode::DisableAll => disable_token_privileges(token.raw())?, } if let Some(integrity) = integrity { set_token_integrity(token.raw(), integrity)?; } Ok(()) } fn launch_with_token_as_user( request: &LaunchRequest, identity: LaunchIdentity, token: &OwnedHandle, ) -> Result { let desktop = interactive_desktop(request); let mut startup = build_startup_info(request.show_window, desktop.as_deref()); let std_handles = inherited_standard_handles(request)?; apply_standard_handles(&mut startup, std_handles.as_ref()); let mut process_info = PROCESS_INFORMATION::default(); let application = application_name_wide(&request.program); let command_line_text = build_process_command_line(&request.program, &request.args); let mut command_line = wide(&command_line_text); let environment = OwnedEnvironmentBlock::for_token(token.raw(), false)?; let current_directory = request.current_directory.as_deref().map(wide); let creation_flags = creation_flags(request); #[allow(unsafe_code)] let created = unsafe { CreateProcessAsUserW( token.raw(), application.as_ref().map_or(null(), Vec::as_ptr), command_line.as_mut_ptr(), null(), null(), inherit_handles_flag(std_handles.as_ref()), creation_flags, environment.raw().cast_mut(), current_directory.as_ref().map_or(null(), Vec::as_ptr), &raw mut startup, &raw mut process_info, ) }; if created == FALSE { return Err(last_error("CreateProcessAsUserW")); } finalize_launch(request, identity, process_info) } fn launch_with_active_user( request: &LaunchRequest, ) -> Result { let status = current_token_status()?; if !request_requires_token_shaping(request) { return launch_with_current_identity(request); } if !status.is_elevated { return Err(WindowsSupportError::Unsupported( "current-user launch requires an elevated administrator context".to_string(), )); } enable_token_discovery_privileges()?; let _impersonation = ImpersonationGuard::as_system_with_privileges(&[ "SeTcbPrivilege", "SeAssignPrimaryTokenPrivilege", "SeIncreaseQuotaPrivilege", "SeImpersonatePrivilege", ])?; let token = duplicate_primary_token(LaunchIdentity::CurrentUser, request.session)?; apply_token_options(&token, request.privileges, request.integrity)?; launch_with_token_as_user(request, LaunchIdentity::CurrentUser, &token) } const fn request_requires_token_shaping(request: &LaunchRequest) -> bool { request.session.is_some() || request.integrity.is_some() || !matches!(request.privileges, PrivilegeMode::Default) } fn finalize_launch( request: &LaunchRequest, identity: LaunchIdentity, process_info: PROCESS_INFORMATION, ) -> Result { let thread = OwnedHandle::new(process_info.hThread)?; drop(thread); let process = OwnedHandle::new(process_info.hProcess)?; let exit_code = if request.wait { let _console_ctrl = ConsoleCtrlGuard::ignore(request.same_console)?; Some(wait_for_exit_code(process.raw())?) } else { None }; Ok(LaunchResult { pid: Some(process_info.dwProcessId), exit_code, identity, }) } fn open_system_process_token() -> Result { open_preferred_system_process_token(TOKEN_QUERY | TOKEN_DUPLICATE) } fn open_system_impersonation_token() -> Result { let source = open_system_process_token()?; duplicate_token( source.raw(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_ADJUST_PRIVILEGES, windows_sys::Win32::Security::TokenImpersonation, ) } fn open_active_user_process_token( explicit_session: Option, ) -> Result { let session = explicit_session .or_else(active_console_session_id) .or_else(current_process_session_id) .ok_or_else(|| { WindowsSupportError::Process("no active user session found".to_string()) })?; let mut token: HANDLE = null_mut(); #[allow(unsafe_code)] let queried = unsafe { WTSQueryUserToken(session, &raw mut token) }; if queried == FALSE { return Err(last_error("WTSQueryUserToken")); } OwnedHandle::new(token) } fn open_preferred_system_process_token( access: TOKEN_ACCESS_MASK, ) -> Result { let expected = well_known_sid(WinLocalSystemSid)?; let processes = crate::snapshot_processes(); let current_session = current_process_session_id(); for process in processes .iter() .filter(|process| { process.image_name.eq_ignore_ascii_case("winlogon.exe") && current_session == process_session_id(process.pid) }) .chain( processes .iter() .filter(|process| process.image_name.eq_ignore_ascii_case("lsass.exe")), ) .chain(processes.iter()) { let opened = open_process_token_for_pid(process.pid, access); let Ok(token) = opened else { continue; }; if token_user_matches_sid(token.raw(), &expected)? { return Ok(token); } } Err(WindowsSupportError::Process( "failed to locate a usable SYSTEM process token".to_string(), )) } fn open_trustedinstaller_process_token() -> Result { let started = Instant::now(); let _impersonation = ImpersonationGuard::as_system()?; loop { let pid = ensure_trustedinstaller_running()?; match open_process_token_for_pid(pid, TOKEN_QUERY | TOKEN_DUPLICATE) { Ok(token) => return Ok(token), Err(error) => { if !should_retry_trustedinstaller_token_open(&error) || started.elapsed() >= START_TIMEOUT { return Err(error); } thread::sleep(Duration::from_millis(100)); } } } } fn duplicate_token( source: HANDLE, desired_access: TOKEN_ACCESS_MASK, token_type: windows_sys::Win32::Security::TOKEN_TYPE, ) -> Result { let mut duplicate: HANDLE = null_mut(); #[allow(unsafe_code)] let duplicated = unsafe { DuplicateTokenEx( source, desired_access, null(), SecurityImpersonation, token_type, &raw mut duplicate, ) }; if duplicated == FALSE { return Err(last_error("DuplicateTokenEx")); } OwnedHandle::new(duplicate) } fn open_process_token_for_pid( pid: u32, access: TOKEN_ACCESS_MASK, ) -> Result { #[allow(unsafe_code)] let process = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid) }; let process = OwnedHandle::new(process)?; let mut token: HANDLE = null_mut(); #[allow(unsafe_code)] let opened = unsafe { OpenProcessToken(process.raw(), access, &raw mut token) }; if opened == FALSE { return Err(last_error("OpenProcessToken")); } OwnedHandle::new(token) } fn process_session_id(pid: u32) -> Option { let mut session = 0_u32; #[allow(unsafe_code)] let ok = unsafe { ProcessIdToSessionId(pid, &raw mut session) }; (ok != FALSE).then_some(session) } fn should_retry_trustedinstaller_token_open(error: &WindowsSupportError) -> bool { matches!( error, WindowsSupportError::WindowsApi { action: "OpenProcessToken" | "OpenProcess", code: ERROR_ACCESS_DENIED, } ) } fn trustedinstaller_presence() -> Result<(bool, bool), WindowsSupportError> { let manager = unsafe { OpenSCManagerW(null(), null(), SC_MANAGER_CONNECT) }; let manager = match OwnedServiceHandle::new(manager, "OpenSCManagerW") { Ok(value) => value, Err(error) => { return match error { WindowsSupportError::WindowsApi { .. } => Ok((false, false)), other => Err(other), }; } }; let service_name = wide("TrustedInstaller"); let service = unsafe { OpenServiceW(manager.raw(), service_name.as_ptr(), SERVICE_QUERY_STATUS) }; let service = match OwnedServiceHandle::new(service, "OpenServiceW") { Ok(value) => value, Err(error) => { return match error { WindowsSupportError::WindowsApi { .. } => Ok((false, false)), other => Err(other), }; } }; let running = service_running(service.raw())?; Ok((true, running)) } fn ensure_trustedinstaller_running() -> Result { let manager = OwnedServiceHandle::new( unsafe { OpenSCManagerW(null(), null(), SC_MANAGER_CONNECT) }, "OpenSCManagerW", )?; let service_name = wide("TrustedInstaller"); let service = OwnedServiceHandle::new( unsafe { OpenServiceW( manager.raw(), service_name.as_ptr(), SERVICE_QUERY_STATUS | SERVICE_START, ) }, "OpenServiceW", )?; let mut deadline = Instant::now() + START_TIMEOUT; let mut last_checkpoint = 0_u32; let mut start_requested = false; loop { let status = query_service_status(service.raw())?; if status.dwCurrentState == SERVICE_RUNNING { return Ok(status.dwProcessId); } if status.dwCurrentState == SERVICE_START_PENDING { if status.dwCheckPoint != 0 && status.dwCheckPoint != last_checkpoint { last_checkpoint = status.dwCheckPoint; deadline = Instant::now() + START_TIMEOUT; } } else if !start_requested { #[allow(unsafe_code)] let _ = unsafe { StartServiceW(service.raw(), 0, null()) }; start_requested = true; } if Instant::now() >= deadline { return Err(WindowsSupportError::Process( "timed out waiting for TrustedInstaller service".to_string(), )); } let wait_hint = if status.dwWaitHint == 0 { 100 } else { status.dwWaitHint.saturating_div(10).clamp(100, 1_000) }; thread::sleep(Duration::from_millis(u64::from(wait_hint))); } } fn service_running(service: SC_HANDLE) -> Result { let status = query_service_status(service)?; Ok(status.dwCurrentState == SERVICE_RUNNING) } fn query_service_status( service: SC_HANDLE, ) -> Result { let mut status = SERVICE_STATUS_PROCESS::default(); let mut needed = 0_u32; #[allow(unsafe_code)] let queried = unsafe { QueryServiceStatusEx( service, SC_STATUS_PROCESS_INFO, (&raw mut status).cast::(), u32::try_from(size_of::()) .expect("service status size always fits in u32"), &raw mut needed, ) }; if queried == FALSE { return Err(last_error("QueryServiceStatusEx")); } Ok(status) } fn enable_token_discovery_privileges() -> Result<(), WindowsSupportError> { let token = open_current_token(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY)?; for privilege in ["SeDebugPrivilege", "SeImpersonatePrivilege"] { enable_named_privilege(token.raw(), privilege)?; } Ok(()) } fn enable_token_privileges(token: HANDLE) -> Result<(), WindowsSupportError> { let bytes = get_token_info_bytes(token, windows_sys::Win32::Security::TokenPrivileges)?; #[allow(unsafe_code)] let privilege_count = unsafe { std::ptr::read_unaligned(bytes.as_ptr().cast::()) }; let privileges_offset = std::mem::offset_of!(TOKEN_PRIVILEGES, Privileges); for index in 0..usize::try_from(privilege_count).expect("privilege count fits usize") { #[allow(unsafe_code)] let privilege = unsafe { std::ptr::read_unaligned( bytes .as_ptr() .add( privileges_offset + index * size_of::( ), ) .cast::(), ) }; adjust_single_token_privilege(token, privilege.Luid, SE_PRIVILEGE_ENABLED)?; } Ok(()) } fn disable_token_privileges(token: HANDLE) -> Result<(), WindowsSupportError> { #[allow(unsafe_code)] let adjusted = unsafe { AdjustTokenPrivileges(token, 1, null_mut(), 0, null_mut(), null_mut()) }; if adjusted == FALSE { return Err(last_error("AdjustTokenPrivileges")); } Ok(()) } fn enable_named_privilege(token: HANDLE, name: &str) -> Result<(), WindowsSupportError> { let name_wide = wide(name); let mut luid = windows_sys::Win32::Foundation::LUID::default(); #[allow(unsafe_code)] let looked_up = unsafe { LookupPrivilegeValueW(null(), name_wide.as_ptr(), &raw mut luid) }; if looked_up == FALSE { return Err(last_error("LookupPrivilegeValueW")); } adjust_single_token_privilege(token, luid, SE_PRIVILEGE_ENABLED) } fn adjust_single_token_privilege( token: HANDLE, luid: windows_sys::Win32::Foundation::LUID, attributes: u32, ) -> Result<(), WindowsSupportError> { let mut privileges = TOKEN_PRIVILEGES { PrivilegeCount: 1, Privileges: [windows_sys::Win32::Security::LUID_AND_ATTRIBUTES { Luid: luid, Attributes: attributes, }], }; #[allow(unsafe_code)] let adjusted = unsafe { AdjustTokenPrivileges( token, FALSE, &raw mut privileges, u32::try_from(size_of::()) .expect("token privileges size always fits in u32"), null_mut(), null_mut(), ) }; if adjusted == FALSE { return Err(last_error("AdjustTokenPrivileges")); } #[allow(unsafe_code)] let status = unsafe { GetLastError() }; if status == ERROR_NOT_ALL_ASSIGNED { return Err(WindowsSupportError::Process( "token did not contain every requested privilege".to_string(), )); } Ok(()) } fn open_current_token(access: TOKEN_ACCESS_MASK) -> Result { let mut token: HANDLE = null_mut(); #[allow(unsafe_code)] let opened = unsafe { OpenProcessToken(GetCurrentProcess(), access, &raw mut token) }; if opened == FALSE { return Err(last_error("OpenProcessToken")); } OwnedHandle::new(token) } fn token_elevation(token: HANDLE) -> Result { let elevation: TOKEN_ELEVATION = get_token_info(token, TokenElevation)?; Ok(elevation.TokenIsElevated != 0) } fn token_integrity(token: HANDLE) -> Result { let bytes = get_token_info_bytes(token, TokenIntegrityLevel)?; let label: TOKEN_MANDATORY_LABEL = unsafe { std::ptr::read_unaligned(bytes.as_ptr().cast::()) }; if label.Label.Attributes & u32::try_from(SE_GROUP_INTEGRITY).expect("integrity flag fits u32") == 0 { return Ok(TokenIntegrity::Unknown); } let sid = label.Label.Sid; let authority = sub_authority_count(sid)?; let rid = sub_authority(sid, authority.saturating_sub(1))?; Ok(match rid { SECURITY_MANDATORY_UNTRUSTED_RID => TokenIntegrity::Untrusted, SECURITY_MANDATORY_LOW_RID => TokenIntegrity::Low, SECURITY_MANDATORY_MEDIUM_RID => TokenIntegrity::Medium, SECURITY_MANDATORY_MEDIUM_PLUS_RID => TokenIntegrity::MediumPlus, SECURITY_MANDATORY_HIGH_RID => TokenIntegrity::High, SECURITY_MANDATORY_SYSTEM_RID => TokenIntegrity::System, _ => TokenIntegrity::Unknown, }) } fn set_token_integrity( token: HANDLE, integrity: TokenIntegrity, ) -> Result<(), WindowsSupportError> { let Some(rid) = integrity_rid(integrity) else { return Ok(()); }; let mut sid = mandatory_label_sid(rid); let mut label = TOKEN_MANDATORY_LABEL { Label: windows_sys::Win32::Security::SID_AND_ATTRIBUTES { Sid: sid.as_mut_ptr().cast::(), Attributes: u32::try_from(SE_GROUP_INTEGRITY).expect("integrity flag fits u32"), }, }; #[allow(unsafe_code)] let updated = unsafe { SetTokenInformation( token, TokenIntegrityLevel, (&raw mut label).cast::(), u32::try_from(size_of::() + sid.len()) .expect("mandatory label buffer size fits in u32"), ) }; if updated == FALSE { return Err(last_error("SetTokenInformation")); } Ok(()) } const fn integrity_rid(integrity: TokenIntegrity) -> Option { match integrity { TokenIntegrity::Unknown => None, TokenIntegrity::Untrusted => Some(SECURITY_MANDATORY_UNTRUSTED_RID), TokenIntegrity::Low => Some(SECURITY_MANDATORY_LOW_RID), TokenIntegrity::Medium => Some(SECURITY_MANDATORY_MEDIUM_RID), TokenIntegrity::MediumPlus => Some(SECURITY_MANDATORY_MEDIUM_PLUS_RID), TokenIntegrity::High => Some(SECURITY_MANDATORY_HIGH_RID), TokenIntegrity::System => Some(SECURITY_MANDATORY_SYSTEM_RID), } } fn mandatory_label_sid(rid: u32) -> [u8; 12] { let mut sid = [0_u8; 12]; sid[0] = 1; sid[1] = 1; sid[7] = 16; sid[8..12].copy_from_slice(&rid.to_le_bytes()); sid } fn token_user_name(token: HANDLE) -> Result, WindowsSupportError> { let bytes = get_token_info_bytes(token, windows_sys::Win32::Security::TokenUser)?; let user: windows_sys::Win32::Security::TOKEN_USER = unsafe { std::ptr::read_unaligned( bytes .as_ptr() .cast::(), ) }; let sid = user.User.Sid; let mut name_len = 0_u32; let mut domain_len = 0_u32; let mut use_type = 0_i32; #[allow(unsafe_code)] let _ = unsafe { LookupAccountSidW( null(), sid, null_mut(), &raw mut name_len, null_mut(), &raw mut domain_len, &raw mut use_type, ) }; if name_len == 0 { return Ok(None); } let mut name = vec![0_u16; usize::try_from(name_len).expect("name length fits usize")]; let mut domain = vec![0_u16; usize::try_from(domain_len).expect("domain length fits usize")]; #[allow(unsafe_code)] let looked_up = unsafe { LookupAccountSidW( null(), sid, name.as_mut_ptr(), &raw mut name_len, domain.as_mut_ptr(), &raw mut domain_len, &raw mut use_type, ) }; if looked_up == FALSE { return Ok(None); } let name = wide_to_string(&name[..usize::try_from(name_len).expect("name length fits usize")]); let domain = wide_to_string( &domain[..usize::try_from(domain_len).expect("domain length fits usize")], ); if domain.is_empty() { Ok(Some(name)) } else { Ok(Some(format!("{domain}\\{name}"))) } } fn current_token_is_admin_member() -> Result { let sid = well_known_sid(WinBuiltinAdministratorsSid)?; let mut result = FALSE; #[allow(unsafe_code)] let checked = unsafe { CheckTokenMembership( null_mut(), sid.as_ptr().cast_mut().cast::(), &raw mut result, ) }; if checked == FALSE { return Err(last_error("CheckTokenMembership")); } Ok(result != FALSE) } fn current_process_session_id() -> Option { let mut session = 0_u32; #[allow(unsafe_code)] let ok = unsafe { ProcessIdToSessionId(GetCurrentProcessId(), &raw mut session) }; (ok != FALSE).then_some(session) } fn active_console_session_id() -> Option { #[allow(unsafe_code)] let session = unsafe { WTSGetActiveConsoleSessionId() }; (session != u32::MAX).then_some(session) } fn resolve_target_session_id(explicit_session: Option) -> Option { explicit_session .or_else(|| current_process_session_id().filter(|session| *session != 0)) .or_else(active_console_session_id) .or_else(current_process_session_id) } fn token_user_matches_sid(token: HANDLE, expected: &[u8]) -> Result { let bytes = get_token_info_bytes(token, windows_sys::Win32::Security::TokenUser)?; let user: windows_sys::Win32::Security::TOKEN_USER = unsafe { std::ptr::read_unaligned( bytes .as_ptr() .cast::(), ) }; sid_equal(user.User.Sid, expected.as_ptr().cast::()) } fn sid_equal(left: *mut c_void, right: *const c_void) -> Result { #[allow(unsafe_code)] let left_len = unsafe { windows_sys::Win32::Security::GetLengthSid(left) }; #[allow(unsafe_code)] let right_len = unsafe { windows_sys::Win32::Security::GetLengthSid(right.cast_mut()) }; if left_len != right_len { return Ok(false); } let left = sid_bytes(left, left_len)?; let right = sid_bytes(right.cast_mut(), right_len)?; Ok(left == right) } fn sid_bytes(sid: *mut c_void, len: u32) -> Result, WindowsSupportError> { let len = usize::try_from(len).expect("sid length fits usize"); if sid.is_null() { return Err(WindowsSupportError::Process("null SID pointer".to_string())); } #[allow(unsafe_code)] let slice = unsafe { std::slice::from_raw_parts(sid.cast::(), len) }; Ok(slice.to_vec()) } fn well_known_sid(kind: i32) -> Result, WindowsSupportError> { let mut bytes = vec![0_u8; SECURITY_MAX_SID_SIZE]; let mut size = u32::try_from(bytes.len()).expect("sid buffer size fits u32"); #[allow(unsafe_code)] let created = unsafe { CreateWellKnownSid( kind, null_mut(), bytes.as_mut_ptr().cast::(), &raw mut size, ) }; if created == FALSE { return Err(last_error("CreateWellKnownSid")); } bytes.truncate(usize::try_from(size).expect("sid size fits usize")); Ok(bytes) } fn sub_authority_count(sid: *mut c_void) -> Result { if sid.is_null() { return Err(WindowsSupportError::Process("null SID pointer".to_string())); } #[allow(unsafe_code)] let count = unsafe { *(sid.cast::().add(1)) }; Ok(u32::from(count)) } fn sub_authority(sid: *mut c_void, index: u32) -> Result { if sid.is_null() { return Err(WindowsSupportError::Process("null SID pointer".to_string())); } let offset = 8_usize + usize::try_from(index).expect("sid index fits usize") * 4; #[allow(unsafe_code)] let value = unsafe { std::ptr::read_unaligned(sid.cast::().add(offset).cast::()) }; Ok(value) } fn set_token_session_id(token: HANDLE, session: u32) -> Result<(), WindowsSupportError> { let mut session = session; #[allow(unsafe_code)] let updated = unsafe { SetTokenInformation( token, TokenSessionId, (&raw mut session).cast::(), u32::try_from(size_of::()).expect("u32 size always fits in u32"), ) }; if updated == FALSE { return Err(last_error("SetTokenInformation")); } Ok(()) } fn interactive_desktop(request: &LaunchRequest) -> Option> { if request.new_window || request.same_console { Some(wide("winsta0\\default")) } else { None } } fn build_startup_info(show_window: ShowWindowMode, desktop: Option<&[u16]>) -> STARTUPINFOW { STARTUPINFOW { cb: u32::try_from(size_of::()) .expect("startup info size always fits in u32"), dwFlags: STARTF_USESHOWWINDOW, wShowWindow: show_window_value(show_window), lpDesktop: desktop.map_or(null_mut(), |value| value.as_ptr().cast_mut()), ..STARTUPINFOW::default() } } fn inherited_standard_handles( request: &LaunchRequest, ) -> Result, WindowsSupportError> { if request.new_window { return Ok(None); } let (output, stdout_file) = if let Some(path) = &request.stdout_path { let file = create_relay_output_file(path)?; ( duplicate_inheritable_handle(file.as_raw_handle().cast::(), "stdout file")?, Some(file), ) } else { (duplicate_standard_handle(STD_OUTPUT_HANDLE)?, None) }; let (error, stderr_file) = if let Some(path) = &request.stderr_path { let file = create_relay_output_file(path)?; ( duplicate_inheritable_handle(file.as_raw_handle().cast::(), "stderr file")?, Some(file), ) } else { (duplicate_standard_handle(STD_ERROR_HANDLE)?, None) }; Ok(Some(InheritedStdHandles { input: duplicate_standard_handle(STD_INPUT_HANDLE)?, output, error, _stdout_file: stdout_file, _stderr_file: stderr_file, })) } fn duplicate_standard_handle( handle_id: STD_HANDLE, ) -> Result { #[allow(unsafe_code)] let source = unsafe { GetStdHandle(handle_id) }; if source.is_null() || std::ptr::eq(source, INVALID_HANDLE_VALUE) { return Err(last_error("GetStdHandle")); } duplicate_inheritable_handle(source, "standard handle") } fn create_relay_output_file(path: &str) -> Result { OpenOptions::new() .create_new(true) .write(true) .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT) .open(path) .map_err(|error| { WindowsSupportError::Process(format!( "failed to create relay output file {path}: {error}" )) }) } fn duplicate_inheritable_handle( source: HANDLE, action: &'static str, ) -> Result { #[allow(unsafe_code)] let current_process = unsafe { GetCurrentProcess() }; let mut duplicate: HANDLE = null_mut(); #[allow(unsafe_code)] let duplicated = unsafe { DuplicateHandle( current_process, source, current_process, &raw mut duplicate, 0, TRUE, DUPLICATE_SAME_ACCESS, ) }; if duplicated == FALSE { return Err(last_error(action)); } OwnedHandle::new(duplicate) } const fn apply_standard_handles( startup: &mut STARTUPINFOW, handles: Option<&InheritedStdHandles>, ) { if let Some(handles) = handles { startup.dwFlags |= STARTF_USESTDHANDLES; startup.hStdInput = handles.input.raw(); startup.hStdOutput = handles.output.raw(); startup.hStdError = handles.error.raw(); } } const fn inherit_handles_flag(handles: Option<&InheritedStdHandles>) -> i32 { if handles.is_some() { TRUE } else { FALSE } } const fn creation_flags(request: &LaunchRequest) -> u32 { let mut flags = priority_flag(request.priority) | CREATE_UNICODE_ENVIRONMENT; if request.new_window { flags |= CREATE_NEW_CONSOLE; } else if matches!(request.show_window, ShowWindowMode::Hidden) { flags |= CREATE_NO_WINDOW; } flags } const fn priority_flag(priority: ProcessPriority) -> u32 { match priority { ProcessPriority::Idle => IDLE_PRIORITY_CLASS, ProcessPriority::BelowNormal => BELOW_NORMAL_PRIORITY_CLASS, ProcessPriority::Normal => NORMAL_PRIORITY_CLASS, ProcessPriority::AboveNormal => ABOVE_NORMAL_PRIORITY_CLASS, ProcessPriority::High => HIGH_PRIORITY_CLASS, ProcessPriority::Realtime => REALTIME_PRIORITY_CLASS, } } const fn show_window_value(mode: ShowWindowMode) -> u16 { match mode { ShowWindowMode::Default => SW_SHOWDEFAULT_VALUE, ShowWindowMode::Hidden => SW_HIDE_VALUE, ShowWindowMode::Normal => SW_NORMAL_VALUE, ShowWindowMode::Minimized => SW_SHOWMINIMIZED_VALUE, ShowWindowMode::Maximized => SW_SHOWMAXIMIZED_VALUE, } } fn build_process_command_line(program: &str, args: &[String]) -> String { let mut values = Vec::with_capacity(args.len() + 1); values.push(program); values.extend(args.iter().map(String::as_str)); quote_command_line(values) } fn application_name_wide(program: &str) -> Option> { if should_use_path_search(program) { None } else { Some(wide(program)) } } fn should_use_path_search(program: &str) -> bool { !program.contains(['\\', '/', ':']) } fn quote_command_line<'a, I>(values: I) -> String where I: IntoIterator, { let mut rendered = String::new(); for value in values { if !rendered.is_empty() { rendered.push(' '); } rendered.push_str("e_windows_arg(value)); } rendered } fn quote_windows_arg(value: &str) -> String { if value.is_empty() { return "\"\"".to_string(); } if !value.contains([' ', '\t', '"']) && !value.ends_with('\\') { return value.to_string(); } let mut rendered = String::with_capacity(value.len() + 2); rendered.push('"'); let mut backslashes = 0; for character in value.chars() { if character == '\\' { backslashes += 1; continue; } if character == '"' { rendered.push_str(&"\\".repeat(backslashes * 2 + 1)); rendered.push('"'); backslashes = 0; continue; } if backslashes > 0 { rendered.push_str(&"\\".repeat(backslashes)); backslashes = 0; } rendered.push(character); } if backslashes > 0 { rendered.push_str(&"\\".repeat(backslashes * 2)); } rendered.push('"'); rendered } fn build_environment_block() -> Vec { let mut pairs = std::env::vars_os() .map(|(name, value)| { let mut rendered = String::new(); rendered.push_str(&name.to_string_lossy()); rendered.push('='); rendered.push_str(&value.to_string_lossy()); rendered }) .collect::>(); pairs.sort_unstable(); let mut buffer = Vec::new(); for pair in pairs { buffer.extend(OsStr::new(&pair).encode_wide()); buffer.push(0); } buffer.push(0); buffer } fn process_id_from_handle(process: HANDLE) -> Result { #[allow(unsafe_code)] let pid = unsafe { windows_sys::Win32::System::Threading::GetProcessId(process) }; if pid == 0 { Err(last_error("GetProcessId")) } else { Ok(pid) } } fn wait_for_exit_code(process: HANDLE) -> Result { #[allow(unsafe_code)] let waited = unsafe { WaitForSingleObject(process, u32::MAX) }; if waited != WAIT_OBJECT_0 { return if waited == WAIT_FAILED { Err(last_error("WaitForSingleObject")) } else { Err(WindowsSupportError::Process(format!( "unexpected wait result: {waited}" ))) }; } let mut code = 0_u32; #[allow(unsafe_code)] let ok = unsafe { GetExitCodeProcess(process, &raw mut code) }; if ok == FALSE { return Err(last_error("GetExitCodeProcess")); } Ok(i32::try_from(code).unwrap_or(i32::MAX)) } fn get_token_info( token: HANDLE, class: windows_sys::Win32::Security::TOKEN_INFORMATION_CLASS, ) -> Result { let bytes = get_token_info_bytes(token, class)?; #[allow(unsafe_code)] let value = unsafe { *(bytes.as_ptr().cast::()) }; Ok(value) } fn get_token_info_bytes( token: HANDLE, class: windows_sys::Win32::Security::TOKEN_INFORMATION_CLASS, ) -> Result, WindowsSupportError> { let mut needed = 0_u32; #[allow(unsafe_code)] let _ = unsafe { GetTokenInformation(token, class, null_mut(), 0, &raw mut needed) }; if needed == 0 { return Err(last_error("GetTokenInformation")); } let mut bytes = vec![0_u8; usize::try_from(needed).expect("token info length fits usize")]; #[allow(unsafe_code)] let ok = unsafe { GetTokenInformation( token, class, bytes.as_mut_ptr().cast::(), needed, &raw mut needed, ) }; if ok == FALSE { return Err(last_error("GetTokenInformation")); } Ok(bytes) } fn wide(value: &str) -> Vec { let mut wide = OsStr::new(value).encode_wide().collect::>(); wide.push(0); wide } fn wide_from_os(value: &OsStr) -> Vec { let mut wide = value.encode_wide().collect::>(); wide.push(0); wide } fn wide_to_string(value: &[u16]) -> String { let terminator = value .iter() .position(|item| *item == 0) .unwrap_or(value.len()); String::from_utf16_lossy(&value[..terminator]) } fn last_error(action: &'static str) -> WindowsSupportError { #[allow(unsafe_code)] let code = unsafe { GetLastError() }; WindowsSupportError::WindowsApi { action, code } } #[cfg(test)] mod tests { use super::{ CREATE_NEW_CONSOLE, CREATE_NO_WINDOW, ERROR_ACCESS_DENIED, ProcessPriority, ShowWindowMode, TokenIntegrity, admin_launch_requires_duplicate_token, application_name_wide, build_environment_block, build_process_command_line, build_startup_info, create_relay_output_file, creation_flags, integrity_rid, interactive_desktop, launch_request, mandatory_label_sid, priority_flag, quote_command_line, quote_windows_arg, resolve_target_session_id, should_retry_trustedinstaller_token_open, should_use_path_search, show_window_value, wide, wide_from_os, wide_to_string, }; use std::ffi::OsStr; use std::fs; use crate::WindowsSupportError; use crate::sudo::{LaunchIdentity, LaunchRequest, PrivilegeMode}; #[test] fn windows_arg_quoting_handles_spaces_quotes_and_trailing_backslashes() { assert_eq!(quote_windows_arg("plain"), "plain"); assert_eq!(quote_windows_arg("two words"), "\"two words\""); assert_eq!(quote_windows_arg("a\"b"), "\"a\\\"b\""); assert_eq!(quote_windows_arg("c:\\temp\\"), "\"c:\\temp\\\\\""); } #[test] fn command_line_builder_includes_program_first() { let command = build_process_command_line("tool.exe", &["two words".to_string()]); assert_eq!(command, "tool.exe \"two words\""); assert_eq!( quote_command_line(["", "tab\tvalue", "simple"]), "\"\" \"tab\tvalue\" simple" ); } #[test] fn bare_commands_use_windows_path_search() { assert!(should_use_path_search("cmd")); assert!(should_use_path_search("pwsh.exe")); assert!(application_name_wide("cmd").is_none()); assert!(application_name_wide("pwsh.exe").is_none()); assert!(application_name_wide("C:\\Windows\\System32\\cmd.exe").is_some()); assert!(application_name_wide(".\\tool.exe").is_some()); } #[test] fn enum_mappings_cover_priority_and_show_window() { assert!(priority_flag(ProcessPriority::Idle) > 0); assert!(priority_flag(ProcessPriority::BelowNormal) > 0); assert!(priority_flag(ProcessPriority::Normal) > 0); assert!(priority_flag(ProcessPriority::AboveNormal) > 0); assert!(priority_flag(ProcessPriority::High) > 0); assert!(priority_flag(ProcessPriority::Realtime) > 0); assert_eq!(show_window_value(ShowWindowMode::Default), 10); assert_eq!(show_window_value(ShowWindowMode::Hidden), 0); assert_eq!(show_window_value(ShowWindowMode::Normal), 1); assert_eq!(show_window_value(ShowWindowMode::Minimized), 2); assert_eq!(show_window_value(ShowWindowMode::Maximized), 3); } #[test] fn token_integrity_debug_shape_is_stable() { assert_eq!(format!("{:?}", TokenIntegrity::MediumPlus), "MediumPlus"); } #[test] fn trustedinstaller_retry_only_handles_access_denied_token_open() { let retryable = WindowsSupportError::WindowsApi { action: "OpenProcessToken", code: ERROR_ACCESS_DENIED, }; let retryable_process = WindowsSupportError::WindowsApi { action: "OpenProcess", code: ERROR_ACCESS_DENIED, }; assert!(should_retry_trustedinstaller_token_open(&retryable)); assert!(should_retry_trustedinstaller_token_open(&retryable_process)); } #[test] fn interactive_launches_use_winsta0_default_desktop() { let request = LaunchRequest { program: "cmd.exe".to_string(), args: Vec::new(), current_directory: None, stdout_path: None, stderr_path: None, identity: LaunchIdentity::TrustedInstaller, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::Normal, show_window: ShowWindowMode::Default, session: None, same_console: false, new_window: true, wait: false, }; let desktop = interactive_desktop(&request).expect("interactive desktop"); let startup = build_startup_info(request.show_window, Some(&desktop)); assert_eq!(wide_to_string(&desktop), "winsta0\\default"); assert!(!startup.lpDesktop.is_null()); } #[test] fn same_console_launches_use_winsta0_default_desktop() { let request = LaunchRequest { program: "cmd.exe".to_string(), args: Vec::new(), current_directory: None, stdout_path: None, stderr_path: None, identity: LaunchIdentity::System, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::Normal, show_window: ShowWindowMode::Default, session: None, same_console: true, new_window: false, wait: true, }; let desktop = interactive_desktop(&request).expect("interactive desktop"); assert_eq!(wide_to_string(&desktop), "winsta0\\default"); } #[test] fn hidden_new_window_does_not_request_no_window_flag() { let request = LaunchRequest { program: "cmd.exe".to_string(), args: Vec::new(), current_directory: None, stdout_path: None, stderr_path: None, identity: LaunchIdentity::Admin, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::Normal, show_window: ShowWindowMode::Hidden, session: None, same_console: false, new_window: true, wait: false, }; let flags = creation_flags(&request); assert_ne!(flags & CREATE_NEW_CONSOLE, 0); assert_eq!(flags & CREATE_NO_WINDOW, 0); } #[test] fn explicit_session_wins_session_resolution() { assert_eq!(resolve_target_session_id(Some(42)), Some(42)); } #[test] fn creation_flags_cover_window_and_priority_combinations() { let mut request = LaunchRequest { program: "cmd.exe".to_string(), args: Vec::new(), current_directory: None, stdout_path: None, stderr_path: None, identity: LaunchIdentity::CurrentProcess, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::High, show_window: ShowWindowMode::Hidden, session: None, same_console: false, new_window: false, wait: false, }; let hidden_same_window = creation_flags(&request); assert_ne!(hidden_same_window & CREATE_NO_WINDOW, 0); assert_eq!(hidden_same_window & CREATE_NEW_CONSOLE, 0); request.new_window = true; let hidden_new_window = creation_flags(&request); assert_ne!(hidden_new_window & CREATE_NEW_CONSOLE, 0); assert_eq!(hidden_new_window & CREATE_NO_WINDOW, 0); } #[test] fn wide_and_environment_helpers_emit_null_terminated_buffers() { let wide = wide("Mercury"); assert_eq!(wide_to_string(&wide), "Mercury"); assert_eq!(wide.last(), Some(&0)); let from_os = wide_from_os(OsStr::new("Toolbox")); assert_eq!(wide_to_string(&from_os), "Toolbox"); assert_eq!(from_os.last(), Some(&0)); let environment = build_environment_block(); assert!(environment.ends_with(&[0, 0])); } #[test] fn relay_output_file_refuses_existing_replacements() { let path = std::env::temp_dir().join(format!( "windowsupport-relay-output-{}.txt", std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("epoch") .as_nanos() )); fs::write(&path, "attacker").expect("replacement"); let error = create_relay_output_file(&path.display().to_string()) .expect_err("existing replacement should be refused"); assert!( error .to_string() .contains("failed to create relay output file") ); assert_eq!( fs::read_to_string(&path).expect("replacement remains"), "attacker" ); let _ = fs::remove_file(path); } #[cfg(windows)] #[test] fn current_process_launch_waits_for_child_exit_code() { let request = LaunchRequest { program: "cmd.exe".to_string(), args: vec![ "/D".to_string(), "/S".to_string(), "/C".to_string(), "exit /b 7".to_string(), ], current_directory: None, stdout_path: None, stderr_path: None, identity: LaunchIdentity::CurrentProcess, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::Normal, show_window: ShowWindowMode::Hidden, session: None, same_console: false, new_window: true, wait: true, }; let result = launch_request(&request).expect("launch current process"); assert_eq!(result.identity, LaunchIdentity::CurrentProcess); assert_eq!(result.exit_code, Some(7)); assert!(result.pid.is_some()); } #[cfg(windows)] #[test] fn current_process_launch_can_relay_stdout_to_exclusive_file() { let path = std::env::temp_dir().join(format!( "windowsupport-relay-capture-{}.txt", std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("epoch") .as_nanos() )); let request = LaunchRequest { program: "cmd.exe".to_string(), args: vec![ "/D".to_string(), "/S".to_string(), "/C".to_string(), "echo MercuryStdout".to_string(), ], current_directory: None, stdout_path: Some(path.display().to_string()), stderr_path: None, identity: LaunchIdentity::CurrentProcess, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::Normal, show_window: ShowWindowMode::Hidden, session: None, same_console: false, new_window: false, wait: true, }; let result = launch_request(&request).expect("launch with stdout relay"); assert_eq!(result.exit_code, Some(0)); assert!( fs::read_to_string(&path) .expect("relay output") .contains("MercuryStdout") ); let _ = fs::remove_file(path); } #[cfg(windows)] #[test] fn current_user_launch_without_token_shaping_uses_current_identity_path() { let status = super::current_token_status().expect("token status"); assert!( status .current_user .as_deref() .is_some_and(|user| !user.is_empty()) ); let request = LaunchRequest { program: "cmd.exe".to_string(), args: vec![ "/D".to_string(), "/S".to_string(), "/C".to_string(), "exit /b 0".to_string(), ], current_directory: None, stdout_path: None, stderr_path: None, identity: LaunchIdentity::CurrentUser, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::Normal, show_window: ShowWindowMode::Hidden, session: None, same_console: false, new_window: true, wait: true, }; let result = launch_request(&request).expect("launch current user"); assert_eq!(result.identity, LaunchIdentity::CurrentUser); assert_eq!(result.exit_code, Some(0)); } #[test] fn admin_launch_requires_duplicate_token_for_token_mutations() { let request = LaunchRequest { program: "cmd.exe".to_string(), args: Vec::new(), current_directory: None, stdout_path: None, stderr_path: None, identity: LaunchIdentity::Admin, privileges: PrivilegeMode::Default, integrity: None, priority: ProcessPriority::Normal, show_window: ShowWindowMode::Default, session: None, same_console: false, new_window: false, wait: false, }; assert!(!admin_launch_requires_duplicate_token(&request)); let mut integrity = request.clone(); integrity.integrity = Some(TokenIntegrity::High); assert!(admin_launch_requires_duplicate_token(&integrity)); let mut privileges = request.clone(); privileges.privileges = PrivilegeMode::DisableAll; assert!(admin_launch_requires_duplicate_token(&privileges)); let mut session = request; session.session = Some(1); assert!(admin_launch_requires_duplicate_token(&session)); } #[test] fn mandatory_label_sid_matches_integrity_authority_shape() { let rid = integrity_rid(TokenIntegrity::MediumPlus).expect("rid"); let sid = mandatory_label_sid(rid); assert_eq!(sid[0], 1); assert_eq!(sid[1], 1); assert_eq!(sid[2..8], [0, 0, 0, 0, 0, 16]); assert_eq!(u32::from_le_bytes([sid[8], sid[9], sid[10], sid[11]]), rid); } } } #[cfg(windows)] pub use imp::{ attach_parent_console, current_token_status, elevate_current_process, launch_request, };