//! Windows Restricted Token creation. use crate::winutil::to_wide; use anyhow::Result; use std::ffi::c_void; use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, LUID}; use windows_sys::Win32::Security::{ AdjustTokenPrivileges, CopySid, CreateRestrictedToken, CreateWellKnownSid, GetLengthSid, GetTokenInformation, LookupPrivilegeValueW, TokenGroups, SID_AND_ATTRIBUTES, TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_PRIVILEGES, TOKEN_ADJUST_SESSIONID, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_PRIVILEGES, TOKEN_QUERY, }; use windows_sys::Win32::System::Threading::GetCurrentProcess; const DISABLE_MAX_PRIVILEGE: u32 = 0x01; const LUA_TOKEN: u32 = 0x04; const WRITE_RESTRICTED: u32 = 0x08; const WIN_WORLD_SID: i32 = 1; const SE_GROUP_LOGON_ID: u32 = 0xC0000000; pub unsafe fn world_sid() -> Result> { let mut size: u32 = 0; CreateWellKnownSid(WIN_WORLD_SID, std::ptr::null_mut(), std::ptr::null_mut(), &mut size); let mut buf: Vec = vec![0u8; size as usize]; let ok = CreateWellKnownSid(WIN_WORLD_SID, std::ptr::null_mut(), buf.as_mut_ptr() as *mut c_void, &mut size); if ok == 0 { return Err(anyhow::anyhow!("CreateWellKnownSid failed: {}", GetLastError())); } Ok(buf) } pub unsafe fn convert_string_sid_to_sid(s: &str) -> Option<*mut c_void> { #[link(name = "advapi32")] extern "system" { fn ConvertStringSidToSidW(StringSid: *const u16, Sid: *mut *mut c_void) -> i32; } let mut psid: *mut c_void = std::ptr::null_mut(); let ok = unsafe { ConvertStringSidToSidW(to_wide(s).as_ptr(), &mut psid) }; if ok != 0 { Some(psid) } else { None } } pub unsafe fn get_current_token_for_restriction() -> Result { let desired = TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | TOKEN_ADJUST_PRIVILEGES; let mut h: HANDLE = 0; #[link(name = "advapi32")] extern "system" { fn OpenProcessToken(ProcessHandle: HANDLE, DesiredAccess: u32, TokenHandle: *mut HANDLE) -> i32; } let ok = unsafe { OpenProcessToken(GetCurrentProcess(), desired, &mut h) }; if ok == 0 { return Err(anyhow::anyhow!("OpenProcessToken failed: {}", GetLastError())); } Ok(h) } pub unsafe fn get_logon_sid_bytes(h_token: HANDLE) -> Result> { unsafe fn scan_token_groups_for_logon(h: HANDLE) -> Option> { let mut needed: u32 = 0; GetTokenInformation(h, TokenGroups, std::ptr::null_mut(), 0, &mut needed); if needed == 0 { return None; } let mut buf: Vec = vec![0u8; needed as usize]; let ok = GetTokenInformation( h, TokenGroups, buf.as_mut_ptr() as *mut c_void, needed, &mut needed, ); if ok == 0 || (needed as usize) < std::mem::size_of::() { return None; } let group_count = std::ptr::read_unaligned(buf.as_ptr() as *const u32) as usize; let after_count = unsafe { buf.as_ptr().add(std::mem::size_of::()) } as usize; let align = std::mem::align_of::(); let aligned = (after_count + (align - 1)) & !(align - 1); let groups_ptr = aligned as *const SID_AND_ATTRIBUTES; for i in 0..group_count { let entry: SID_AND_ATTRIBUTES = unsafe { std::ptr::read_unaligned(groups_ptr.add(i)) }; if (entry.Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID { let sid = entry.Sid; let sid_len = unsafe { GetLengthSid(sid) }; if sid_len == 0 { return None; } let mut out = vec![0u8; sid_len as usize]; if unsafe { CopySid(sid_len, out.as_mut_ptr() as *mut c_void, sid) } == 0 { return None; } return Some(out); } } None } if let Some(v) = unsafe { scan_token_groups_for_logon(h_token) } { return Ok(v); } #[repr(C)] struct TOKEN_LINKED_TOKEN { linked_token: HANDLE, } const TOKEN_LINKED_TOKEN_CLASS: i32 = 19; let mut ln_needed: u32 = 0; unsafe { GetTokenInformation( h_token, TOKEN_LINKED_TOKEN_CLASS, std::ptr::null_mut(), 0, &mut ln_needed, ) }; if ln_needed >= std::mem::size_of::() as u32 { let mut ln_buf: Vec = vec![0u8; ln_needed as usize]; let ok = unsafe { GetTokenInformation( h_token, TOKEN_LINKED_TOKEN_CLASS, ln_buf.as_mut_ptr() as *mut c_void, ln_needed, &mut ln_needed, ) }; if ok != 0 { let lt: TOKEN_LINKED_TOKEN = unsafe { std::ptr::read_unaligned(ln_buf.as_ptr() as *const TOKEN_LINKED_TOKEN) }; if lt.linked_token != 0 { let res = unsafe { scan_token_groups_for_logon(lt.linked_token) }; unsafe { CloseHandle(lt.linked_token) }; if let Some(v) = res { return Ok(v); } } } } Err(anyhow::anyhow!("Logon SID not present on token")) } unsafe fn enable_single_privilege(h_token: HANDLE, name: &str) -> Result<()> { let mut luid = LUID { LowPart: 0, HighPart: 0 }; let ok = unsafe { LookupPrivilegeValueW(std::ptr::null(), to_wide(name).as_ptr(), &mut luid) }; if ok == 0 { return Err(anyhow::anyhow!("LookupPrivilegeValueW failed: {}", GetLastError())); } let mut tp: TOKEN_PRIVILEGES = unsafe { std::mem::zeroed() }; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = 0x00000002; let ok2 = unsafe { AdjustTokenPrivileges(h_token, 0, &tp, 0, std::ptr::null_mut(), std::ptr::null_mut()) }; if ok2 == 0 { return Err(anyhow::anyhow!("AdjustTokenPrivileges failed: {}", GetLastError())); } let err = unsafe { GetLastError() }; if err != 0 { return Err(anyhow::anyhow!("AdjustTokenPrivileges error {}", err)); } Ok(()) } /// Create a restricted token for the sandbox. /// /// When `write_restricted` is true, the token gets WRITE_RESTRICTED flag and /// restricting SIDs (logon SID, everyone SID, capability SID), which prevents /// write access except to paths explicitly allowed via DACL ACEs. /// /// When `write_restricted` is false (serve mode), the token only gets /// DISABLE_MAX_PRIVILEGE | LUA_TOKEN, allowing child processes to spawn /// grandchildren. File-system write protection is handled by DACL ACEs alone. pub unsafe fn create_restricted_token( psid_capability: *mut c_void, write_restricted: bool, ) -> Result<(HANDLE, *mut c_void)> { let base = get_current_token_for_restriction()?; let (flags, restricting_count, restricting_sids) = if write_restricted { let mut logon_sid_bytes = get_logon_sid_bytes(base) .map_err(|e| { CloseHandle(base); e })?; let psid_logon = logon_sid_bytes.as_mut_ptr() as *mut c_void; let mut everyone = world_sid().map_err(|e| { CloseHandle(base); e })?; let psid_everyone = everyone.as_mut_ptr() as *mut c_void; let mut entries: [SID_AND_ATTRIBUTES; 3] = std::mem::zeroed(); entries[0].Sid = psid_capability; entries[0].Attributes = 0; entries[1].Sid = psid_logon; entries[1].Attributes = 0; entries[2].Sid = psid_everyone; entries[2].Attributes = 0; (DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED, 3, entries) } else { (DISABLE_MAX_PRIVILEGE | LUA_TOKEN, 0, [std::mem::zeroed(); 3]) }; let mut new_token: HANDLE = 0; let ok = CreateRestrictedToken( base, flags, 0, std::ptr::null(), 0, std::ptr::null(), restricting_count, if restricting_count > 0 { restricting_sids.as_ptr() as *mut SID_AND_ATTRIBUTES } else { std::ptr::null() }, &mut new_token, ); CloseHandle(base); if ok == 0 { return Err(anyhow::anyhow!("CreateRestrictedToken failed: {}", GetLastError())); } enable_single_privilege(new_token, "SeChangeNotifyPrivilege") .map_err(|e| { CloseHandle(new_token); e })?; Ok((new_token, psid_capability)) }