From 7b6cfccd71c04a2ee87d6665b6822ccfe6d389b5 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 13 Dec 2023 18:37:34 +0200 Subject: [PATCH] feat(process): add `FindProcessCurrentUser` and `KillProcessCurrentUser` (#24) Co-authored-by: FabianLars-crabnebula --- .changes/process-currentuser.md | 6 ++ Cargo.toml | 1 + crates/nsis-download/Cargo.toml | 24 ++--- crates/nsis-process/Cargo.toml | 20 ++-- crates/nsis-process/src/lib.rs | 143 ++++++++++++++++++++++++++- crates/nsis-semvercompare/Cargo.toml | 22 ++--- crates/nsis-tauri-utils/Cargo.toml | 14 +-- crates/pluginapi/Cargo.toml | 2 +- 8 files changed, 169 insertions(+), 63 deletions(-) create mode 100644 .changes/process-currentuser.md diff --git a/.changes/process-currentuser.md b/.changes/process-currentuser.md new file mode 100644 index 0000000..e625a00 --- /dev/null +++ b/.changes/process-currentuser.md @@ -0,0 +1,6 @@ +--- +"nsis_tauri_utils": "patch" +"nsis_process": "patch" +--- + +Add `FindProcessCurrentUser` and `KillProcessCurrentUser`. diff --git a/Cargo.toml b/Cargo.toml index ec1e4bc..a299928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ version = "0.52.0" features = [ "Win32_Foundation", "Win32_Globalization", + "Win32_Security", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Memory", "Win32_System_Threading", diff --git a/crates/nsis-download/Cargo.toml b/crates/nsis-download/Cargo.toml index f1fb45f..e26069e 100644 --- a/crates/nsis-download/Cargo.toml +++ b/crates/nsis-download/Cargo.toml @@ -1,25 +1,15 @@ [package] name = "nsis-download" version = "0.3.0" - - [package.authors] - workspace = true - - [package.edition] - workspace = true - - [package.license] - workspace = true +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [lib] -crate-type = [ "rlib", "cdylib" ] +crate-type = ["rlib", "cdylib"] [dependencies] -ureq = { version = "2", default-features = false, features = [ "tls" ] } +ureq = { version = "2", default-features = false, features = ["tls"] } progress-streams = "1.1" - - [dependencies.pluginapi] - workspace = true - - [dependencies.windows-sys] - workspace = true +pluginapi = { workspace = true } +windows-sys = { workspace = true } diff --git a/crates/nsis-process/Cargo.toml b/crates/nsis-process/Cargo.toml index 4786ac3..ec9773b 100644 --- a/crates/nsis-process/Cargo.toml +++ b/crates/nsis-process/Cargo.toml @@ -1,21 +1,13 @@ [package] name = "nsis-process" version = "0.2.1" - - [package.authors] - workspace = true - - [package.edition] - workspace = true - - [package.license] - workspace = true +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [lib] -crate-type = [ "rlib", "cdylib" ] +crate-type = ["rlib", "cdylib"] [dependencies] -pluginapi = { path = "../pluginapi" } - - [dependencies.windows-sys] - workspace = true +pluginapi = { workspace = true } +windows-sys = { workspace = true } diff --git a/crates/nsis-process/src/lib.rs b/crates/nsis-process/src/lib.rs index b1a0a4b..e855f8d 100644 --- a/crates/nsis-process/src/lib.rs +++ b/crates/nsis-process/src/lib.rs @@ -1,15 +1,19 @@ -use std::mem::size_of; +use std::{ffi::c_void, mem, ptr}; use pluginapi::{decode_wide, exdll_init, popstring, pushint, stack_t, wchar_t}; use windows_sys::Win32::{ - Foundation::{CloseHandle, HWND}, + Foundation::{CloseHandle, HANDLE, HWND}, + Security::{EqualSid, GetTokenInformation, TokenUser, TOKEN_QUERY, TOKEN_USER}, System::{ Diagnostics::ToolHelp::{ CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS, }, - Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE}, + Threading::{ + OpenProcess, OpenProcessToken, TerminateProcess, PROCESS_QUERY_INFORMATION, + PROCESS_TERMINATE, + }, }, }; @@ -36,6 +40,41 @@ pub unsafe extern "C" fn FindProcess( } } +/// Test if there is a running process with the given name that belongs to the current user, skipping processes with the host's pid. The input and process names are case-insensitive. +/// +/// # Safety +/// +/// This function always expects 1 string on the stack ($1: name) and will panic otherwise. +#[no_mangle] +pub unsafe extern "C" fn FindProcessCurrentUser( + _hwnd_parent: HWND, + string_size: u32, + variables: *mut wchar_t, + stacktop: *mut *mut stack_t, +) { + exdll_init(string_size, variables, stacktop); + + let name = popstring().unwrap(); + + let processes = get_processes(&name); + + if let Some(user_sid) = get_sid(std::process::id()) { + if processes + .into_iter() + .any(|pid| belongs_to_user(user_sid, pid)) + { + pushint(0); + } else { + pushint(1); + } + // Fall back to perMachine checks if we can't get current user id + } else if processes.is_empty() { + pushint(1); + } else { + pushint(0); + } +} + /// Kill all running process with the given name, skipping processes with the host's pid. The input and process names are case-insensitive. /// /// # Safety @@ -61,6 +100,55 @@ pub unsafe extern "C" fn KillProcess( } } +/// Kill all running process with the given name that belong to the current user, skipping processes with the host's pid. The input and process names are case-insensitive. +/// +/// # Safety +/// +/// This function always expects 1 string on the stack ($1: name) and will panic otherwise. +#[no_mangle] +pub unsafe extern "C" fn KillProcessCurrentUser( + _hwnd_parent: HWND, + string_size: u32, + variables: *mut wchar_t, + stacktop: *mut *mut stack_t, +) { + exdll_init(string_size, variables, stacktop); + + let name = popstring().unwrap(); + + let processes = get_processes(&name); + + if processes.is_empty() { + pushint(1); + return; + } + + let success = if let Some(user_sid) = get_sid(std::process::id()) { + processes + .into_iter() + .filter(|pid| belongs_to_user(user_sid, *pid)) + .map(kill) + .all(|b| b) + } else { + processes.into_iter().map(kill).all(|b| b) + }; + + if success { + pushint(0) + } else { + pushint(1) + } +} + +unsafe fn belongs_to_user(user_sid: *mut c_void, pid: u32) -> bool { + let p_sid = get_sid(pid); + // Trying to get the sid of a process of another user will give us an "Access Denied" error. + // TODO: Consider checking for HRESULT(0x80070005) if we want to return true for other errors to try and kill those processes later. + p_sid + .map(|p_sid| EqualSid(user_sid, p_sid) != 0) + .unwrap_or_default() +} + fn kill(pid: u32) -> bool { unsafe { let handle = OpenProcess(PROCESS_TERMINATE, 0, pid); @@ -70,6 +158,51 @@ fn kill(pid: u32) -> bool { } } +// Get the SID of a process. Returns None on error. +unsafe fn get_sid(pid: u32) -> Option<*mut c_void> { + let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid); + + let mut sid = None; + let mut token_handle = HANDLE::default(); + + if OpenProcessToken(handle, TOKEN_QUERY, &mut token_handle) != 0 { + let mut info_length = 0; + + GetTokenInformation( + token_handle, + TokenUser, + ptr::null_mut(), + 0, + &mut info_length as *mut u32, + ); + + // GetTokenInformation always returns 0 for the first call so we check if it still gave us the buffer length + if info_length == 0 { + return sid; + } + + let info = vec![0u8; info_length as usize].as_mut_ptr() as *mut TOKEN_USER; + + if GetTokenInformation( + token_handle, + TokenUser, + info as *mut c_void, + info_length, + &mut info_length, + ) == 0 + { + return sid; + } + + sid = Some((*info).User.Sid) + } + + CloseHandle(token_handle); + CloseHandle(handle); + + sid +} + fn get_processes(name: &str) -> Vec { let current_pid = std::process::id(); let mut processes = Vec::new(); @@ -78,8 +211,8 @@ fn get_processes(name: &str) -> Vec { let handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); let mut process = PROCESSENTRY32W { - dwSize: size_of::() as u32, - ..std::mem::zeroed() + dwSize: mem::size_of::() as u32, + ..mem::zeroed() }; if Process32FirstW(handle, &mut process) != 0 { diff --git a/crates/nsis-semvercompare/Cargo.toml b/crates/nsis-semvercompare/Cargo.toml index ff645f4..0b8ccb2 100644 --- a/crates/nsis-semvercompare/Cargo.toml +++ b/crates/nsis-semvercompare/Cargo.toml @@ -1,24 +1,14 @@ [package] name = "nsis-semvercompare" version = "0.2.0" - - [package.authors] - workspace = true - - [package.edition] - workspace = true - - [package.license] - workspace = true +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [lib] -crate-type = [ "rlib", "cdylib" ] +crate-type = ["rlib", "cdylib"] [dependencies] semver = "1.0" - - [dependencies.pluginapi] - workspace = true - - [dependencies.windows-sys] - workspace = true +pluginapi = { workspace = true } +windows-sys = { workspace = true } diff --git a/crates/nsis-tauri-utils/Cargo.toml b/crates/nsis-tauri-utils/Cargo.toml index 341f259..5f64a6f 100644 --- a/crates/nsis-tauri-utils/Cargo.toml +++ b/crates/nsis-tauri-utils/Cargo.toml @@ -1,18 +1,12 @@ [package] name = "nsis-tauri-utils" version = "0.2.1" - - [package.authors] - workspace = true - - [package.edition] - workspace = true - - [package.license] - workspace = true +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [lib] -crate-type = [ "cdylib" ] +crate-type = ["cdylib"] [dependencies] nsis-download = { path = "../nsis-download" } diff --git a/crates/pluginapi/Cargo.toml b/crates/pluginapi/Cargo.toml index b6c10d4..0e006cf 100644 --- a/crates/pluginapi/Cargo.toml +++ b/crates/pluginapi/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -windows-sys.workspace = true +windows-sys = { workspace = true }