#![allow(unsafe_op_in_unsafe_fn)]
use anyhow::{Result, anyhow};
use std::ffi::c_void;
use std::path::Path;
use windows_sys::Win32::Foundation::{ERROR_SUCCESS, HLOCAL, LocalFree};
use windows_sys::Win32::Security::{
ACL, ACCESS_ALLOWED_ACE, ACCESS_DENIED_ACE, ACE_HEADER, ACL_SIZE_INFORMATION,
AclSizeInformation, EqualSid, GetAce, GetAclInformation,
DACL_SECURITY_INFORMATION,
};
use windows_sys::Win32::Security::Authorization::{
EXPLICIT_ACCESS_W, GetNamedSecurityInfoW, SetEntriesInAclW,
SetNamedSecurityInfoW,
TRUSTEE_IS_SID, TRUSTEE_IS_UNKNOWN, TRUSTEE_W,
};
use windows_sys::Win32::Storage::FileSystem::{
FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
DELETE, FILE_DELETE_CHILD, FILE_APPEND_DATA, FILE_WRITE_DATA, FILE_WRITE_EA,
FILE_WRITE_ATTRIBUTES,
};
use crate::winutil::to_wide;
const ACCESS_ALLOWED_ACE_TYPE: u8 = 0;
const ACCESS_DENIED_ACE_TYPE: u8 = 1;
const INHERIT_ONLY_ACE: u8 = 0x08;
const CONTAINER_INHERIT_ACE: u32 = 0x2;
const OBJECT_INHERIT_ACE: u32 = 0x1;
const DENY_ACCESS: i32 = 3;
const SET_ACCESS: i32 = 2;
const REVOKE_ACCESS: i32 = 4;
const GENERIC_READ_MASK: u32 = 0x8000_0000;
const GENERIC_WRITE_MASK: u32 = 0x4000_0000;
const WRITE_ALLOW_MASK: u32 =
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE | FILE_DELETE_CHILD;
const WRITE_DENY_MASK: u32 = FILE_GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA
| FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | GENERIC_WRITE_MASK | DELETE | FILE_DELETE_CHILD;
const READ_DENY_MASK: u32 = FILE_GENERIC_READ | GENERIC_READ_MASK;
const DENY_ALL_MASK: u32 = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE
| DELETE | FILE_DELETE_CHILD;
unsafe fn get_security_dacl(path: &Path) -> Result<(*mut c_void, *mut ACL)> {
let wpath = to_wide(path);
let mut p_sd: *mut c_void = std::ptr::null_mut();
let mut p_dacl: *mut ACL = std::ptr::null_mut();
let code = GetNamedSecurityInfoW(
wpath.as_ptr(),
1, DACL_SECURITY_INFORMATION,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut p_dacl,
std::ptr::null_mut(),
&mut p_sd,
);
if code != ERROR_SUCCESS {
return Err(anyhow!("GetNamedSecurityInfoW failed: {code}"));
}
Ok((p_sd, p_dacl))
}
fn make_explicit_access(psid: *mut c_void, mask: u32, mode: i32, inheritance: u32) -> EXPLICIT_ACCESS_W {
let trustee = TRUSTEE_W {
pMultipleTrustee: std::ptr::null_mut(),
MultipleTrusteeOperation: 0,
TrusteeForm: TRUSTEE_IS_SID,
TrusteeType: TRUSTEE_IS_UNKNOWN,
ptstrName: psid as *mut u16,
};
let mut explicit: EXPLICIT_ACCESS_W = unsafe { std::mem::zeroed() };
explicit.grfAccessPermissions = mask;
explicit.grfAccessMode = mode;
explicit.grfInheritance = inheritance;
explicit.Trustee = trustee;
explicit
}
unsafe fn commit_dacl_change(
path: &Path,
p_old_dacl: *mut ACL,
p_sd: *mut c_void,
psid: *mut c_void,
mask: u32,
mode: i32,
inheritance: u32,
) -> Result<bool> {
let explicit = make_explicit_access(psid, mask, mode, inheritance);
let mut p_new_dacl: *mut ACL = std::ptr::null_mut();
let code2 = SetEntriesInAclW(1, &explicit, p_old_dacl, &mut p_new_dacl);
if code2 != ERROR_SUCCESS {
if !p_sd.is_null() {
LocalFree(p_sd as HLOCAL);
}
return Err(anyhow!("SetEntriesInAclW failed: {code2}"));
}
let code3 = SetNamedSecurityInfoW(
to_wide(path).as_ptr() as *mut u16,
1, DACL_SECURITY_INFORMATION,
std::ptr::null_mut(),
std::ptr::null_mut(),
p_new_dacl,
std::ptr::null_mut(),
);
let success = code3 == ERROR_SUCCESS;
if !p_new_dacl.is_null() {
LocalFree(p_new_dacl as HLOCAL);
}
if !p_sd.is_null() {
LocalFree(p_sd as HLOCAL);
}
if !success {
return Err(anyhow!("SetNamedSecurityInfoW failed: {code3}"));
}
Ok(true)
}
unsafe fn dacl_has_allow_mask(p_dacl: *mut ACL, psid: *mut c_void, desired_mask: u32) -> bool {
if p_dacl.is_null() {
return false;
}
let mut info: ACL_SIZE_INFORMATION = std::mem::zeroed();
if GetAclInformation(
p_dacl as *const ACL,
&mut info as *mut _ as *mut c_void,
std::mem::size_of::<ACL_SIZE_INFORMATION>() as u32,
AclSizeInformation,
) == 0
{
return false;
}
for i in 0..(info.AceCount as usize) {
let mut p_ace: *mut c_void = std::ptr::null_mut();
if GetAce(p_dacl as *const ACL, i as u32, &mut p_ace) == 0 {
continue;
}
let hdr = &*(p_ace as *const ACE_HEADER);
if hdr.AceType != ACCESS_ALLOWED_ACE_TYPE || (hdr.AceFlags & INHERIT_ONLY_ACE) != 0 {
continue;
}
let ace = &*(p_ace as *const ACCESS_ALLOWED_ACE);
let base = p_ace as usize;
let sid_ptr =
(base + std::mem::size_of::<ACE_HEADER>() + std::mem::size_of::<u32>()) as *mut c_void;
if EqualSid(sid_ptr, psid) != 0 && (ace.Mask & desired_mask) == desired_mask {
return true;
}
}
false
}
unsafe fn dacl_has_deny_mask(p_dacl: *mut ACL, psid: *mut c_void, deny_mask: u32) -> bool {
if p_dacl.is_null() {
return false;
}
let mut info: ACL_SIZE_INFORMATION = std::mem::zeroed();
if GetAclInformation(
p_dacl as *const ACL,
&mut info as *mut _ as *mut c_void,
std::mem::size_of::<ACL_SIZE_INFORMATION>() as u32,
AclSizeInformation,
) == 0
{
return false;
}
for i in 0..info.AceCount {
let mut p_ace: *mut c_void = std::ptr::null_mut();
if GetAce(p_dacl as *const ACL, i, &mut p_ace) == 0 {
continue;
}
let hdr = &*(p_ace as *const ACE_HEADER);
if hdr.AceType != ACCESS_DENIED_ACE_TYPE || (hdr.AceFlags & INHERIT_ONLY_ACE) != 0 {
continue;
}
let ace = &*(p_ace as *const ACCESS_DENIED_ACE);
let base = p_ace as usize;
let sid_ptr =
(base + std::mem::size_of::<ACE_HEADER>() + std::mem::size_of::<u32>()) as *mut c_void;
if EqualSid(sid_ptr, psid) != 0 && (ace.Mask & deny_mask) != 0 {
return true;
}
}
false
}
unsafe fn add_ace(
path: &Path,
psid: *mut c_void,
mask: u32,
mode: i32,
check_fn: impl Fn(*mut ACL, *mut c_void) -> bool,
) -> Result<bool> {
let (p_sd, p_dacl) = get_security_dacl(path)?;
if check_fn(p_dacl, psid) {
if !p_sd.is_null() {
LocalFree(p_sd as HLOCAL);
}
return Ok(false);
}
commit_dacl_change(path, p_dacl, p_sd, psid, mask, mode,
CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE)
}
pub unsafe fn add_allow_ace(path: &Path, psid: *mut c_void) -> Result<bool> {
add_ace(path, psid, WRITE_ALLOW_MASK, SET_ACCESS, |dacl, sid| {
dacl_has_allow_mask(dacl, sid, WRITE_ALLOW_MASK)
})
}
pub unsafe fn add_deny_write_ace(path: &Path, psid: *mut c_void) -> Result<bool> {
add_ace(path, psid, WRITE_DENY_MASK, DENY_ACCESS, |dacl, sid| {
dacl_has_deny_mask(dacl, sid, WRITE_DENY_MASK)
})
}
pub unsafe fn add_deny_read_ace(path: &Path, psid: *mut c_void) -> Result<bool> {
add_ace(path, psid, READ_DENY_MASK, DENY_ACCESS, |dacl, sid| {
dacl_has_deny_mask(dacl, sid, READ_DENY_MASK)
})
}
pub unsafe fn add_deny_all_ace(path: &Path, psid: *mut c_void) -> Result<bool> {
add_ace(path, psid, DENY_ALL_MASK, DENY_ACCESS, |dacl, sid| {
dacl_has_deny_mask(dacl, sid, DENY_ALL_MASK)
})
}
pub unsafe fn remove_sid_aces(path: &Path, psid: *mut c_void) -> Result<bool> {
let (p_sd, p_dacl) = get_security_dacl(path)?;
commit_dacl_change(path, p_dacl, p_sd, psid, 0, REVOKE_ACCESS, 0)
}
pub unsafe fn clean_session_acls(
work_dir: &Path,
write_cap_sids: &[*mut c_void],
whitelist_paths: &[&Path],
sensitive_deny_paths: &[&Path],
blacklist_paths: &[&Path],
blacklist_sid: Option<*mut c_void>,
) -> Result<()> {
for &sid in write_cap_sids {
remove_sid_aces(work_dir, sid)?;
}
for w_path in whitelist_paths {
for &sid in write_cap_sids {
remove_sid_aces(w_path, sid)?;
}
}
for s_path in sensitive_deny_paths {
for &sid in write_cap_sids {
remove_sid_aces(s_path, sid)?;
}
}
if let Some(bl_sid) = blacklist_sid {
for b_path in blacklist_paths {
remove_sid_aces(b_path, bl_sid)?;
}
}
Ok(())
}