工作区放在 C 盘根目录下,沙箱授权就报 Win32 错误 5,排查记录
1462 字约 5 分钟
摘要Windows 上的 Electron 应用,把工作区放在 C:\Projects 这类位置时,沙箱里的 PowerShell 命令全部失败,报 SetNamedSecurityInfoW 的 Win32 错误 5。放到用户目录下就好。原因是设置完整性标签需要 WRITE_OWNER,而 C 盘根目录下新建的文件夹默认没有。
我参与的那个开源 Electron 桌面应用,在 Windows 上有一个沙箱:Agent 要执行 PowerShell 命令,默认会先把命令放进受限环境里跑。这次是在做精简运行时文件的验证时,被一个报错绊住了。
只要把工作区放在 C:\ 根目录下自己新建的文件夹里,比如 C:\Projects\demo,沙箱里所有的 PowerShell 命令都失败,报错是这样的:
SetNamedSecurityInfoW failed (Win32 5): grantWrite(C:\...\workspace)
Win32 错误 5 是“拒绝访问”。在应用里选“允许一次”,也就是跳过沙箱,命令又能正常执行。
先排除:不是我们改的东西,也不是虚拟机
我是在 Windows 虚拟机里测的,得先排除是不是虚拟机特有的问题,也要排除是不是我正在做的精简引入的。助手核对了:发布的上一个正式包原样也能复现,和精简无关。之前另一个 PR 修的是 Electron 运行文件的 AppContainer 权限,处理的是另一个地方,也不是这个。
原因:设置完整性标签需要 WRITE_OWNER
助手去读了沙箱模块的说明。沙箱在受限执行之前,会给工作区根目录做两件事:写入一条允许写入的访问控制项,再给它打上 Low 完整性标签,让低完整性的受限进程能写这个目录。
标签存放在 SACL 里。写 SACL 需要 WRITE_OWNER 权限。模块的 README 也写得很直白:被授权的目录必须由调用者拥有并授予 WRITE_OWNER,只有“修改”权限的目录会直接失败,而不是悄悄跳过隔离。
这就解释了为什么放哪里有区别:
C:\根目录默认只给 Authenticated Users 授予“修改”。Administrators 的“完全控制”在没有提权的令牌里,只用来拒绝,不能用来授权。- 用户在根目录下新建的文件夹,归用户所有,所以有
WRITE_DAC,但没有WRITE_OWNER,授权就失败。 - 用户配置文件目录下的文件夹,继承的是用户自己的“完全控制”,不受影响。
Low 完整性标签是干什么的Windows 给每个进程和对象都标了一个完整性级别,低级别的进程默认不能写高级别的对象。沙箱想让受限进程能写工作区,就要把工作区降到 Low,让它们够得着。而改这个标签,本身需要对目录有
WRITE_OWNER。
在虚拟机里验证
助手把应用自带的 Node 和沙箱模块暂存进虚拟机,直接调用沙箱的授权函数,对新建的空目录测试,结果和推论一致:
| 工作区位置 | 结果 |
|---|---|
C:\<新建目录>\workspace | 失败,Win32 错误 5 |
| 同上,先由所有者给自己补上“完全控制” | 成功 |
%USERPROFILE%\<新建目录>\workspace | 成功 |
文档\<新建目录>\workspace | 成功 |
用的是 Windows 11 ARM64 虚拟机,x64 仿真,登录的是没有提权的管理员账户。测完虚拟机里的测试目录和暂存文件都已删除,虚拟机恢复为挂起状态。
处理方向:启动前预检,征得同意再修
助手整理了三个处理方向,我选了第一个:
- 在 Windows 上打开或新增工作区时先预检。缺少
WRITE_OWNER就说明原因,经用户确认后,由所有者给自己补上“完全控制”。 - 把这条底层错误翻译成可理解的提示和处理指引。
- 只在使用文档里说明工作区位置的要求。
实现时有几个取舍:
- 预检只读,不改任何东西。 它用
WRITE_DAC | WRITE_OWNER去打开工作区根目录,打得开就说明能授权。 - 缺权限时弹原生对话框,说明原因并询问要不要调整。同一个文件夹每次运行只问一次。
- 修复不自己写。 用户同意后,运行沙箱模块随包自带的诊断修复脚本,只改这一个工作区文件夹,不需要管理员权限,每项改动都有备份和恢复命令。
- 选“暂不调整”就什么都不改,原有的“报错之后经批准再修复”的路径仍然可用。
手动做的话,效果相当于由所有者执行下面这样一条 icacls,让自己对这个文件夹拥有完全控制,前面验证时补权限用的就是它:
icacls <工作区> /grant *<用户SID>:(OI)(CI)F
验证和没测到的
在虚拟机里,用真实的 Win32 调用和修复脚本跑通了三种情况,只有对话框是桩:
| 工作区 | 对话框 | 选择 | 结果 |
|---|---|---|---|
| C 盘根目录下新建 | 弹 1 次 | 调整权限 | 授权成功 |
| C 盘根目录下新建 | 弹 1 次 | 暂不调整 | 未改动,仍是原来的报错 |
| 用户目录下 | 不弹 | — | 正常 |
另外不存在的路径会返回“不可用”,不会提示。新增的测试加进了 CI,在 GitHub 的 Windows 机器上,新测试真实执行了 Win32 探测,而不是被跳过。
没测到的有四项:真实应用窗口里对话框的显示效果和弹出时机,完整 Windows 安装包的运行,原生 Windows x64(我用的是 ARM64 上的仿真),以及其他 NTFS 卷,比如 D:\ 根目录下的文件夹。这些我都写进了 PR 的“未测”一栏。
这个问题的一般规律
不只是这个应用。任何要给用户目录打低完整性标签的程序,都会碰到同一个条件:你得对这个目录有 WRITE_OWNER。把东西放在根目录下自己新建的文件夹里,是一个很容易踩到的位置,因为它看起来“是我自己建的”,实际只拿到了继承来的“修改”权限。
遇到 SetNamedSecurityInfoW 的 Win32 错误 5,先看目录的访问控制列表里,当前用户有没有“完全控制”,而不是只看它是不是你创建的。