Others

工作区放在 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 仿真,登录的是没有提权的管理员账户。测完虚拟机里的测试目录和暂存文件都已删除,虚拟机恢复为挂起状态。

处理方向:启动前预检,征得同意再修

助手整理了三个处理方向,我选了第一个:

  1. 在 Windows 上打开或新增工作区时先预检。缺少 WRITE_OWNER 就说明原因,经用户确认后,由所有者给自己补上“完全控制”。
  2. 把这条底层错误翻译成可理解的提示和处理指引。
  3. 只在使用文档里说明工作区位置的要求。

实现时有几个取舍:

  • 预检只读,不改任何东西。 它用 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,先看目录的访问控制列表里,当前用户有没有“完全控制”,而不是只看它是不是你创建的。

评论