[win32][sandbox] 打包桌面端(GUI 子系统宿主)下,受限令牌子进程 100% 以 0xC0000142(STATUS_DLL_INIT_FAILED)死亡 #8721
Replies: 4 comments
|
?????? OS????????????,?????????? ??
????
???????,?????? console ???? |
|
Thanks @sshnuke3 — the Windows Server 2022 / build 20348 datapoint and the ACE string are genuinely useful, and they extend this beyond Windows 11. Two clarifications, because your "cannot reproduce" is actually consistent with this report. 1. The layer that must own a console is the mediator, not the target. The
The path where you see 100% success — 2. There is a second, independent root cause on this same exit code: #8497 — Proposed 2×2 to separate them — if you can run this on your box (Server 2022 / 20348) it would settle both threads:
If the GUI row still fails under Note that your suggested workaround — run via a plain Related threads: #8497 (independent root cause, same exit code) · Astral-Dream/dsh-windows-sandbox-0xC0000142-workaround#1 (independent reproduction + workaround) · sjh9714/dsh-win32#91 (CI-side occurrence of this exit code).
|
|
Follow-up with new measured data on this thread, directly relevant to the exit code under discussion. 1. The initial post now carries an English executive summary at the top (symptom / root cause / decisive evidence / the two measured fixes), so the mechanism is readable without the Chinese body. 2. I ran #8497's A/B on Windows 11 26200 — and could not reproduce it.
The restriction was genuinely enforced (workspace write allowed; 3. Why the capability-SID substitution is not the whole story: 4. Correction to my own earlier reply on this thread. I described #8497 as "an independent root cause of this same exit code". After the 26200 run I would phrase it more carefully: it is an independent reported root cause on Windows 10 19045 that does not reproduce on 26200, so it is platform-specific rather than universal. The two remain non-exclusive in practice — a machine whose ambient Default DACL lacks SYSTEM/Administrators, or a host process that owns no console, will still fail for its own reason. 5. Therefore: both fixes are still warranted, because they close different failure modes — (a) Measured 2×2, for anyone who wants the table filled in — on 26200, both rows of the Probe sources and the full readout are in this thread's repository-adjacent notes; happy to re-run any cell with different parameters if the team wants a specific configuration characterized. |
|
New measured data that fills in all four cells of the 2×2 and refines the root-cause statement in the initial post. All runs are on Windows 11 26200 (registry The matrix (target always
|
| Mediator interpreter | read-only | workspace-write (capability SID) | workspace-write + temp SID |
|---|---|---|---|
GUI-subsystem image (DeepSeek Harness.exe + ELECTRON_RUN_AS_NODE=1) |
✅ 0x00000000 SANDBOX_SHELL_CHECK |
❌ 0xC0000142, no output |
❌ 0xC0000142, no output |
console-subsystem node.exe |
✅ | ✅ | ✅ |
So the failure needs both conditions at once — a mediator with no console and workspace-write. It is not "every sandbox mode under a GUI host", and that matters for the fix's scope and for anyone reproducing this: a plain node.exe mediator never shows it.
Where exactly it dies
spawn() returns normally every time (spawn() returned pid=20700, then the wait failed) — the child is created and dies during DLL initialization, so this is STATUS_DLL_INIT_FAILED inside the child, not a failure of the spawn call.
| Variation (GUI mediator, workspace-write unless noted) | Result |
|---|---|
| baseline workspace-write | ❌ 0xC0000142 |
AllocConsole() in the mediator before spawn |
✅ 0x00000000 |
| read-only baseline | ✅ |
read-only + FreeConsole() |
✅ still succeeds |
workspace-write with tempWriteSid set to the workspace SID |
❌ still fails |
workspace-write with stdio: 'inherit' |
❌ still fails |
Two of those are worth stating plainly:
- Giving the mediator a console fixes it (
AllocConsole()before the spawn, 1/1 here and consistent with the earlier 3/3). - Discarding the mediator's console does not break read-only — so "a console is required" is too simple. The real shape is that the workspace-write token/DACL path is unsafe when the mediator has no console; read-only's path is safe either way.
The tempWriteSid and stdio variants rule out the specific SID value and the stdio mode as the trigger.
Consequence for the proposed fix
Both fixes in the initial post remain correct, but the scope should be stated as workspace-write rather than all modes: (1) AllocConsole() (hidden via STARTF_USESHOWWINDOW) in runner.js main() before sandbox.spawn, or (2) resolve a console-subsystem interpreter at dsh-sandbox-local/lib/index.js:543 instead of process.execPath.
Relation to the Default DACL thread
This also separates cleanly from #8497. In this run the read-only ACE[0] is Everyone and the workspace-write ACE[0] is the capability SID — yet read-only succeeds and workspace-write fails with the same console state. So on 26200 the capability SID is not the trigger; the workspace-write token path under a console-less mediator is. #8497's machinery is still relevant on machines whose ambient Default DACL lacks a usable trustee, but it is a different mechanism from this one.
Probe sources for anyone who wants to re-run: probe-dacl-mediate.mjs (mediator-agnostic), probe-dacl-stepwise.mjs (the variations above), plus the token dumper posted earlier.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
English TL;DR
Symptom. On the packaged Windows desktop app (GUI-subsystem host), every shell call under the
workspace-write/read-onlyACL sandbox dies with exit code 3221225794 = 0xC0000142 (STATUS_DLL_INIT_FAILED) and zero output, while the same session works immediately underdanger-full-access.Root cause. The process that creates the restricted child must itself own a console. In the packaged desktop the mediator interpreter is the app image (a PE Subsystem = 2 GUI binary run with
ELECTRON_RUN_AS_NODE=1), soCreateProcessAsUserWhas no console to hand down and the restricted child dies during DLL initialization. Swapping only the mediator interpreter for a console-subsystem (Subsystem = 3)node.exe— same token, same runner script, same argv — returns exit 0.Decisive evidence. Mediator = GUI-subsystem image: 4/4 crash. Mediator = console-subsystem
node.exe: 2/2 exit 0. GUI-subsystem mediator plus anAllocConsole()before the spawn: 3/3 exit 0. The exit code and the zero output are the whole user-visible symptom.Relevant source.
@deepseek-ai/dsh-sandbox-local/lib/index.js:539-553—windowsAclRunnerInvocation()returns[process.execPath, runner.js](line 543; dev fallback at line 548), i.e. the mediator is whatever binary runs the app.@deepseek-ai/dsh-sandbox-windows-acl/lib/runner.js:157— thesandbox.spawn(...)that issuesCreateProcessAsUserW(main()at line 113).@deepseek-ai/dsh-sandbox-windows-acl/lib/types/index.d.ts:26-31— the package documents this boundary itself: "console isolation is unavailable — children share the host console (CREATE_NO_WINDOW / CREATE_NEW_CONSOLE children die with STATUS_DLL_INIT_FAILED under the restriction)".Two measured fixes (either one works).
runner.js, callAllocConsole()(window hidden viaSTARTF_USESHOWWINDOW) inmain()beforesandbox.spawn, so the mediator owns a console it can pass down.dsh-sandbox-local/lib/index.js:543, resolve a console-subsystem interpreter instead ofprocess.execPath.Independent second cause of the same exit code. discussion #8497 reports the same
0xC0000142fromAclSandbox.init()setting the restricted token's Default DACL to a capability SID (this.tempWriteSidPtr ?? this.writeSidPtr ?? worldSid) instead ofworldSid. The two are not mutually exclusive, so both need checking — fixing only one still leaves some users broken.Cross-check on the second cause (Windows 11 26200). The capability-SID ACE is appended to the ambient Default DACL by
SetEntriesInAclW, not substituted for it, and a workspace-write restricted child on this reporter's 26200 machine startscmd.exeand returns 0x00000000 (restriction verified enforced: workspace write allowed,C:\Windows\Tempwrite denied with EPERM, system reads allowed). So the capability-SID substitution is not by itself sufficient on current builds; what differs on the failing Windows 10 19045 machine is still open, and the decisive datum is the Default DACL of a restricted child on that machine.一、摘要
在 Windows 上,只要「创建受限令牌子进程」的那一层是 GUI 子系统镜像(打包桌面端正是这种情形:应用主程序以 ELECTRON_RUN_AS_NODE=1 充当 node 解释器),ACL 沙箱在 workspace-write 下运行的 shell 工具就会 100% 以 exit code 3221225794(0xC0000142 = STATUS_DLL_INIT_FAILED)失败,且 stdout / stderr 全空(连 Get-Date 都没有输出)。
把同一份代码路径的解释器换成 console 子系统的 node.exe —— 同样的令牌、同样的 runner 脚本、同样的命令 —— 立刻 exit 0。由此可排除:
准确机制:dsh-sandbox-windows-acl 用 CreateRestrictedToken(flags = 13 = DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED)加 Low 完整性标签创建受限令牌子进程。控制台子系统的早期初始化发生在被创建进程内部、DLL 初始化期,此时该进程必须能自持或继承一个控制台。GUI 子系统镜像不会被 Windows 自动分配控制台,也不会继承父进程的控制台;而 dsh-subprocess-local 创建 runner 时带 windowsHide: true(等价于 CREATE_NO_WINDOW)。于是处于末端的受限子进程既无处继承、又只能自行申请控制台,就在 DLL 初始化期死亡。
二、环境(脱敏)
三、用户可见症状
四、最小重现
真链的 argv 契约(方括号内为占位符):
→ exit 0,受限 pwsh 正常输出(自报完整性 Low / rid=4096)。
→ 0xC0000142,stdout / stderr 全空。
两者除解释器镜像外完全相同:同一份 runner.js、同一份令牌构造代码、同一个目标命令。
五、决定性证据
最小 C# 驱动(CreateRestrictedToken(..., 0x13, ...) + Low 完整性 + CreateProcessAsUserW),同一份源码分别以 /target:winexe(GUI 子系统,PE=2)与 /target:exe(console 子系统,PE=3)编译,2x2 矩阵(父有控制台 / 父无控制台,每格两次):
因果隔离(决定性):在同一个 winexe 镜像里,让驱动在创建受限子进程之前自己调用一次 AllocConsole(),同样代码、同样令牌,得到 4/4 exit 0 + stdout 7。所以 GUI 镜像本身不是原因,「创建者有没有自己的控制台」才是。
真链上的同一结论(只换中介解释器,其余全同):
补充:在 GUI 中介进程内显式 AllocConsole() 后,该中介确实继承到控制台(GetConsoleProcessList 返回 2 且含自身),但受限子进程仍然 0xC0000142(2/2)。所以「给宿主 / 中介一个控制台」不是充分修法;充分条件是创建受限子进程的那个进程在本进程内自持控制台。
两条容易误判的边界(供维护者避坑):
六、建议补丁(两个方向均已实测有效)
补丁一(推荐:最小、无 API 变更)—— @deepseek-ai/dsh-sandbox-windows-acl/lib/runner.js:在 main() 创建受限子进程之前调用 AllocConsole(),随即用 ShowWindow 与 STARTF_USESHOWWINDOW 隐藏新控制台窗口(避免闪黑窗)。runner 进程本身不受限,AllocConsole() 必然成功;之后受限子进程以继承方式拿到该控制台。实测:同一格由 0xC0000142 变为 exit 0(3/3)。
补丁二(等价方向,保留 runner 一跳不动)—— @deepseek-ai/dsh-sandbox-local 的 windowsAclRunnerInvocation()(lib/index.js:539-553 附近,返回值是 [process.execPath, sandbox-windows-acl/runner.js]):不要把 process.execPath 当解释器,在 Electron 宿主下它是 GUI 子系统镜像。改用 console 子系统的解释器(随 DSH 分发的 node.exe,或一个 console 子系统的 stub 再 exec 原 runner)。实测:2/2 与 4/4 exit 0。
不要做的事(会主动制造死亡条件):给受限子进程或其父加 CREATE_NO_WINDOW 或 CREATE_NEW_CONSOLE,或者为 shell 接 ConPTY。这与包内 README 已有的 “Console isolation is unavailable” 记录一致;缺的那一环是中介自身必须持有控制台。附注:@deepseek-ai/dsh-win32-process 已经正确地不对子进程加这两个标志。
七、旁证:同一缺陷已被第三方独立复现
Astral-Dream/dsh-windows-sandbox-0xC0000142-workaround(2026-10-02 创建,MIT)针对 DSH 0.2.0-rc.2 Windows workspace-write,报告完全相同的症状(exit code 3221225794,沙箱内 shell 完全不可用;切到 danger-full-access 后正常),并把根因定位到「托管沙箱程序的那个运行时」:普通 node.exe → exit 0,应用主程序(Electron-as-node)→ 0xC0000142,其余条件相同。它的修法是检测 process.versions.electron 后用自带普通 node 从磁盘副本重执行沙箱启动程序(沙箱核心 JS 零改动)。
本报告的增量:把 Electron-as-node 这个黑盒变量下钻到控制台 / 子系统机制,给出「创建者自持控制台」这一充分条件,并给出两个改产品源码的最小补丁点(第六节补丁一 / 补丁二)。
相关(同类但不同 bug):
其它项目里的同族证据(说明这是进程创建 / 控制台初始化类的共性坑):openai/codex 的 Windows 沙箱相关 issue(session 0 与 session 1、USER32、Winsta0\Default)、microsoft/terminal 中默认终端接管时并发控制台应用 0xc0000142。
八、结论与诉求
九、未证实 / 已知不确定项(如实标注)
All reactions