#[cfg(target_os = "windows")]
use std::ffi::c_void;
#[cfg(target_os = "windows")]
use std::ptr;
#[cfg(target_os = "windows")]
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, ERROR_SUCCESS, HANDLE};
#[cfg(target_os = "windows")]
#[allow(unused_imports)]
use windows_sys::Win32::Security::{
AdjustTokenPrivileges, CopySid, CreateRestrictedToken, CreateWellKnownSid, GetLengthSid,
GetTokenInformation, LookupPrivilegeValueW, SetTokenInformation, TokenDefaultDacl, ACL,
SID_AND_ATTRIBUTES, TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_PRIVILEGES, TOKEN_ADJUST_SESSIONID,
TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_PRIVILEGES, TOKEN_QUERY,
};
#[cfg(target_os = "windows")]
use windows_sys::Win32::Security::Authorization::{
SetEntriesInAclW, EXPLICIT_ACCESS_W, GRANT_ACCESS, TRUSTEE_IS_SID, TRUSTEE_IS_UNKNOWN,
TRUSTEE_W,
};
#[cfg(target_os = "windows")]
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
const DISABLE_MAX_PRIVILEGE: u32 = 0x01;
const LUA_TOKEN: u32 = 0x04;
const WRITE_RESTRICTED: u32 = 0x08;
const GENERIC_ALL: u32 = 0x1000_0000;
const WIN_WORLD_SID: i32 = 1;
const SE_GROUP_LOGON_ID: u32 = 0xC0000000;
#[cfg(target_os = "windows")]
#[repr(C)]
struct TokenDefaultDaclInfo {
default_dacl: *mut ACL,
}
#[cfg(target_os = "windows")]
unsafe fn set_default_dacl(h_token: HANDLE, sids: &[*mut c_void]) -> Result<(), String> {
if sids.is_empty() {
return Ok(());
}
let entries: Vec<EXPLICIT_ACCESS_W> = sids
.iter()
.map(|sid| EXPLICIT_ACCESS_W {
grfAccessPermissions: GENERIC_ALL,
grfAccessMode: GRANT_ACCESS,
grfInheritance: 0,
Trustee: TRUSTEE_W {
pMultipleTrustee: ptr::null_mut(),
MultipleTrusteeOperation: 0,
TrusteeForm: TRUSTEE_IS_SID,
TrusteeType: TRUSTEE_IS_UNKNOWN,
ptstrName: *sid as *mut u16,
},
})
.collect();
let mut p_new_dacl: *mut ACL = ptr::null_mut();
let res = SetEntriesInAclW(
entries.len() as u32,
entries.as_ptr(),
ptr::null_mut(),
&mut p_new_dacl,
);
if res != ERROR_SUCCESS {
return Err(format!("SetEntriesInAclW failed: {}", res));
}
let mut info = TokenDefaultDaclInfo {
default_dacl: p_new_dacl,
};
let ok = SetTokenInformation(
h_token,
TokenDefaultDacl,
&mut info as *mut _ as *mut c_void,
std::mem::size_of::<TokenDefaultDaclInfo>() as u32,
);
if ok == 0 {
return Err(format!(
"SetTokenInformation(TokenDefaultDacl) failed: {}",
GetLastError()
));
}
Ok(())
}
#[cfg(target_os = "windows")]
fn get_current_token_for_restriction() -> Result<HANDLE, String> {
unsafe {
let mut token: HANDLE = std::ptr::null_mut();
let ok = OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY,
&mut token,
);
if ok == 0 {
return Err(format!("OpenProcessToken failed: {}", GetLastError()));
}
Ok(token)
}
}
#[cfg(target_os = "windows")]
fn world_sid() -> Result<Vec<u8>, String> {
unsafe {
let mut size: u32 = 0;
CreateWellKnownSid(WIN_WORLD_SID, ptr::null_mut(), ptr::null_mut(), &mut size);
if size == 0 {
return Err("Failed to get World SID size".to_string());
}
let mut sid = vec![0u8; size as usize];
if CreateWellKnownSid(
WIN_WORLD_SID,
ptr::null_mut(),
sid.as_mut_ptr() as *mut _,
&mut size,
) == 0
{
return Err("Failed to create World SID".to_string());
}
Ok(sid)
}
}
#[cfg(target_os = "windows")]
unsafe fn get_logon_sid_bytes(token: HANDLE) -> Result<Vec<u8>, String> {
#[allow(non_upper_case_globals)]
const TokenGroups: i32 = 5;
let mut size: u32 = 0;
let res = GetTokenInformation(token, TokenGroups, ptr::null_mut(), 0, &mut size);
if res != 0 || size == 0 {
return Err(format!(
"GetTokenInformation size query failed: {}",
GetLastError()
));
}
let mut data = vec![0u8; size as usize];
let mut return_length: u32 = 0;
let res = GetTokenInformation(
token,
TokenGroups,
data.as_mut_ptr() as *mut c_void,
size,
&mut return_length,
);
if res == 0 {
return Err(format!("GetTokenInformation failed: {}", GetLastError()));
}
let group_count = *(data.as_ptr() as *const u32);
let groups_ptr = data.as_ptr().add(std::mem::size_of::<u32>()) as *const SID_AND_ATTRIBUTES;
for i in 0..group_count {
let sid_attr = *groups_ptr.add(i as usize);
if (sid_attr.Attributes & SE_GROUP_LOGON_ID) != 0 {
let sid_len = GetLengthSid(sid_attr.Sid) as usize;
let mut logon_sid = vec![0u8; sid_len];
if CopySid(
sid_len as u32,
logon_sid.as_mut_ptr() as *mut _,
sid_attr.Sid,
) == 0
{
return Err(format!("CopySid failed: {}", GetLastError()));
}
return Ok(logon_sid);
}
}
Err("Logon SID not found".to_string())
}
#[cfg(target_os = "windows")]
unsafe fn enable_single_privilege(token: HANDLE, name: &str) -> Result<(), String> {
let mut luid: windows_sys::Win32::Foundation::LUID = std::mem::zeroed();
let wide_name: Vec<u16> = name.encode_utf16().chain(std::iter::once(0)).collect();
if LookupPrivilegeValueW(ptr::null(), wide_name.as_ptr(), &mut luid) == 0 {
return Err(format!("LookupPrivilegeValueW failed: {}", GetLastError()));
}
let mut tp = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [windows_sys::Win32::Security::LUID_AND_ATTRIBUTES {
Luid: luid,
Attributes: 2, }],
};
#[allow(clippy::unnecessary_mut_passed)]
if AdjustTokenPrivileges(token, 0, &mut tp, 0, ptr::null_mut(), ptr::null_mut()) == 0 {
return Err(format!("AdjustTokenPrivileges error {}", GetLastError()));
}
Ok(())
}
#[cfg(target_os = "windows")]
pub unsafe fn create_restricted_token_with_caps(
psid_capabilities: &[*mut c_void],
) -> Result<HANDLE, String> {
if psid_capabilities.is_empty() {
return Err("no capability SIDs provided".to_string());
}
let base = get_current_token_for_restriction()?;
let result = create_token_with_caps_from(base, psid_capabilities);
let _ = CloseHandle(base);
result
}
#[cfg(target_os = "windows")]
unsafe fn create_token_with_caps_from(
base_token: HANDLE,
psid_capabilities: &[*mut c_void],
) -> Result<HANDLE, String> {
if psid_capabilities.is_empty() {
return Err("no capability SIDs provided".to_string());
}
let mut logon_sid_bytes = get_logon_sid_bytes(base_token)?;
let psid_logon = logon_sid_bytes.as_mut_ptr() as *mut c_void;
let mut everyone = world_sid()?;
let psid_everyone = everyone.as_mut_ptr() as *mut c_void;
let mut entries: Vec<SID_AND_ATTRIBUTES> =
vec![std::mem::zeroed(); psid_capabilities.len() + 2];
for (i, psid) in psid_capabilities.iter().enumerate() {
entries[i].Sid = *psid;
entries[i].Attributes = 0;
}
let logon_idx = psid_capabilities.len();
entries[logon_idx].Sid = psid_logon;
entries[logon_idx].Attributes = 0;
entries[logon_idx + 1].Sid = psid_everyone;
entries[logon_idx + 1].Attributes = 0;
let mut new_token: HANDLE = std::ptr::null_mut();
let flags = DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED;
let ok = CreateRestrictedToken(
base_token,
flags,
0,
ptr::null(),
0,
ptr::null(),
entries.len() as u32,
entries.as_mut_ptr(),
&mut new_token,
);
if ok == 0 {
return Err(format!("CreateRestrictedToken failed: {}", GetLastError()));
}
let mut dacl_sids: Vec<*mut c_void> = Vec::with_capacity(psid_capabilities.len() + 2);
dacl_sids.push(psid_logon);
dacl_sids.push(psid_everyone);
dacl_sids.extend_from_slice(psid_capabilities);
set_default_dacl(new_token, &dacl_sids)?;
enable_single_privilege(new_token, "SeChangeNotifyPrivilege")?;
Ok(new_token)
}
#[cfg(target_os = "windows")]
pub unsafe fn create_readonly_token() -> Result<HANDLE, String> {
create_restricted_token_with_caps(&[])
}
#[cfg(target_os = "windows")]
pub unsafe fn close_token(handle: HANDLE) -> Result<(), String> {
if handle.is_null() {
return Ok(());
}
if CloseHandle(handle) == 0 {
Err(format!("CloseHandle failed: {}", GetLastError()))
} else {
Ok(())
}
}
#[cfg(test)]
#[cfg(target_os = "windows")]
mod tests {
use super::*;
#[test]
fn test_create_readonly_token() {
unsafe {
let result = create_readonly_token();
match result {
Ok(token) => {
assert!(!token.is_null(), "Token should not be null on success");
let close_result = close_token(token);
assert!(close_result.is_ok(), "Failed to close token");
}
Err(e) => {
eprintln!(
"create_readonly_token failed (expected in non-elevated CI): {}",
e
);
}
}
}
}
#[test]
fn test_create_restricted_token_rejects_empty_caps() {
unsafe {
let result = create_restricted_token_with_caps(&[]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.contains("no capability SIDs provided"),
"Expected 'no capability SIDs provided' error, got: {}",
err
);
}
}
#[test]
fn test_close_null_token() {
unsafe {
let result = close_token(std::ptr::null_mut());
assert!(result.is_ok());
}
}
}