diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-03-24 16:44:44 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-03-24 16:44:44 +0100 |
| commit | b2aa1dad4b9bb3e4c76b4c3077436fe3d6c454d8 (patch) | |
| tree | 8499586e8983b8a56870c11b8f41fb075a9931a1 | |
| parent | bdaefa370f9a0464753690655b017a868872b69b (diff) | |
| parent | 60c6e1a5a582c14864f454668fd4443c9b4a1068 (diff) | |
| download | mullvadvpn-b2aa1dad4b9bb3e4c76b4c3077436fe3d6c454d8.tar.xz mullvadvpn-b2aa1dad4b9bb3e4c76b4c3077436fe3d6c454d8.zip | |
Merge branch 'win-ensure-pipe-ownership'
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | desktop/packages/windows-utils/Cargo.toml | 3 | ||||
| -rw-r--r-- | desktop/packages/windows-utils/src/index.cts | 9 | ||||
| -rw-r--r-- | desktop/packages/windows-utils/windows-utils-rs/fs.rs | 37 | ||||
| -rw-r--r-- | desktop/packages/windows-utils/windows-utils-rs/lib.rs | 2 | ||||
| -rw-r--r-- | talpid-windows/Cargo.toml | 8 | ||||
| -rw-r--r-- | talpid-windows/src/fs.rs | 78 | ||||
| -rw-r--r-- | talpid-windows/src/lib.rs | 3 |
8 files changed, 142 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock index d95f914eb8..e9d7219758 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6145,6 +6145,8 @@ name = "windows-utils" version = "0.0.0" dependencies = [ "neon", + "talpid-types", + "talpid-windows", "thiserror 2.0.9", "windows 0.59.0", ] diff --git a/desktop/packages/windows-utils/Cargo.toml b/desktop/packages/windows-utils/Cargo.toml index 377e17e208..f64ea99812 100644 --- a/desktop/packages/windows-utils/Cargo.toml +++ b/desktop/packages/windows-utils/Cargo.toml @@ -19,3 +19,6 @@ path = "windows-utils-rs/lib.rs" neon = "1" windows = { version = "0.59.0", features = ["Win32", "Win32_UI", "Win32_UI_Shell", "Win32_System", "Win32_System_Com", "Win32_Storage_FileSystem"] } thiserror = { workspace = true } + +talpid-types = { path = "../../../talpid-types" } +talpid-windows = { path = "../../../talpid-windows" } diff --git a/desktop/packages/windows-utils/src/index.cts b/desktop/packages/windows-utils/src/index.cts index 717968cda4..011e6e8217 100644 --- a/desktop/packages/windows-utils/src/index.cts +++ b/desktop/packages/windows-utils/src/index.cts @@ -7,6 +7,7 @@ import * as addon from './load.cjs'; // which otherwise by default are `any`. declare module './load.cjs' { function readShortcut(linkPath: string): string | null; + function pipeIsAdminOwned(pipePath: string): boolean; } /** @@ -16,3 +17,11 @@ declare module './load.cjs' { export function readShortcut(linkPath: string): string | null { return addon.readShortcut(linkPath); } + +/** + * Return whether a named pipe is owned by the admin or SYSTEM account. + * @param pipePath path to a named pipe. + */ +export function pipeIsAdminOwned(pipePath: string): boolean { + return addon.pipeIsAdminOwned(pipePath); +} diff --git a/desktop/packages/windows-utils/windows-utils-rs/fs.rs b/desktop/packages/windows-utils/windows-utils-rs/fs.rs new file mode 100644 index 0000000000..47b343dd3d --- /dev/null +++ b/desktop/packages/windows-utils/windows-utils-rs/fs.rs @@ -0,0 +1,37 @@ +use std::io; +use std::path::Path; + +use neon::prelude::{Context, FunctionContext}; +use neon::result::JsResult; +use neon::types::{JsString, JsValue, Value}; + +use talpid_types::ErrorExt; + +#[derive(thiserror::Error, Debug)] +enum Error { + /// Failed to open the provided file + #[error("Failed to open named pipe")] + OpenPipe(#[source] io::Error), + + /// Failed to check pipe ownership (GetSecurityInfo) + #[error("Failed to check named pipe ownership (GetSecurityInfo failed)")] + CheckPermissions(#[source] io::Error), +} + +pub fn pipe_is_admin_owned(mut cx: FunctionContext<'_>) -> JsResult<'_, JsValue> { + let link_path = cx.argument::<JsString>(0)?.value(&mut cx); + + match pipe_is_admin_owned_inner(link_path) { + Ok(is_admin_owned) => Ok(cx.boolean(is_admin_owned).as_value(&mut cx)), + Err(err) => cx.throw_error(err.display_chain()), + } +} + +fn pipe_is_admin_owned_inner<P: AsRef<Path>>(path: P) -> Result<bool, Error> { + let client = std::fs::File::options() + .read(true) + .open(path) + .map_err(Error::OpenPipe)?; + + talpid_windows::fs::is_admin_owned(client).map_err(Error::CheckPermissions) +} diff --git a/desktop/packages/windows-utils/windows-utils-rs/lib.rs b/desktop/packages/windows-utils/windows-utils-rs/lib.rs index bdb0c90559..23765c1c4e 100644 --- a/desktop/packages/windows-utils/windows-utils-rs/lib.rs +++ b/desktop/packages/windows-utils/windows-utils-rs/lib.rs @@ -2,11 +2,13 @@ use neon::{prelude::ModuleContext, result::NeonResult}; +mod fs; mod shortcut; #[neon::main] fn main(mut cx: ModuleContext<'_>) -> NeonResult<()> { cx.export_function("readShortcut", shortcut::read_shortcut)?; + cx.export_function("pipeIsAdminOwned", fs::pipe_is_admin_owned)?; Ok(()) } diff --git a/talpid-windows/Cargo.toml b/talpid-windows/Cargo.toml index 0b9e1d2672..fae8c72949 100644 --- a/talpid-windows/Cargo.toml +++ b/talpid-windows/Cargo.toml @@ -23,9 +23,17 @@ features = [ "Win32_Foundation", "Win32_Globalization", "Win32_Security", + "Win32_Security_Authorization", "Win32_System_Diagnostics_ToolHelp", "Win32_System_IO", "Win32_Networking_WinSock", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", ] + +[target.'cfg(windows)'.dev-dependencies.windows-sys] +workspace = true +features = [ + "Win32_Storage", + "Win32_Storage_FileSystem" +] diff --git a/talpid-windows/src/fs.rs b/talpid-windows/src/fs.rs new file mode 100644 index 0000000000..51d714fe6d --- /dev/null +++ b/talpid-windows/src/fs.rs @@ -0,0 +1,78 @@ +use core::ffi::c_void; +use std::io; +use std::os::windows::io::AsRawHandle; +use std::ptr; + +use windows_sys::Win32::{ + Foundation::{LocalFree, ERROR_SUCCESS}, + Security::{ + Authorization::{GetSecurityInfo, SE_FILE_OBJECT}, + IsWellKnownSid, WinBuiltinAdministratorsSid, WinLocalSystemSid, OWNER_SECURITY_INFORMATION, + SECURITY_DESCRIPTOR, SID, + }, +}; + +/// Return whether a file handle is owned by either SYSTEM or the built-in administrators account +pub fn is_admin_owned<T: AsRawHandle>(handle: T) -> io::Result<bool> { + let mut security_descriptor: *mut SECURITY_DESCRIPTOR = ptr::null_mut(); + let mut owner: *mut SID = ptr::null_mut(); + + // SAFETY: `handle` is a valid handle + let result = unsafe { + GetSecurityInfo( + handle.as_raw_handle() as isize, + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION, + (&mut owner) as *mut *mut SID as *mut *mut c_void, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + (&mut security_descriptor) as *mut *mut SECURITY_DESCRIPTOR as *mut *mut c_void, + ) + }; + + if result != ERROR_SUCCESS { + return Err(io::Error::from_raw_os_error(result as i32)); + } + + // SAFETY: `owner` is valid since `security_descriptor` still is, and the well-known type is a valid argument + let is_system_owned = unsafe { IsWellKnownSid(owner as _, WinLocalSystemSid) != 0 }; + // SAFETY: `owner` is valid since `security_descriptor` still is, and the well-known type is a valid argument + let is_admin_owned = unsafe { IsWellKnownSid(owner as _, WinBuiltinAdministratorsSid) != 0 }; + + // SAFETY: Since we no longer need the descriptor (or owner), it may be freed + unsafe { LocalFree(security_descriptor.cast()) }; + + Ok(is_system_owned || is_admin_owned) +} + +#[cfg(test)] +mod test { + use std::os::windows::fs::OpenOptionsExt; + use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS; + + use super::is_admin_owned; + + #[test] + pub fn test_is_admin_owned() { + // The kernel image is owned by "TrustedInstaller", so we expect the function to return 'false' + let path = std::fs::File::open(r"C:\Windows\System32\ntoskrnl.exe").unwrap(); + let result = is_admin_owned(path); + assert!( + matches!(result, Ok(false)), + "expected ntoskrnl.exe to be owned by TrustedInstaller (false), got {result:?}" + ); + + // The Windows system temp directory is owned by SYSTEM, so we expect 'true' + let path = std::fs::File::options() + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(r"C:\Windows\Temp") + .unwrap(); + let result = is_admin_owned(path); + assert!( + matches!(result, Ok(true)), + "expected TEMP to be owned by SYSTEM (true), got {result:?}" + ); + } +} diff --git a/talpid-windows/src/lib.rs b/talpid-windows/src/lib.rs index 53fa172ee0..92db769623 100644 --- a/talpid-windows/src/lib.rs +++ b/talpid-windows/src/lib.rs @@ -3,6 +3,9 @@ #![deny(missing_docs)] #![cfg(windows)] +/// File system +pub mod fs; + /// I/O pub mod io; |
