Repository navigation
[Bug] Windows workspace-write: 0xC0000142 comes from the token default DACL naming a non-restricting capability SID (corrected; follow-up to #8991 / #8067) #11223
Replies: 4 comments
|
补一条你可能想先对的证据:#11448 在同一台机器上做了受控变体,第 6 组只改默认 DACL 里的 SID(换成 而 setTokenDefaultDaclGrant(api, restrictedToken, this.tempWriteSidPtr ?? this.writeSidPtr ?? worldSid)所以在 要把你的结论钉死,需要像 #11448 的第 6 组那样只动一处。现在两份报告的根因指向不同的令牌部位,先对一次再定稿比留下两个互相矛盾的根因要好。 只读了源码( |
|
You are right, and my original experiment could not have separated the two factors: read-only and workspace-write differ in both the restricting list and the default DACL, so the "capability SID has no read ACE on System32" claim was under-determined. I have now run the single-variable version. The result is that your factor (default DACL) is the decisive one, and my factor (System32 ACE) is not — my suggested fixes were wrong and I withdraw them. SetupKernel-only manipulation, no source changes: I let Each row is a separate process; the child is a Workspace: a fresh directory with inheritance disabled, Results
So: the same restricting list is fatal with a capability-SID default DACL and fine with an Everyone default DACL (A1 vs A2). That kills my System32-ACE theory outright — the capability SID never needed a System32 ACE, the child never got far enough to use one. Where I agree and where the data disagrees with #11448Agreed: the trigger is But the "any well-known SID works" reading does not reproduce here. Everyone (A2) works; Authenticated Users (A3) and BUILTIN\Users (A4) both stay at 0xC0000142. That is the opposite of #11448's variant 6, which reports That makes your proposed fix direction the right one and the #11448 patch the wrong one: append the logon SID to the default DACL next to the capability SID, rather than replacing it with a well-known SID. Appending satisfies "the named SID is also a restricting SID", which is the invariant that actually predicts the outcome across A1–A4. Replacing with AU would be expected to keep failing on this host. I have not yet run the append-the-logon-SID variant — I can do that next if useful, since it is the one experiment that would confirm the invariant rather than merely being consistent with it. B2B2 is not a sandbox-start failure: with writeSid set to Everyone the child starts normally and prints Also, on #11448's escape claim#11448 reports that an in-workspace child writing to the Desktop is denied. On this host, with the shipped default DACL replaced by Everyone (A2), a child that runs successfully writes to Environment: Windows 10/11 x64, DSH desktop 0.2.1-rc.3 (commit I will correct the original post's Root cause and Suggested fixes sections so the thread does not carry the withdrawn claim. |
Follow-up: the second layer, plus the variable separation PerryLink asked forThis comment completes the picture. It adds three things:
TL;DR: fixing only the default DACL makes the child start, but the capability SID in the workspace ACL is dead weight. Isolation rests on "workspace already inherits write for Authenticated Users", not on the sandbox's own ACE. 1. Only the default DACL changesFixed:
The A2 vs A3/A4 pair is the point: AU and BU are ordinary groups with perfectly normal read access on System32, yet as default-DACL SIDs they still fail. Only Everyone works, and the one property Everyone has that AU/BU do not is that Everyone is already in the restricting list ( So the invariant is:
That is why #11448's variant 6 ( Append vs replace, and the mask constraintOn the same restricted token, touching only the token default DACL:
Append and replace are equivalent for getting the child to start; append is the safer patch because it keeps the capability ACE. Mask sweep, with the logon SID appended:
The appended ACE must be FILE_ALL_ACCESS; read-only or write-only masks do not lift the loader failure. That is a direct constraint on the fix: the existing I have now run the append-the-logon-SID variant I said I had not run yet. It works. 2. The capability SID never grants anythingWith layer one fixed, does the workspace become writable? Dumping the restricted token's groups on the host side: The capability SID Two workspaces, identical ACLs except for one thing: Same
The only difference is the inherited ACE, and workspace writability flips with it. Conclusion: workspace write access comes from ordinary NTFS inheritance (Authenticated Users), not from the capability SID. The capability ACE the sandbox installs is inert. Consequences:
Correction to something I said earlierOn So the accurate framing is: the 0xC0000142 is what users actually hit; the inert capability SID is a separate correctness defect that normal workspaces mask. 3. Integrity label ruled outI suspected the
4. Two independent bugs found along the way
5. What a correct fix looks likeLayer one — name in the default DACL a SID that is also a restricting SID, with Layer two — the capability-SID design does not work as written: it is restricting-only, never a real group, so it authorizes nothing. Either:
Fixing only layer one leaves workspaces with inheritance stripped in a new failure mode: the child starts but cannot write anything. That is harder to diagnose than a clean Environment safety
Host: Windows 10/11 x64, DSH desktop |
|
Read the matrix — A2/A1 is the decisive pair. Withdrawing the System32 claim was the right call; one caveat changes the last section, though. The restricting list is not just the capability SIDs. Which means the row where you removed the capability ACE needs one more control. It matters for the conclusion: "the capability SID is dead weight, isolation rests on NTFS inheritance" and "writes come through the keep-alive logon SID" are different boundaries. The second says any process in that logon session can write to the workspace too, which is much weaker than the per-workspace capability SID the design claims ( Agreed on the access-mask constraint on the appended ACE — that one is worth writing down regardless of how the above lands. Version check: the restricting list is identical in rc.3, rc.4, rc.5 and master |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Relation to existing reports
This continues #8991 ("every confined pwsh command exits with 0xC0000142 (STATUS_DLL_INIT_FAILED)") and #8067 (the 0xC0000142 bubble). Both describe the symptom. #8991 explicitly labels its root cause as "hypothesis, not verified against source" and asks whether the runner needs
SeAssignPrimaryTokenPrivilege, a specific UAC configuration, AppContainer support, or particular workspace ACLs.This post answers that question: it is none of those. The trigger can be isolated with a controlled experiment.
#8067 quotes
sandbox-windows-acl/README.zh.mddescribing 0xC0000142 at DLL-init as a known limitation ofCREATE_NO_WINDOW/CREATE_NEW_CONSOLE. The data below shows a second, distinct trigger that also fires on the ordinarystdio: "inherit"path, so the README's framing does not cover every affected host.Summary
On Windows, every
pwsh/shell tool call under the defaultworkspace-writesandbox mode fails before the command ever runs. The restricted-token ACL runner starts fine inread-onlymode, but as soon as the mode becomesworkspace-writethe child process exits with3221225794(0xC0000142,STATUS_DLL_INIT_FAILED) and produces no stdout and no stderr at all.Two distinct problems:
workspace-writeon this host. Root cause: the token's default DACL names the derived capability SID (setTokenDefaultDaclGrant,index.ts:304), and under aWRITE_RESTRICTEDtoken the image section carrying that DACL fails the access check, so the loader dies before any user code runs. The earlier "capability SID has no read ACE on System32" claim is withdrawn — see Root cause (corrected) below.dsh-pwsh-sandbox'sclassifyRunnerFailure/isRunnerSpawnFailureonly recognisesspawnENOENT/EACCESofargv[0]. A child that exits with0xC0000142is not treated as a sandbox failure, so the user sees a generic command failure instead ofSandboxUnavailableError. This is what made the bug hard to diagnose, and it is arguably the more important half to fix.Environment
0.2.1-rc.3, commitbe72761ee1a6736ebcdf4f3739363b083ae53c98v24.21.0COMPUTER\Administrator(S-1-5-21-…-500), high integrity, elevated via UACrequestedExecutionLevel level="asInvoker"D:\DSH-WorkspaceC:\Users\Administrator\AppData\Local\Temp, and separatelyD:\DSH-ww-probe(outside the workspace)Steps to reproduce
Run the sandbox runner directly, bypassing DSH tooling:
Same result through the DSH pwsh tool under the default permission preset.
What is not the cause (all verified empirically)
RunLevel=LeastPrivilege,ELECTRON_RUN_AS_NODE=1) reproduces0xC0000142identically whileread-onlystill succeeds.%TEMP%(toD:\DSH-ww-probe) changes nothing.S-1-4-424242-424242that has no ACE anywhere, withmanageDacls: false, still fails.AclSandbox.init()andwin32()binding load succeed in-process (3 ms / 7 ms); the runner reachesspawn()and gets a pid. The crash is in the child.cmd.exe,where.exe,hostname.exe,whoami.exeall fail identically.where.exeinto a directory the capability SID can read does not help.app.asar.read-onlyruns from the same asar path successfully; a SHA-256 sweep over the archive is clean.Explicitly ruled out: the privileges named in #8991
#8991 asked whether the runner needs
SeAssignPrimaryTokenPrivilege. On this host:SeAssignPrimaryTokenPrivilegeis absent from the token entirely; LSA policy (secedit /export /cfg) grants it only to*S-1-5-19,*S-1-5-20(LOCAL SERVICE, NETWORK SERVICE).AdjustTokenPrivilegesreturnsERROR_NOT_ALL_ASSIGNED (1300).CreateRestrictedTokensucceeds,AclSandbox.init()succeeds, andspawn()returns a pid — the privilege is not on the failing path for this backend.TokenElevation=1, high integrity) via UAC, yet an unelevated run reproduces0xC0000142identically whileread-onlystill succeeds.The variable is the content of the restricting-SID list, as shown next.
Root cause (corrected 2026-10-31)
dsh-sandbox-windows-aclbuilds the child token withand then grants the derived capability SID in the token's default DACL (
index.ts:304):When the process creates kernel objects without an explicit security descriptor — pipes, events, image sections — those objects inherit that default DACL. Under a
WRITE_RESTRICTEDtoken the access check consults the restricting SIDs, and an object whose DACL names only a SID that cannot satisfy the check makes the loader fail to map the system DLL, surfacing asSTATUS_DLL_INIT_FAILEDbefore any user code runs.The invariant that predicts the outcome is "the SID named in the default DACL must also be a restricting SID". Everyone is in the restricting list (
[logonSid, known.world, ...writeSids]); a capability SID, Authenticated Users, and BUILTIN\Users are not.Controlled experiment (only the default DACL changes)
Token built exactly as shipped; only
TokenDefaultDacloverwritten viaSetEntriesInAclW+SetTokenInformation(TokenDefaultDacl = 6). Workspace write SID and temp write SID are the shipped capability SIDs in every row. Workspace has inheritance disabled,AU:(M)removed, granting onlyBA:(F),SY:(F)and the capability SID(W,D,DC).S-1-1-0EveryoneS-1-5-11Auth. UsersS-1-5-32-545UsersA1 vs A2 is the isolating pair: identical restricting list, only the default DACL SID differs, and the outcome flips. The earlier "System32 read ACE" table below is retained only as a record of what was actually run; its interpretation was wrong.
Original (withdrawn) table, kept for the record
S-1-5-32-545BUILTIN\Users0x1200a9(read+execute)S-1-1-0EveryoneS-1-5-11Authenticated UsersS-1-5-21-…-500current userS-1-5-18LOCAL SYSTEM0x1301bf(full control)S-1-5-32-544BUILTIN\Administrators0x1301bf(full control)S-1-5-80-…service SID (TrustedInstaller)0x1f01ff(full control)S-1-4-136077656-194402612capability SIDThis table varied the write SID, which changes the restricting list and the default DACL together — it could not attribute the failure.
Relevant code:
dsh-sandbox-local/lib/index.js—windowsAclRunnerArgv()adds--write-sid/--temp-write-sidwhenpolicy.sessionId !== undefined && mode !== "read-only";materializeAclGrant()creates the standing recursive workspace ACE.dsh-sandbox-windows-acl/lib/runner.js—const api = await win32(), thennew AclSandbox({... manageDacls: !seamManaged })→init()→spawn({ stdio: "inherit" }).dsh-win32-process/lib/index.js:425-427—createRestrictedProcess()callsCreateProcessAsUserWwithlpEnvironment = NULL(documented workaround for a koffiERROR_INVALID_PARAMETER); every failure there throws and emits thewindows-acl-run:signature, so a silent0xC0000142cannot originate in that code.Impact
On affected hosts the default
workspace-writepreset makes the shell tool unusable, which is the primary tool for most workflows. The only working configurations I found areread-only(no writes) anddanger-full-access(no sandbox).Suggested fixes for the sandbox half (in order of preference)
findLogonSidalready exists intoken.tsand is imported atindex.ts:55. The logon SID is already in the restricting list, so the pass-2 check that motivated the grant still passes, and the "never tag default-DACL objects with the shared workspace capability" intent still holds. This is the change that matches the A1–A4 data; replacing the SID with Authenticated Users (as proposed in Windows 10: workspace-write sandbox kills every child process with 0xC0000142 (default DACL names a capability SID) #11448) is expected to keep failing on this host, because AU is not a restricting SID here. Not yet verified by me — it is the next experiment.workspace-write, not justread-only.dsh-sandbox-localselectswindows-aclfromPLATFORM_CHAINS = { win32: ["windows-acl"] }without probing (chainVerdict()returns the first candidate when the rest list is empty), even thoughL137documents a "functional windows-acl probe" that runs the runner in read-only mode only. Extend that probe toworkspace-writeand surfaceSandboxUnavailableErrorwhen it fails.README.zh.mdline 119 and the keep-alive-group note at line 94 describe 0xC0000142 as aCREATE_NO_WINDOWissue; per the data above the default-DACL composition is an independent trigger.Additional note
app.asarin the desktop distribution is a valid asar plus a DSH trailer JSON appended after the data section (data base =8 + headerSize + 4 = 4748104for this build). Not a bug, just worth documenting — it cost me a false "corrupted archive" hypothesis early on.All reactions