window-sand-box 0.1.1

Windows 沙盒终端执行工具 — 使用受限令牌、ACL 和私有桌面隔离进程权限,提供安全的命令执行环境
//! ACL 权限管理
//!
//! 使用 Windows DACL 对文件/目录添加 Allow/Deny ACE,
//! 控制受限令牌对文件系统的访问。
//!
//! # Safety
//!
//! 本模块所有操作涉及 Windows ACL API,函数均标记为 `unsafe`。
//! 模块内 `unsafe fn` 内部直接调用其他 unsafe 操作是设计使然,
//! 调用者必须确保传入的 SID 指针和路径参数有效。

#![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;

// ACE 类型常量
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;
// SET_ACCESS 模式
const SET_ACCESS: i32 = 2;
// REVOKE_ACCESS 模式
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;

// ==================== 通用内部 helper ====================

/// 从路径获取安全描述符和 DACL
///
/// # Safety
/// - `p_sd` 和 `p_dacl` 指向的输出位置会被写入,调用者需确保后续释放 `p_sd`
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, // SE_FILE_OBJECT
        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))
}

/// 构建单条 EXPLICIT_ACCESS_W
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
}

/// 使用 SetEntriesInAclW + SetNamedSecurityInfoW 修改 DACL
///
/// # Safety
/// - `psid` 必须指向有效的 SID
/// - `path` 必须存在且可访问
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, // SE_FILE_OBJECT
        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)
}

/// 检查 SID 在 DACL 中是否有允许 ACE 覆盖指定掩码
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
}

/// 检查 SID 在 DACL 中是否有拒绝 ACE 覆盖指定掩码
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
}

// ==================== 公开 API ====================

/// 通用 helper:为路径添加 ACE(允许或拒绝)
///
/// 如果 `check_fn` 返回 `true`,表示 ACE 已存在,跳过操作。
///
/// # Safety
/// - `psid` 必须指向有效的 SID
/// - `path` 必须存在
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)
}

/// 为路径添加 Allow ACE(允许指定 SID 读写执行删除)
///
/// # Safety
/// psid 必须指向有效的 SID,path 必须存在
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)
    })
}

/// 为路径添加 Deny Write ACE(拒绝指定 SID 写/追加/删除)
///
/// # Safety
/// psid 必须指向有效的 SID,path 必须存在
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)
    })
}

/// 为路径添加 Deny Read ACE(拒绝指定 SID 读取)
///
/// # Safety
/// psid 必须指向有效的 SID,path 必须存在
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)
    })
}

/// 为路径添加 Deny ALL ACE(拒绝指定 SID 读+写+执行+删除)
///
/// 用于黑名单路径,完全禁止访问。
/// 如果该 SID 的 Deny ALL ACE 已存在,则跳过写入(幂等)。
///
/// # Safety
/// psid 必须指向有效的 SID,path 必须存在
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)
    })
}

/// 从路径的 DACL 中移除指定 SID 的所有 ACE 条目
///
/// 使用 `SetEntriesInAclW` 的 `REVOKE_ACCESS` 模式,
/// 删除该 SID 相关的 Allow ACE 和 Deny ACE。
///
/// # Safety
/// `psid` 必须指向有效的 SID
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)
}

/// 清理会话的 ACL 规则
///
/// 反向操作 `apply_session_acls`:
/// - 从工作目录移除所有写入 SID 的 Allow ACE
/// - 从白名单路径移除所有写入 SID 的 Allow ACE
/// - 从敏感路径移除所有写入 SID 的 Deny Write ACE
///
/// # Safety
/// 所有 SID 指针必须有效
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<()> {
    // 1. 从工作目录移除 Allow ACE
    for &sid in write_cap_sids {
        remove_sid_aces(work_dir, sid)?;
    }

    // 2. 从白名单路径移除 Allow ACE
    for w_path in whitelist_paths {
        for &sid in write_cap_sids {
            remove_sid_aces(w_path, sid)?;
        }
    }

    // 3. 从敏感路径移除 Deny Write ACE
    for s_path in sensitive_deny_paths {
        for &sid in write_cap_sids {
            remove_sid_aces(s_path, sid)?;
        }
    }

    // 4. 从黑名单路径移除 Deny ALL ACE
    if let Some(bl_sid) = blacklist_sid {
        for b_path in blacklist_paths {
            remove_sid_aces(b_path, bl_sid)?;
        }
    }

    Ok(())
}