chore: initialize qiming workspace repository
This commit is contained in:
225
qimingclaw/crates/windows-sandbox-helper/src/token.rs
Normal file
225
qimingclaw/crates/windows-sandbox-helper/src/token.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
//! 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))
|
||||
}
|
||||
Reference in New Issue
Block a user