Skip to content

Commit

Permalink
feat(process): add FindProcessCurrentUser and `KillProcessCurrentUs…
Browse files Browse the repository at this point in the history
…er` (#24)

Co-authored-by: FabianLars-crabnebula <[email protected]>
  • Loading branch information
amr-crabnebula and FabianLars-crabnebula authored Dec 13, 2023
1 parent 86b96b6 commit 7b6cfcc
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 63 deletions.
6 changes: 6 additions & 0 deletions .changes/process-currentuser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"nsis_tauri_utils": "patch"
"nsis_process": "patch"
---

Add `FindProcessCurrentUser` and `KillProcessCurrentUser`.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 7 additions & 17 deletions crates/nsis-download/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
20 changes: 6 additions & 14 deletions crates/nsis-process/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
143 changes: 138 additions & 5 deletions crates/nsis-process/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
},
},
};

Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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<u32> {
let current_pid = std::process::id();
let mut processes = Vec::new();
Expand All @@ -78,8 +211,8 @@ fn get_processes(name: &str) -> Vec<u32> {
let handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

let mut process = PROCESSENTRY32W {
dwSize: size_of::<PROCESSENTRY32W>() as u32,
..std::mem::zeroed()
dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
..mem::zeroed()
};

if Process32FirstW(handle, &mut process) != 0 {
Expand Down
22 changes: 6 additions & 16 deletions crates/nsis-semvercompare/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
14 changes: 4 additions & 10 deletions crates/nsis-tauri-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion crates/pluginapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ edition = "2021"
license = "MIT OR Apache-2.0"

[dependencies]
windows-sys.workspace = true
windows-sys = { workspace = true }

0 comments on commit 7b6cfcc

Please sign in to comment.