Files
qiming/qimingclaw/crates/windows-sandbox-helper/src/token.rs

226 lines
8.3 KiB
Rust

//! 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<Vec<u8>> {
let mut size: u32 = 0;
CreateWellKnownSid(WIN_WORLD_SID, std::ptr::null_mut(), std::ptr::null_mut(), &mut size);
let mut buf: Vec<u8> = 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<HANDLE> {
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<Vec<u8>> {
unsafe fn scan_token_groups_for_logon(h: HANDLE) -> Option<Vec<u8>> {
let mut needed: u32 = 0;
GetTokenInformation(h, TokenGroups, std::ptr::null_mut(), 0, &mut needed);
if needed == 0 {
return None;
}
let mut buf: Vec<u8> = 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::<u32>() {
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::<u32>()) } as usize;
let align = std::mem::align_of::<SID_AND_ATTRIBUTES>();
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::<TOKEN_LINKED_TOKEN>() as u32 {
let mut ln_buf: Vec<u8> = 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))
}