diff options
| -rw-r--r-- | dist-assets/windows/installer.nsh | 17 | ||||
| -rw-r--r-- | mullvad-nsis/include/mullvad-nsis.h | 13 | ||||
| -rw-r--r-- | mullvad-nsis/src/lib.rs | 31 | ||||
| -rw-r--r-- | talpid-platform-metadata/Cargo.toml | 4 | ||||
| -rw-r--r-- | talpid-platform-metadata/src/windows.rs | 152 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/cleanup/cleanup.cpp | 3 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/log.cpp | 38 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/log.def | 1 |
8 files changed, 235 insertions, 24 deletions
diff --git a/dist-assets/windows/installer.nsh b/dist-assets/windows/installer.nsh index f80d379d31..c6fcf161d1 100644 --- a/dist-assets/windows/installer.nsh +++ b/dist-assets/windows/installer.nsh @@ -689,10 +689,19 @@ ManifestSupportedOS "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" Push $0 - ${IfNot} ${AtLeastWin10} - MessageBox MB_ICONSTOP|MB_TOPMOST|MB_OK "Windows versions below 10 are unsupported. The last version to support Windows 7 and 8/8.1 is 2021.6." - Abort - ${EndIf} + # We do not use AtLeastWin10, because it is affected by compatibility mode. Instead, we infer + # the version from the kernel image. + log::GetWindowsMajorVersion + Pop $0 + + IntCmp $0 10 customInit_compatibleWinVer +1 customInit_compatibleWinVer + # Best effort only. Ignore errors + IntCmp $0 -1 customInit_compatibleWinVer customInit_compatibleWinVer +1 + + MessageBox MB_ICONSTOP|MB_TOPMOST|MB_OK "Windows versions below 10 are unsupported. The last version to support Windows 7 and 8/8.1 is 2021.6." + Abort + + customInit_compatibleWinVer: Var /GLOBAL NativeTarget ${If} ${IsNativeAMD64} diff --git a/mullvad-nsis/include/mullvad-nsis.h b/mullvad-nsis/include/mullvad-nsis.h index 3847df273b..f8870acff9 100644 --- a/mullvad-nsis/include/mullvad-nsis.h +++ b/mullvad-nsis/include/mullvad-nsis.h @@ -12,6 +12,13 @@ enum class Status { Panic, }; +/// Windows version details +struct WindowsVer { + uint32_t major_version; + uint32_t minor_version; + uint32_t build_number; +}; + extern "C" { /// Creates a privileged directory at the specified Windows path. @@ -43,4 +50,10 @@ Status get_system_local_appdata(uint16_t *buffer, uintptr_t *buffer_size); /// at least `*buffer_size` number of `u16` values. `buffer_size` must be a valid pointer. Status get_system_version(uint16_t *buffer, uintptr_t *buffer_size); +/// Write OS version into `version_out` when `Status::Ok` is returned. +/// +/// # Safety +/// `version_out` should point to a valid `WindowsVer` +Status get_system_version_struct(WindowsVer *version_out); + } // extern "C" diff --git a/mullvad-nsis/src/lib.rs b/mullvad-nsis/src/lib.rs index 5aed1389c0..6bb7472eff 100644 --- a/mullvad-nsis/src/lib.rs +++ b/mullvad-nsis/src/lib.rs @@ -142,6 +142,37 @@ pub unsafe extern "C" fn get_system_version(buffer: *mut u16, buffer_size: *mut }) } +/// Windows version details +#[repr(C)] +pub struct WindowsVer { + major_version: u32, + minor_version: u32, + build_number: u32, +} + +/// Write OS version into `version_out` when `Status::Ok` is returned. +/// +/// # Safety +/// `version_out` should point to a valid `WindowsVer` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn get_system_version_struct(version_out: *mut WindowsVer) -> Status { + use talpid_platform_metadata::WindowsVersion; + catch_and_log_unwind(|| { + // Try to retrieve the version based on the kernel image. Use normal method as fallback. + let winver = WindowsVersion::from_ntoskrnl() + .or_else(|_| WindowsVersion::new()) + .unwrap(); + let c_ver = WindowsVer { + major_version: winver.major_version(), + minor_version: winver.minor_version(), + build_number: winver.build_number(), + }; + // SAFETY: `version_out` is a valid `WindowsVer` if the caller upholds the contract. + unsafe { ptr::write(version_out, c_ver) }; + Status::Ok + }) +} + fn catch_and_log_unwind(func: impl FnOnce() -> Status + UnwindSafe) -> Status { match std::panic::catch_unwind(func) { Ok(status) => status, diff --git a/talpid-platform-metadata/Cargo.toml b/talpid-platform-metadata/Cargo.toml index 9eb58f4a55..66db6c4285 100644 --- a/talpid-platform-metadata/Cargo.toml +++ b/talpid-platform-metadata/Cargo.toml @@ -22,6 +22,10 @@ talpid-dbus = { path = "../talpid-dbus", optional = true } workspace = true features = [ "Win32_Foundation", + "Win32", + "Win32_Storage", + "Win32_Storage_FileSystem", + "Win32_System_Diagnostics_Debug", "Win32_System_LibraryLoader", "Win32_System_SystemInformation", "Win32_System_SystemServices", diff --git a/talpid-platform-metadata/src/windows.rs b/talpid-platform-metadata/src/windows.rs index ec2b7b82be..9a23cc21c4 100644 --- a/talpid-platform-metadata/src/windows.rs +++ b/talpid-platform-metadata/src/windows.rs @@ -1,14 +1,23 @@ use std::{ - ffi::OsString, + ffi::{OsStr, OsString}, io, iter, mem::{self, MaybeUninit}, - os::windows::ffi::OsStrExt, + os::{ + raw::c_void, + windows::ffi::{OsStrExt, OsStringExt}, + }, + path::PathBuf, + ptr, }; use windows_sys::Win32::{ - Foundation::{NTSTATUS, STATUS_SUCCESS}, + Foundation::{MAX_PATH, NTSTATUS, STATUS_SUCCESS}, + Storage::FileSystem::{ + GetFileVersionInfoSizeW, GetFileVersionInfoW, VS_FFI_SIGNATURE, VS_FIXEDFILEINFO, + VerQueryValueW, + }, System::{ LibraryLoader::{GetModuleHandleW, GetProcAddress}, - SystemInformation::OSVERSIONINFOEXW, + SystemInformation::{GetSystemDirectoryW, OSVERSIONINFOEXW}, SystemServices::VER_NT_WORKSTATION, }, }; @@ -41,16 +50,22 @@ pub fn extra_metadata() -> impl Iterator<Item = (String, String)> { } pub struct WindowsVersion { - inner: RTL_OSVERSIONINFOEXW, + major: u32, + minor: u32, + build: u32, + product_type: ProductType, +} + +#[derive(PartialEq)] +enum ProductType { + Unknown, + Workstation, + Server, } impl WindowsVersion { pub fn new() -> Result<WindowsVersion, io::Error> { - let module_name: Vec<u16> = OsString::from("ntdll") - .as_os_str() - .encode_wide() - .chain(iter::once(0u16)) - .collect(); + let module_name = to_wide("ntdll"); // SAFETY: module_name is a valid UTF-16/WTF-16 null-terminated string. let ntdll = unsafe { GetModuleHandleW(module_name.as_ptr()) }; @@ -87,15 +102,35 @@ impl WindowsVersion { ); Ok(WindowsVersion { - inner: version_info, + major: version_info.dwMajorVersion, + minor: version_info.dwMinorVersion, + build: version_info.dwBuildNumber, + product_type: match u32::from(version_info.wProductType) { + // `wProductType != VER_NT_WORKSTATION` implies that OS is Windows Server + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw + VER_NT_WORKSTATION => ProductType::Workstation, + _ => ProductType::Server, + }, + }) + } + + /// Extract Windows version information from the kernel image, which is unaffected by compatibility + /// mode. Note that this does not infer whether we are running Windows Server or a normal version. + pub fn from_ntoskrnl() -> io::Result<Self> { + let (major, minor, build) = ntoskrnl_version()?; + + Ok(Self { + major, + minor, + build, + // NOTE: We do not have the product type here + product_type: ProductType::Unknown, }) } pub fn windows_version_string(&self) -> String { - // `wProductType != VER_NT_WORKSTATION` implies that OS is Windows Server - // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw - // NOTE: This does not deduce which Windows Server version is running. - if u32::from(self.inner.wProductType) != VER_NT_WORKSTATION { + if self.product_type == ProductType::Server { + // NOTE: This does not deduce which Windows Server version is running. return "Server".to_owned(); } @@ -125,23 +160,104 @@ impl WindowsVersion { } pub fn major_version(&self) -> u32 { - self.inner.dwMajorVersion + self.major } pub fn minor_version(&self) -> u32 { - self.inner.dwMinorVersion + self.minor } pub fn build_number(&self) -> u32 { - self.inner.dwBuildNumber + self.build + } +} + +fn ntoskrnl_version() -> io::Result<(u32, u32, u32)> { + let ntoskrnl_path = get_system_dir()?.join("ntoskrnl.exe"); + let wide_path = to_wide(ntoskrnl_path); + let mut handle = 0u32; + + // SAFETY: We have a valid string and `handle` pointer + let size = unsafe { GetFileVersionInfoSizeW(wide_path.as_ptr(), &mut handle) }; + if size == 0 { + return Err(io::Error::last_os_error()); + } + + let mut buffer = vec![0u8; size as usize]; + // SAFETY: `buffer` contains enough space to store the result + let status = + unsafe { GetFileVersionInfoW(wide_path.as_ptr(), 0, size, buffer.as_mut_ptr() as *mut _) }; + + if status == 0 { + return Err(io::Error::last_os_error()); + } + + let mut lp_buffer: *mut c_void = ptr::null_mut(); + let mut len = 0u32; + + let sub_block = to_wide(r"\"); + // SAFETY: `buffer` points to a valid version-info resource + let success = unsafe { + VerQueryValueW( + buffer.as_ptr() as *const _, + sub_block.as_ptr(), + &mut lp_buffer, + &mut len, + ) + }; + + if success == 0 || lp_buffer.is_null() { + return Err(io::Error::last_os_error()); + } + + // SAFETY: `lp_buffer` points to a valid `VS_FIXEDFILEINFO` + let info = unsafe { &*(lp_buffer as *const VS_FIXEDFILEINFO) }; + if info.dwSignature != VS_FFI_SIGNATURE as u32 { + return Err(io::Error::other("Invalid version info signature")); + } + + let major = info.dwProductVersionMS >> 16; + let minor = info.dwProductVersionMS & 0xFFFF; + let build = info.dwProductVersionLS >> 16; + + Ok((major, minor, build)) +} + +fn get_system_dir() -> io::Result<PathBuf> { + let mut sysdir = [0u16; MAX_PATH as usize + 1]; + // SAFETY: `sysdir` points to a valid buffer + let len = unsafe { GetSystemDirectoryW(sysdir.as_mut_ptr(), (sysdir.len() - 1) as u32) }; + if len == 0 { + return Err(io::Error::last_os_error()); } + Ok(PathBuf::from(OsString::from_wide( + &sysdir[0..(len as usize)], + ))) +} + +/// Return a null-terminated UTF16 string +fn to_wide(s: impl AsRef<OsStr>) -> Vec<u16> { + s.as_ref().encode_wide().chain(iter::once(0u16)).collect() } #[cfg(test)] mod test { use super::*; + #[test] fn test_windows_version() { WindowsVersion::new().unwrap(); } + + #[test] + fn test_ntoskrnl_version() { + let winver = WindowsVersion::new().unwrap(); + let nt_winver = WindowsVersion::from_ntoskrnl().unwrap(); + + assert_eq!(winver.major, nt_winver.major); + assert_eq!(winver.minor, nt_winver.minor); + assert_eq!(winver.build, nt_winver.build); + + // NOTE: We do not know the product type for `nt_winver` + } } diff --git a/windows/nsis-plugins/src/cleanup/cleanup.cpp b/windows/nsis-plugins/src/cleanup/cleanup.cpp index ca943934a7..b52c269577 100644 --- a/windows/nsis-plugins/src/cleanup/cleanup.cpp +++ b/windows/nsis-plugins/src/cleanup/cleanup.cpp @@ -7,6 +7,9 @@ #include <functional> #include <vector> +// NOTE: Linker refuses to find the library unless specified here +#pragma comment(lib, "version.lib") + void __declspec(dllexport) NSISCALL RemoveLogsAndCache ( HWND hwndParent, diff --git a/windows/nsis-plugins/src/log/log.cpp b/windows/nsis-plugins/src/log/log.cpp index d1e37d7669..64eabedf8d 100644 --- a/windows/nsis-plugins/src/log/log.cpp +++ b/windows/nsis-plugins/src/log/log.cpp @@ -90,7 +90,7 @@ std::vector<std::wstring> BlockToRows(const std::wstring &textBlock) return common::string::Tokenize(textBlock, L"\r\n"); } -std::wstring GetWindowsVersion() +std::wstring GetWindowsVersionString() { std::vector<uint16_t> version(256); size_t bufferSize = version.size(); @@ -287,7 +287,7 @@ void __declspec(dllexport) NSISCALL LogWindowsVersion try { std::wstringstream version; - version << L"Windows version: " << GetWindowsVersion(); + version << L"Windows version: " << GetWindowsVersionString(); g_logger->log(version.str()); } catch (std::exception &err) @@ -306,6 +306,40 @@ void __declspec(dllexport) NSISCALL LogWindowsVersion } // +// GetWindowsMajorVersion +// +// Returns the current Windows major version on the stack. -1 on error. +// +void __declspec(dllexport) NSISCALL GetWindowsMajorVersion +( + HWND hwndParent, + int string_size, + LPTSTR variables, + stack_t **stacktop, + extra_parameters *extra, + ... +) +{ + EXDLL_INIT(); + + WindowsVer ver = { 0 }; + + if (get_system_version_struct(&ver) == Status::Ok) + { + pushint(ver.major_version); + return; + } + + + if (nullptr != g_logger) + { + g_logger->log(L"Windows version: Failed to determine version"); + } + + pushint(-1); +} + +// // PluginLog // // Writes a message to the log file. diff --git a/windows/nsis-plugins/src/log/log.def b/windows/nsis-plugins/src/log/log.def index d51b04ab7b..f8bafa4a57 100644 --- a/windows/nsis-plugins/src/log/log.def +++ b/windows/nsis-plugins/src/log/log.def @@ -6,6 +6,7 @@ SetLogTarget Log LogWithDetails LogWindowsVersion +GetWindowsMajorVersion PluginLog PluginLogWithDetails |
