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" ) ) ]
1819use 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" ) ]
2141use 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+ ) ) ]
294410fn get_variable ( _name : & str ) -> Option < Vec < u8 > > {
295411 error ! ( "Reading UEFI variables is not implemented on this OS" ) ;
296412 None
0 commit comments