mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-16 07:16:36 +08:00
fix(sandbox): grant default Windows read access
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
> upstream: `/Users/coso/Documents/dev/rust/codex`(当前观测 HEAD:`99660ab3c7b861c916e467581fa9b8723504d66b`)
|
||||
> checklist progress: `44/46 (95.7%)`;这是未加权任务数,不等于交付完成度,剩余项包含 Windows 平台安全、live/verifier 与正式分发硬门槛。
|
||||
|
||||
> 当前主目标(2026-08-26):关闭 C-3 Windows restricted execution 的真机证据。Codex current 的 per-user Windows Firewall 非 loopback/loopback 出站阻断、offline/online sandbox identity、`LimeSandboxUsers` 普通 token ACL、短生命周期 capability SID restricted ACL、clean-runner setup/read-back、原始 DACL rollback、sandbox-account command runner 与七项 evidence collector 已进入唯一 current owner。真机已证明 runner transport/MSVC 编译成功,但 restricted child 仍在无输出前 exit `1`;当前下一刀是补齐 Codex setup 对 Windows 内置 `Users` 组成员身份的强制与 read-back,再重跑真实 Windows/MSVC 矩阵。在证据通过前保持 `Planned/enforced=false`。
|
||||
> 当前主目标(2026-08-26):关闭 C-3 Windows restricted execution 的真机证据。Codex current 的 per-user Windows Firewall 非 loopback/loopback 出站阻断、offline/online sandbox identity、`LimeSandboxUsers` 普通 token ACL、短生命周期 capability SID restricted ACL、clean-runner setup/read-back、原始 DACL rollback、sandbox-account command runner 与七项 evidence collector 已进入唯一 current owner。真机已证明 runner transport/MSVC 编译和内置 `Users` setup/read-back 成功,但 restricted child 仍在无输出前 exit `1`;当前下一刀是补齐 Codex 对 Windows 平台目录与当前用户 profile 非敏感子目录的默认 RX ACL 刷新/read-back,再重跑真实 Windows/MSVC 矩阵。在证据通过前保持 `Planned/enforced=false`。
|
||||
> 参考原则:`/Users/coso/Documents/dev/rust/codex` 是协议、Thread/Turn/Item、runtime lifecycle、tool/approval 和 GUI 语义的直接实现参考源;Lime 只在产品范围、provider 和桌面宿主边界做显式适配,不以旧矩阵或同名 legacy API 代替对齐。
|
||||
> 目标:把 Lime 的产品主链、协议字段、GUI 工作区、执行安全和真实 Desktop 证据推进到当前 Codex Desktop 语义;不恢复已退役 runtime、v0 或 `agentSession/*` 兼容主链。
|
||||
|
||||
@@ -68,7 +68,7 @@ Renderer typed gateway
|
||||
| MCP resource origin | Workspace MCP App 从 canonical Item 的 `appContext`/resource URI 建立 WebContentsView surface,并以 `originCallId` 请求同一 Thread authority;`codex_apps` 不依赖本地 Plugin 安装态 | 已完成 canonical producer/cold restore、bounded provenance、link/account revalidation、稳定组件回归和独立 Electron Gate B;证据见 `.lime/qc/gui-evidence/mcp-resource-origin-electron-gate-b/` |
|
||||
| Environment lifecycle | `TaskCenterEnvironmentPanel` + `useThreadEnvironmentLifecycleStatus` 已消费 `environment/status` 与 typed connected/disconnected notification,稳定 DOM 覆盖环境 identity/status | 已完成五语言、组件回归和独立 Electron Gate B:真实远端 Environment 断线与有限重连在同一 canonical Thread 可见,mock/console/page/invoke error 为 0 |
|
||||
|
||||
当前执行顺序:Provider capability、Queue(含重复正文视觉收口)、Project、Strict Review、D-2 MCP event stream/resource origin、D-3 Environment、Thread Revert、Browser/Computer Use requirements 产品裁决、Thread/Item current 字段/恢复复核、Agent current 聚合门禁、本机 macOS packaged candidate、`1.135.0` 正式 macOS 分发和 Windows Squirrel N-1 packaged Gate B 已关闭;C-3 Windows restricted-token、ACL/Job、ConPTY、elevated setup、offline/online account、Firewall/WFP、readiness read-back、本地 fail-closed 单测和真实 packaged evidence collector 已进入 current owner。当前先对齐内置 `Users` 组成员身份并重跑七项 Windows/MSVC 安全矩阵;矩阵 `7/7` 后再验证 packaged runner digest。live provider/Pier/DeepSWE verifier 和覆盖当前工作树的正式分发同样等待外部环境。每完成一项即更新本计划与运行证据,不批量提前勾选。
|
||||
当前执行顺序:Provider capability、Queue(含重复正文视觉收口)、Project、Strict Review、D-2 MCP event stream/resource origin、D-3 Environment、Thread Revert、Browser/Computer Use requirements 产品裁决、Thread/Item current 字段/恢复复核、Agent current 聚合门禁、本机 macOS packaged candidate、`1.135.0` 正式 macOS 分发和 Windows Squirrel N-1 packaged Gate B 已关闭;C-3 Windows restricted-token、ACL/Job、ConPTY、elevated setup、offline/online account、Firewall/WFP、readiness read-back、本地 fail-closed 单测和真实 packaged evidence collector 已进入 current owner。当前先对齐默认平台/profile read roots 并重跑七项 Windows/MSVC 安全矩阵;矩阵 `7/7` 后再验证 packaged runner digest。live provider/Pier/DeepSWE verifier 和覆盖当前工作树的正式分发同样等待外部环境。每完成一项即更新本计划与运行证据,不批量提前勾选。
|
||||
|
||||
> 最新 Queue/CI 收口状态(2026-08-26):Queue 状态条已删除 submission preview 与 `thread-queue-items` 正文列表,canonical 时间线成为唯一正文 owner;五语言只保留标题、loading、数量和刷新失败状态。组件、Gate B 与 Sherpa 定向回归 25/25,`npm run i18n:check` 保持 100%。真实 Queue Gate B 已通过:`timelineMarkerCount=1`、`queueStatusContainsMarker=false`、`queueItemsPresent=false`、47 次真实 Electron IPC、`mockFallbackHitCount=0`、console/page/invoke error 均为 0,证据见 `.lime/qc/gui-evidence/thread-queue-electron-gate-b/thread-queue-electron-gate-b-summary.json`。根 `package.json`/lock importer 已声明直接 `ws@8.18.3`,修复 Quality CI Frontend Full 的 `Failed to resolve import "ws"`;Windows Shell Runtime 上限调为 60 分钟,restricted security matrix 已前移到 Sherpa/sidecar 重型阶段之前,Sherpa 清理/解压前后输出阶段日志。`smoke:agent-runtime-current-fixture` 完整通过,`npm run verify:gui-smoke` Shell-01 21/21、contracts 299 checks、typecheck、scripts/legacy governance 均通过;App Server Windows sandbox fail-closed 定向单测 4/4 通过。上述本机测试不替代 Windows restricted matrix 或 live verifier;正式 macOS 与 Squirrel 证据由独立 Release run `32864956267` 证明。
|
||||
|
||||
@@ -394,6 +394,7 @@ Renderer typed gateway
|
||||
- 2026-08-26:Windows Quality run `32957046834`(SHA `e147a8731`)完成 setup/MSVC 编译,但六项 restricted case 在 child spawn 前统一返回 `CreateFileW(Windows sandbox NUL): ERROR_ACCESS_DENIED`,七项仍为 `1/7`。该证据证明低权限 sandbox-account runner 不能持有 NUL 的 `WRITE_DAC`;修复将 capability NUL lease 移至宿主,在 `CreateProcessWithLogonW` 前安装 ACE,并与 workspace ACL lease 一起由 supervisor/reaper 持有到 runner 及 descendants 退出,sandbox account 不获得全局 DACL 管理权。定向 related Rust `373/373`、fmt/diff check 通过;待下一轮 Windows schema v3 真机重跑,完成度/readiness 不变。
|
||||
- 2026-08-26:Windows Quality run `32957785741`(SHA `a2a6caa85`,artifact `9602756033`)在 setup 阶段编译 `tool-runtime` 失败,七项矩阵未执行。MSVC `E0277` 明确指向 `NullDeviceLease` 持有 `LocalSid(*mut c_void)`,因此无法跨 supervisor/reaper 线程移动;修复为 lease 仅保存 capability SID 字符串,安装/撤销 ACE 时在当前线程短暂解析 `LocalSid`,不添加 `unsafe impl Send`。同时移除日志暴露的 Windows-only unused import;本机 related Rust 反向依赖链与 `tool-runtime 373/373`、rustfmt、`git diff --check` 通过。该 run 继续是 `failedStage=setup`、矩阵 `0/7`,不改变 `44/46 (95.7%)` 或 `Planned/enforced=false`。
|
||||
- 2026-08-26:Windows Quality run `32958651645`(SHA `37f385db6`,artifact `9603104216`)已通过 setup 和 MSVC 编译,schema v3 七项矩阵完整执行为 `1/7`:Everyone-write audit 通过,其余六项 restricted child 仍在无输出前 exit `1`,terminate 场景因 runner 提前结束返回 `ControlClosed`。该 run 证明 NUL lease 的 `Send` 编译问题已关闭,但 NUL ACE 未解决 child 启动。继续对照 Codex `setup_main::sandbox_users` 发现 Lime 遗漏将 sandbox account 加入 Windows 内置 `Users`(SID `S-1-5-32-545`);当前修复对本地化组名执行 setup 幂等加入、readiness 与每次 runner 启动 read-back,避免子进程缺少仅授权给内置 Users 的系统资源访问。完成度继续为 `44/46 (95.7%)`,readiness 继续 `Planned/enforced=false`。
|
||||
- 2026-08-26:Windows Quality run `32960057901`(SHA `f1bec2328`,artifact `9603664649`)再次通过 setup/MSVC 编译并完整执行 schema v3,但矩阵仍为 `1/7`;六项 restricted child 继续无有效输出 exit `1`,说明内置 `Users` 成员身份已正确写入/read-back,却不是唯一缺口。继续逐段对照 Codex `gather_read_roots -> run_setup_refresh -> apply_read_acls` 后确认 Lime 尚未刷新 `C:\Windows`、Program Files/ProgramData 与当前用户 profile 非敏感子目录的默认 RX ACL;这会让已创建的 restricted PowerShell 在加载 DLL、模块或 AppData 时静默失败。当前修复将该行为收归 Windows setup、补 read-back 并提升 setup marker version 强制旧安装重跑;完成度/readiness 不变。
|
||||
|
||||
## 7. 阻塞与避让
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ use uuid::Uuid;
|
||||
#[cfg(windows)]
|
||||
#[path = "windows_setup/accounts.rs"]
|
||||
mod accounts;
|
||||
#[cfg(windows)]
|
||||
#[path = "windows_setup/read_access.rs"]
|
||||
mod read_access;
|
||||
|
||||
#[cfg(windows)]
|
||||
use accounts::{
|
||||
@@ -21,8 +24,10 @@ pub(crate) use accounts::{
|
||||
resolve_windows_account_sid, verify_windows_sandbox_group_membership,
|
||||
windows_sandbox_users_group_sid,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use read_access::{ensure_default_read_access, validate_default_read_access};
|
||||
|
||||
pub const WINDOWS_SANDBOX_SETUP_VERSION: u32 = 1;
|
||||
pub const WINDOWS_SANDBOX_SETUP_VERSION: u32 = 2;
|
||||
pub const WINDOWS_SANDBOX_OFFLINE_USERNAME: &str = "LimeSandboxOffline";
|
||||
pub const WINDOWS_SANDBOX_ONLINE_USERNAME: &str = "LimeSandboxOnline";
|
||||
pub const WINDOWS_SANDBOX_USERS_GROUP: &str = "LimeSandboxUsers";
|
||||
@@ -146,6 +151,7 @@ pub fn run_windows_sandbox_setup(
|
||||
ensure_local_group_member(WINDOWS_SANDBOX_USERS_GROUP, WINDOWS_SANDBOX_ONLINE_USERNAME)?;
|
||||
ensure_builtin_users_group_member(WINDOWS_SANDBOX_OFFLINE_USERNAME)?;
|
||||
ensure_builtin_users_group_member(WINDOWS_SANDBOX_ONLINE_USERNAME)?;
|
||||
ensure_default_read_access()?;
|
||||
ensure_sandbox_accounts_null_device_access()?;
|
||||
|
||||
let offline_blob = dpapi_protect(offline_password.as_bytes())?;
|
||||
@@ -493,6 +499,7 @@ fn validate_protected_password(label: &str, value: &str) -> Result<(), String> {
|
||||
fn validate_windows_runtime_artifacts(artifacts: &ValidatedSetupArtifacts) -> Result<(), String> {
|
||||
validate_windows_sandbox_group_membership(WINDOWS_SANDBOX_OFFLINE_USERNAME)?;
|
||||
validate_windows_sandbox_group_membership(WINDOWS_SANDBOX_ONLINE_USERNAME)?;
|
||||
validate_default_read_access()?;
|
||||
validate_sandbox_accounts_null_device_access()?;
|
||||
validate_windows_sandbox_user("offline", &artifacts.users.offline)?;
|
||||
validate_windows_sandbox_user("online", &artifacts.users.online)?;
|
||||
@@ -746,7 +753,7 @@ mod tests {
|
||||
let marker_path = windows_sandbox_setup_marker_path(root.path());
|
||||
fs::write(
|
||||
marker_path,
|
||||
r#"{"version":2,"offline_username":"Other","online_username":"LimeSandboxOnline"}"#,
|
||||
r#"{"version":3,"offline_username":"Other","online_username":"LimeSandboxOnline"}"#,
|
||||
)
|
||||
.expect("rewrite marker");
|
||||
|
||||
@@ -765,7 +772,7 @@ mod tests {
|
||||
let marker_path = windows_sandbox_setup_marker_path(root.path());
|
||||
fs::write(
|
||||
marker_path,
|
||||
r#"{"version":1,"offline_username":"Other","online_username":"LimeSandboxOnline"}"#,
|
||||
r#"{"version":2,"offline_username":"Other","online_username":"LimeSandboxOnline"}"#,
|
||||
)
|
||||
.expect("rewrite marker");
|
||||
|
||||
@@ -784,7 +791,7 @@ mod tests {
|
||||
let marker_path = windows_sandbox_setup_marker_path(root.path());
|
||||
fs::write(
|
||||
marker_path,
|
||||
r#"{"version":1,"offline_username":"LimeSandboxOffline","online_username":"LimeSandboxOnline","unexpected":true}"#,
|
||||
r#"{"version":2,"offline_username":"LimeSandboxOffline","online_username":"LimeSandboxOnline","unexpected":true}"#,
|
||||
)
|
||||
.expect("rewrite marker");
|
||||
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
use super::accounts::windows_sandbox_users_group_sid;
|
||||
use super::to_wide;
|
||||
use std::ffi::c_void;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
use windows_sys::Win32::Foundation::{LocalFree, ERROR_SUCCESS, HLOCAL};
|
||||
use windows_sys::Win32::Security::Authorization::{
|
||||
ConvertStringSidToSidW, GetEffectiveRightsFromAclW, GetNamedSecurityInfoW, SetEntriesInAclW,
|
||||
SetNamedSecurityInfoW, EXPLICIT_ACCESS_W, GRANT_ACCESS, SE_FILE_OBJECT, TRUSTEE_IS_SID,
|
||||
TRUSTEE_IS_UNKNOWN, TRUSTEE_W,
|
||||
};
|
||||
use windows_sys::Win32::Security::{ACL, DACL_SECURITY_INFORMATION};
|
||||
use windows_sys::Win32::Storage::FileSystem::{FILE_GENERIC_EXECUTE, FILE_GENERIC_READ};
|
||||
|
||||
const BUILTIN_USERS_SID: &str = "S-1-5-32-545";
|
||||
const AUTHENTICATED_USERS_SID: &str = "S-1-5-11";
|
||||
const EVERYONE_SID: &str = "S-1-1-0";
|
||||
const PROFILE_ROOT_EXCLUSIONS: &[&str] = &[
|
||||
".ssh",
|
||||
".tsh",
|
||||
".brev",
|
||||
".gnupg",
|
||||
".aws",
|
||||
".azure",
|
||||
".kube",
|
||||
".docker",
|
||||
".config",
|
||||
".npm",
|
||||
".pki",
|
||||
".terraform.d",
|
||||
];
|
||||
|
||||
pub(super) fn ensure_default_read_access() -> io::Result<()> {
|
||||
let sandbox_group = LocalSid::parse(&windows_sandbox_users_group_sid()?)?;
|
||||
let builtin_users = LocalSid::parse(BUILTIN_USERS_SID)?;
|
||||
let authenticated_users = LocalSid::parse(AUTHENTICATED_USERS_SID)?;
|
||||
let everyone = LocalSid::parse(EVERYONE_SID)?;
|
||||
let readable_principals = [
|
||||
sandbox_group.raw(),
|
||||
builtin_users.raw(),
|
||||
authenticated_users.raw(),
|
||||
everyone.raw(),
|
||||
];
|
||||
|
||||
for root in default_read_roots()? {
|
||||
with_path_dacl(&root, |dacl| {
|
||||
if principals_allow_read_execute(dacl, &readable_principals)? {
|
||||
return Ok(());
|
||||
}
|
||||
grant_read_execute(&root, dacl, sandbox_group.raw())
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn validate_default_read_access() -> Result<(), String> {
|
||||
let sandbox_group_sid = windows_sandbox_users_group_sid()
|
||||
.map_err(|error| format!("sandbox group SID validation failed: {error}"))?;
|
||||
let sandbox_group = LocalSid::parse(&sandbox_group_sid)
|
||||
.map_err(|error| format!("sandbox group SID validation failed: {error}"))?;
|
||||
let builtin_users = LocalSid::parse(BUILTIN_USERS_SID)
|
||||
.map_err(|error| format!("built-in Users SID validation failed: {error}"))?;
|
||||
let authenticated_users = LocalSid::parse(AUTHENTICATED_USERS_SID)
|
||||
.map_err(|error| format!("Authenticated Users SID validation failed: {error}"))?;
|
||||
let everyone = LocalSid::parse(EVERYONE_SID)
|
||||
.map_err(|error| format!("Everyone SID validation failed: {error}"))?;
|
||||
let principals = [
|
||||
sandbox_group.raw(),
|
||||
builtin_users.raw(),
|
||||
authenticated_users.raw(),
|
||||
everyone.raw(),
|
||||
];
|
||||
|
||||
for root in default_read_roots().map_err(|error| error.to_string())? {
|
||||
let allowed = with_path_dacl(&root, |dacl| {
|
||||
principals_allow_read_execute(dacl, &principals)
|
||||
})
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"default read root {} validation failed: {error}",
|
||||
root.display()
|
||||
)
|
||||
})?;
|
||||
if !allowed {
|
||||
return Err(format!(
|
||||
"Windows sandbox cannot read and execute default root {}",
|
||||
root.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_read_roots() -> io::Result<Vec<PathBuf>> {
|
||||
let mut roots = Vec::new();
|
||||
for (name, fallback) in [
|
||||
("SystemRoot", Some(r"C:\Windows")),
|
||||
("ProgramFiles", Some(r"C:\Program Files")),
|
||||
("ProgramFiles(x86)", Some(r"C:\Program Files (x86)")),
|
||||
("ProgramData", Some(r"C:\ProgramData")),
|
||||
] {
|
||||
let path = std::env::var_os(name)
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| fallback.map(PathBuf::from));
|
||||
if let Some(path) = path.filter(|path| path.exists()) {
|
||||
roots.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(profile) = std::env::var_os("USERPROFILE").map(PathBuf::from) {
|
||||
let entries = std::fs::read_dir(&profile).map_err(|error| {
|
||||
io::Error::new(
|
||||
error.kind(),
|
||||
format!(
|
||||
"failed to enumerate Windows user profile {}: {error}",
|
||||
profile.display()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|error| {
|
||||
io::Error::new(
|
||||
error.kind(),
|
||||
format!(
|
||||
"failed to enumerate Windows user profile {}: {error}",
|
||||
profile.display()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if !PROFILE_ROOT_EXCLUSIONS
|
||||
.iter()
|
||||
.any(|excluded| name.eq_ignore_ascii_case(excluded))
|
||||
{
|
||||
roots.push(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
roots.sort_by(|left, right| {
|
||||
left.to_string_lossy()
|
||||
.to_ascii_lowercase()
|
||||
.cmp(&right.to_string_lossy().to_ascii_lowercase())
|
||||
});
|
||||
roots.dedup_by(|left, right| {
|
||||
left.to_string_lossy()
|
||||
.eq_ignore_ascii_case(right.to_string_lossy().as_ref())
|
||||
});
|
||||
Ok(roots)
|
||||
}
|
||||
|
||||
fn with_path_dacl<T>(
|
||||
path: &Path,
|
||||
operation: impl FnOnce(*mut ACL) -> io::Result<T>,
|
||||
) -> io::Result<T> {
|
||||
let path_wide = to_wide(path.as_os_str());
|
||||
let mut descriptor = ptr::null_mut();
|
||||
let mut dacl = ptr::null_mut();
|
||||
let result = unsafe {
|
||||
GetNamedSecurityInfoW(
|
||||
path_wide.as_ptr(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
&mut dacl,
|
||||
ptr::null_mut(),
|
||||
&mut descriptor,
|
||||
)
|
||||
};
|
||||
if result != ERROR_SUCCESS {
|
||||
return Err(win32_result_error(
|
||||
&format!("GetNamedSecurityInfoW({})", path.display()),
|
||||
result,
|
||||
));
|
||||
}
|
||||
if dacl.is_null() {
|
||||
unsafe {
|
||||
LocalFree(descriptor as HLOCAL);
|
||||
}
|
||||
return Err(io::Error::other(format!(
|
||||
"Windows default read root {} has no DACL",
|
||||
path.display()
|
||||
)));
|
||||
}
|
||||
let operation_result = operation(dacl);
|
||||
unsafe {
|
||||
if !descriptor.is_null() {
|
||||
LocalFree(descriptor as HLOCAL);
|
||||
}
|
||||
}
|
||||
operation_result
|
||||
}
|
||||
|
||||
fn principals_allow_read_execute(dacl: *mut ACL, principals: &[*mut c_void]) -> io::Result<bool> {
|
||||
let required = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;
|
||||
for principal in principals {
|
||||
let trustee = trustee(*principal);
|
||||
let mut effective = 0;
|
||||
let result = unsafe { GetEffectiveRightsFromAclW(dacl, &trustee, &mut effective) };
|
||||
if result != ERROR_SUCCESS {
|
||||
return Err(win32_result_error(
|
||||
"GetEffectiveRightsFromAclW(default read root)",
|
||||
result,
|
||||
));
|
||||
}
|
||||
if effective & required == required {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn grant_read_execute(path: &Path, dacl: *mut ACL, sandbox_group: *mut c_void) -> io::Result<()> {
|
||||
let entry = EXPLICIT_ACCESS_W {
|
||||
grfAccessPermissions: FILE_GENERIC_READ | FILE_GENERIC_EXECUTE,
|
||||
grfAccessMode: GRANT_ACCESS,
|
||||
grfInheritance: 0x3,
|
||||
Trustee: trustee(sandbox_group),
|
||||
};
|
||||
let mut updated_dacl = ptr::null_mut();
|
||||
let result = unsafe { SetEntriesInAclW(1, &entry, dacl, &mut updated_dacl) };
|
||||
if result != ERROR_SUCCESS {
|
||||
return Err(win32_result_error(
|
||||
"SetEntriesInAclW(default read root)",
|
||||
result,
|
||||
));
|
||||
}
|
||||
if updated_dacl.is_null() {
|
||||
return Err(io::Error::other(
|
||||
"SetEntriesInAclW(default read root) returned a null DACL",
|
||||
));
|
||||
}
|
||||
let path_wide = to_wide(path.as_os_str());
|
||||
let applied = unsafe {
|
||||
SetNamedSecurityInfoW(
|
||||
path_wide.as_ptr() as *mut u16,
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
updated_dacl,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
LocalFree(updated_dacl as HLOCAL);
|
||||
}
|
||||
if applied == ERROR_SUCCESS {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(win32_result_error(
|
||||
&format!("SetNamedSecurityInfoW({})", path.display()),
|
||||
applied,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn trustee(sid: *mut c_void) -> TRUSTEE_W {
|
||||
TRUSTEE_W {
|
||||
pMultipleTrustee: ptr::null_mut(),
|
||||
MultipleTrusteeOperation: 0,
|
||||
TrusteeForm: TRUSTEE_IS_SID,
|
||||
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
||||
ptstrName: sid as *mut u16,
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalSid(*mut c_void);
|
||||
|
||||
impl LocalSid {
|
||||
fn parse(value: &str) -> io::Result<Self> {
|
||||
let mut sid = ptr::null_mut();
|
||||
let value = to_wide(value);
|
||||
if unsafe { ConvertStringSidToSidW(value.as_ptr(), &mut sid) } == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(Self(sid))
|
||||
}
|
||||
|
||||
fn raw(&self) -> *mut c_void {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LocalSid {
|
||||
fn drop(&mut self) {
|
||||
if !self.0.is_null() {
|
||||
unsafe {
|
||||
LocalFree(self.0 as HLOCAL);
|
||||
}
|
||||
self.0 = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn win32_result_error(context: &str, code: u32) -> io::Error {
|
||||
io::Error::other(format!(
|
||||
"{context} failed: {}",
|
||||
io::Error::from_raw_os_error(code as i32)
|
||||
))
|
||||
}
|
||||
Reference in New Issue
Block a user