summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-03-24 16:44:44 +0100
committerDavid Lönnhager <david.l@mullvad.net>2025-03-24 16:44:44 +0100
commitb2aa1dad4b9bb3e4c76b4c3077436fe3d6c454d8 (patch)
tree8499586e8983b8a56870c11b8f41fb075a9931a1
parentbdaefa370f9a0464753690655b017a868872b69b (diff)
parent60c6e1a5a582c14864f454668fd4443c9b4a1068 (diff)
downloadmullvadvpn-b2aa1dad4b9bb3e4c76b4c3077436fe3d6c454d8.tar.xz
mullvadvpn-b2aa1dad4b9bb3e4c76b4c3077436fe3d6c454d8.zip
Merge branch 'win-ensure-pipe-ownership'
-rw-r--r--Cargo.lock2
-rw-r--r--desktop/packages/windows-utils/Cargo.toml3
-rw-r--r--desktop/packages/windows-utils/src/index.cts9
-rw-r--r--desktop/packages/windows-utils/windows-utils-rs/fs.rs37
-rw-r--r--desktop/packages/windows-utils/windows-utils-rs/lib.rs2
-rw-r--r--talpid-windows/Cargo.toml8
-rw-r--r--talpid-windows/src/fs.rs78
-rw-r--r--talpid-windows/src/lib.rs3
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;