Skip to content

Commit cbc82e3

Browse files
committed
--wifisar: windows support
Signed-off-by: Daniel Schaefer <dhs@frame.work>
1 parent 81b84c9 commit cbc82e3

2 files changed

Lines changed: 121 additions & 3 deletions

File tree

framework_lib/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ built = { version = "0.8", features = ["chrono", "git2"] }
2626
[dependencies]
2727
lazy_static = "1.4.0"
2828
dmidecode = { version = "1", default-features = false }
29-
sha1 = { version = "0.10.6", default-features = false }
29+
sha1 = { version = "0.10.6", default-features = false, features = [ "force-soft" ] }
3030
sha2 = { version = "0.10.8", default-features = false, features = [ "force-soft" ] }
3131
regex = { version = "1.11.1", default-features = false }
3232
num = { version = "0.4", default-features = false }
@@ -80,6 +80,8 @@ features = [
8080
"Win32_System_Ioctl",
8181
"Win32_System_SystemInformation",
8282
"Win32_System_SystemServices",
83+
# For reading UEFI variables
84+
"Win32_System_WindowsProgramming",
8385
# For HID devices
8486
"Win32_Devices_DeviceAndDriverInstallation",
8587
"Win32_Devices_HumanInterfaceDevice",

framework_lib/src/wifi_sar.rs

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Get the Intel CNVi Wi-Fi SAR power tables from UEFI variables.
22
//!
3-
//! Currently only implemented on Linux (needs root) and UEFI.
3+
//! Currently only implemented on Linux (needs root), Windows (needs
4+
//! Administrator) and UEFI.
45
//!
56
//! SAR (Specific Absorption Rate) limits are the maximum TX power the Wi-Fi
67
//! module may use per frequency sub-band. The BIOS hands them to the driver in
@@ -17,6 +18,25 @@ use std::prelude::v1::*;
1718
#[cfg(all(not(feature = "uefi"), target_os = "linux"))]
1819
use std::fs;
1920

21+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
22+
use core::ffi::c_void;
23+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
24+
use windows::core::PCWSTR;
25+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
26+
use windows::Win32::Foundation::{
27+
CloseHandle, GetLastError, ERROR_ENVVAR_NOT_FOUND, ERROR_INVALID_FUNCTION,
28+
ERROR_NOT_ALL_ASSIGNED, ERROR_PRIVILEGE_NOT_HELD, HANDLE, LUID,
29+
};
30+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
31+
use windows::Win32::Security::{
32+
AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED,
33+
SE_SYSTEM_ENVIRONMENT_NAME, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES,
34+
};
35+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
36+
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
37+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
38+
use windows::Win32::System::WindowsProgramming::GetFirmwareEnvironmentVariableW;
39+
2040
#[cfg(feature = "uefi")]
2141
use uefi::runtime::{self, VariableVendor};
2242
#[cfg(feature = "uefi")]
@@ -290,7 +310,103 @@ fn get_variable(name: &str) -> Option<Vec<u8>> {
290310
.map(|(data, _attributes)| data.to_vec())
291311
}
292312

293-
#[cfg(all(not(feature = "uefi"), not(target_os = "linux")))]
313+
/// Enable SeSystemEnvironmentPrivilege, which is required to read UEFI variables
314+
///
315+
/// Elevated processes have the privilege in their token, but it's disabled by
316+
/// default, so we have to enable it ourselves.
317+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
318+
fn enable_system_environment_privilege() -> bool {
319+
let mut token = HANDLE::default();
320+
// SAFETY: Both handles are only used within this function and the token is
321+
// closed before returning
322+
unsafe {
323+
if let Err(err) = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &mut token)
324+
{
325+
error!("Failed to open process token: {}", err);
326+
return false;
327+
}
328+
329+
let mut luid = LUID::default();
330+
let res = LookupPrivilegeValueW(PCWSTR::null(), SE_SYSTEM_ENVIRONMENT_NAME, &mut luid)
331+
.and_then(|_| {
332+
let privileges = TOKEN_PRIVILEGES {
333+
PrivilegeCount: 1,
334+
Privileges: [LUID_AND_ATTRIBUTES {
335+
Luid: luid,
336+
Attributes: SE_PRIVILEGE_ENABLED,
337+
}],
338+
};
339+
AdjustTokenPrivileges(token, false, Some(&privileges), 0, None, None)
340+
});
341+
// AdjustTokenPrivileges succeeds even if it couldn't assign the
342+
// privilege, that's only reported by the last error
343+
let assigned = GetLastError() != ERROR_NOT_ALL_ASSIGNED;
344+
let _ = CloseHandle(token);
345+
346+
if let Err(err) = res {
347+
error!("Failed to enable SeSystemEnvironmentPrivilege: {}", err);
348+
return false;
349+
}
350+
if !assigned {
351+
error!("Not allowed to enable SeSystemEnvironmentPrivilege");
352+
info!("Make sure to run as Administrator to access UEFI variables on Windows");
353+
return false;
354+
}
355+
}
356+
true
357+
}
358+
359+
/// Encode a string as a NUL terminated wide string for the Win32 API
360+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
361+
fn wide(s: &str) -> Vec<u16> {
362+
s.encode_utf16().chain(core::iter::once(0)).collect()
363+
}
364+
365+
/// Read a UEFI variable of the Intel CNVi Wi-Fi vendor GUID
366+
#[cfg(all(not(feature = "uefi"), target_os = "windows"))]
367+
fn get_variable(name: &str) -> Option<Vec<u8>> {
368+
if !enable_system_environment_privilege() {
369+
return None;
370+
}
371+
372+
let name_wide = wide(name);
373+
// Win32 wants the vendor GUID as a string in braces
374+
let guid_wide = wide(&format!("{{{}}}", CNVI_WIFI_GUID));
375+
// Plenty for all revisions of the tables we know about
376+
let mut buf = [0; 1024];
377+
// SAFETY: Both strings are NUL terminated and the buffer length is passed along
378+
let len = unsafe {
379+
GetFirmwareEnvironmentVariableW(
380+
PCWSTR(name_wide.as_ptr()),
381+
PCWSTR(guid_wide.as_ptr()),
382+
Some(buf.as_mut_ptr() as *mut c_void),
383+
buf.len() as u32,
384+
)
385+
};
386+
if len == 0 {
387+
// SAFETY: Called right after the failed call above
388+
match unsafe { GetLastError() } {
389+
ERROR_INVALID_FUNCTION => {
390+
error!("Failed to read UEFI variable {}", name);
391+
info!("UEFI variables are only available when booted in UEFI mode");
392+
}
393+
ERROR_PRIVILEGE_NOT_HELD => {
394+
error!("Not allowed to read UEFI variable {}", name);
395+
info!("Make sure to run as Administrator to access UEFI variables on Windows");
396+
}
397+
ERROR_ENVVAR_NOT_FOUND => error!("UEFI variable {} does not exist", name),
398+
err => error!("Failed to read UEFI variable {}: {:?}", name, err),
399+
}
400+
return None;
401+
}
402+
Some(buf[..len as usize].to_vec())
403+
}
404+
405+
#[cfg(all(
406+
not(feature = "uefi"),
407+
not(target_os = "linux"),
408+
not(target_os = "windows")
409+
))]
294410
fn get_variable(_name: &str) -> Option<Vec<u8>> {
295411
error!("Reading UEFI variables is not implemented on this OS");
296412
None

0 commit comments

Comments
 (0)