chainlesschain 0.162.181 → 0.162.182
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/scripts/build-windows-sandbox-helper.ps1 +163 -0
- package/src/assets/web-panel/assets/{AIOps-cq0dwbnf.js → AIOps-DzZ5_xoj.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CvcgGl3w.js → ActionButton-DeLXddIg.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-hM2-jFqg.js → Analytics-rvDCmet1.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-B9_Gsd_g.js → AppLayout-Yt6QVDnp.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-B7bu52Og.js → Artifacts-BS112K5L.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DThfDgqB.js → Audit-0wJuQrNu.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DjlFiD4v.js → BackgroundAgents-B90PMgbn.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CK9xJKnG.js → Backup-TkgMglQQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DEiSU8K8.js → BaseInput-CQVS9p1l.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CpT96czj.js → Chat-YTjQHoC7.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BKPPNmZI.js → ChatBubbleRenderer-PR074hUN.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-C1GfDnO0.js → Checkbox-5bwaOabq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-3DAKgiLx.js → Codegen-BFmNQ_Xe.js} +1 -1
- package/src/assets/web-panel/assets/{Col-74cOwZU1.js → Col-BT_u4Dzp.js} +1 -1
- package/src/assets/web-panel/assets/{Community-tn9Kkz9j.js → Community-CJP52u5b.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BcoXH7m4.js → Compact-BpzhJE9i.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DIPGUBgi.js → Compliance-Cg7rfslp.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-doD5_91c.js → Cowork-JisxL6Yy.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-C-IDvubz.js → Cron-Bu7Ug_MC.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CdcCoBdj.js → Crosschain-CvuqoLdj.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DdH5aMMA.js → DID-C2u9G8Pu.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DHLA4mm0.js → Dashboard-BFt5Yv3d.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-dEeCt7Md.js → Dropdown-DIaT9Wxq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BMaecFv9.js → EmailListRenderer-D_A601Y4.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DUaUukTN.js → FamilyGuardDashboard--IyQ8I8w.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CMBE0_rp.js → Federation-BihWHxuY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-PG-JDbXq.js → FormItemContext-BB8dOXc_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Bcb0p88m.js → GenericCardRenderer-DQYhj1HH.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Bgv15myB.js → Git-B-2QZxGf.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DB8YTArI.js → Governance-CusSb-nG.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BQMwCWaS.js → Inference-BAjmvPPL.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B1_ZziiB.js → KnowledgeGraph-DoNYWKYq.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DovEEnt_.js → Logs-B8fKWD-n.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-D201OL4T.js → MarkdownRenderer-BlJm1J7E.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BlCXSpyB.js → Marketplace-CyOscbSL.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CsCR09Wj.js → McpTools-Ex6Om5da.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-CBVRLKno.js → Memory-CDcD8l8r.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C9OIafgA.js → MobileBridge-xfBqoFDg.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D_zoJ8Xs.js → MobileProjects-BwObMMA2.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-vE-HdvLj.js → Mtc-CIWd36OV.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CPLaekCd.js → MtcAudit-CPX0ouuA.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-C_UP9C2o.js → Multisig-BDJsIUuW.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-2nODJcU1.js → NLProgramming-DEkbuD3N.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BhE-SJGu.js → Notes-DJpHRhH1.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-D21q7BNU.js → NotificationSettings-C1UZ-Qd5.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-0b62GYOD.js → OrderTableRenderer-GlJtqLjT.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CEqmw_sb.js → Organization-Bbhj0fac.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-MW-gMDO_.js → Overflow-1H04x3kx.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CY0hjI_G.js → P2P-CDpPe3et.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BbWh7Man.js → PdhVaultBrowser-MUX0Bpvw.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-ChqEA_4Q.js → Permissions-A07-0Ci1.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B3YrbYfD.js → PersonalDataHub-DhhDWxAZ.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-B8WBhhlW.js → Pipeline-CyfbJfe-.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Cmb0PXNx.js → Privacy-CNFps3ED.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-hF6zW3Nz.js → ProjectInit--T-u3Dr5.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BFSVfpW0.js → ProjectSettings-DJmrK2fC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DAWkVFkc.js → Projects-Qx_MdX9k.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B8yux8Dx.js → Providers-DLzzbOZd.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-N76cQluO.js → QrScannerModal-CexOomKS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-ClvZ0KYR.js → QuickAsk-QklGi19_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B-GfgBpT.js → Recommend-Bb9kwSw8.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-mlglCdQi.js → RemoteSession-DZuVNNN5.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DJIZzQeG.js → Reputation-Dr5cLP0N.js} +1 -1
- package/src/assets/web-panel/assets/{Row-COzxu_bb.js → Row-oCQHYv5t.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CoYsJQuE.js → RssFeed-IFDEykIR.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DC77Mq9S.js → Search-CspVIxDc.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Loy2EdMO.js → Security-Bm66o_DH.js} +3 -3
- package/src/assets/web-panel/assets/{Services-GUD07Eu0.js → Services-DvanNqXl.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWzkptYU.js → Skeleton-CHjEe9_O.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CEDn1r-Z.js → Skills-Cb8jWjMX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-ub_k4pXh.js → Sla-DxZm1491.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-D7HLX9CA.js → SpeechSettings-DWxEEclC.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DbwOvl6m.js → SyncSettings-FIvOFw5p.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CBQzoAu5.js → Tasks-CyuLnDFR.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BNVeIhud.js → Templates-CaNjzV_t.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-PS9AzhXU.js → Tenant-DjlP6TmH.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-CrmXMOkg.js → TimelineRenderer-DoZ8y7y8.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DD-VDHw1.js → Tokens-390cg2TC.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BsoN_n-n.js → Trigger-CLrtCj5_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-n8LmN4ZV.js → Trust-DpESfSPn.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C9_7MImA.js → UkeySign-CN6279O0.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DyBteNgn.js → VideoEditing-DPtAUlSe.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BS5ixeXI.js → Wallet-nKOMV_1n.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-spc4UImU.js → WebAuthn-HuXlSOc4.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-D7iVvaBO.js → WorkflowEditor-CUfFjARi.js} +1 -1
- package/src/assets/web-panel/assets/{chat-6CmBkHrL.js → chat-6Eq0PcZJ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BGoCLkxI.js → colors-BP_Sf6Iy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-fYZBetMO.js → compact-item-BufMKIh5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BeM-1UVj.js → createContext-BBMXxWSW.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CE7ZuEnV.js → hasIn-fUK_tRO2.js} +1 -1
- package/src/assets/web-panel/assets/{index-QIpnpZgz.js → index-2A5ph8b2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BU_2qiDk.js → index-3FuwBewR.js} +1 -1
- package/src/assets/web-panel/assets/{index--lap6cf7.js → index-6GmBa4Kg.js} +1 -1
- package/src/assets/web-panel/assets/{index-De0quFDw.js → index-B2vS66eI.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1r9tT3J.js → index-B7Etk0Te.js} +1 -1
- package/src/assets/web-panel/assets/{index-YjcumOZ4.js → index-B9WYSn4m.js} +1 -1
- package/src/assets/web-panel/assets/{index-MtOuq2i6.js → index-BHy6yNiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-RMZ8a7Yt.js → index-BMLHv389.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0v6YKKO.js → index-BWKoBU8R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRnUE8Cb.js → index-Bkg4ZePE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsMfoaeo.js → index-C44atv4k.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoXk5KJ8.js → index-CE2DdTP2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4wiTEOM.js → index-CGjmILDm.js} +1 -1
- package/src/assets/web-panel/assets/{index-zb7rVOhV.js → index-CW_G-MXw.js} +1 -1
- package/src/assets/web-panel/assets/{index-ATcSr_2O.js → index-CZ6Yt17L.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9Y3k0HI.js → index-CeASi4fr.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2U-ObMq.js → index-CgiECHu2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Puq6Luf1.js → index-Cp8vhyZy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BV551878.js → index-CwSf5pnf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BowH1eLQ.js → index-D4_MuGkd.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3CDgvNW.js → index-D9vU25ij.js} +1 -1
- package/src/assets/web-panel/assets/{index-WUrJ6S1j.js → index-DASluSqY.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRLjEP1H.js → index-DLpIs_P_.js} +1 -1
- package/src/assets/web-panel/assets/index-DOZTSoS7.js +1 -0
- package/src/assets/web-panel/assets/{index-DT3zPSVX.js → index-DPdUNk1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-BunaQCa0.js → index-DVsqvI4M.js} +1 -1
- package/src/assets/web-panel/assets/index-DhpvjpqI.js +1 -0
- package/src/assets/web-panel/assets/{index-CIpVrf5P.js → index-DmkMXwnR.js} +1 -1
- package/src/assets/web-panel/assets/{index-lJBjB1QX.js → index-DocdRcbd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEVm0lCS.js → index-Dyngbd29.js} +19 -19
- package/src/assets/web-panel/assets/{index-C4SJVjs4.js → index-GwdRUZ-x.js} +1 -1
- package/src/assets/web-panel/assets/{index-ukg99D1V.js → index-LvCBfTLo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfPL-m46.js → index-cpEli44X.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpIGIB85.js → index-fzaMtSJ8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNZDILcO.js → index-pum7HYBA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dw1PyYPm.js → index-t8N1tSeX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cip8emOQ.js → index-uCbEN7cY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsvfrESp.js → index-wKTLbKyc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8BsdNV_.js → index-wV6iGdpY.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CYHMs1ag.js → initDefaultProps-V5Fo95FM.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DDB2Ffk5.js → motion-BYWU0hUT.js} +1 -1
- package/src/assets/web-panel/assets/{move-Bg3cD-dX.js → move-Cr1M-J2h.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B5nedjTS.js → mtc-parser-BysQKi2N.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BHyuUILu.js → omit-D1zHU8GC.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-C9YQnEN9.js → pickAttrs-CUArxtfi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DBUFYUe7.js → placementArrow-ChoYEys0.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-nBs2Obnk.js → responsiveObserve-BQRs3Mo7.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DkCEaXNU.js → slide-B8zbsCkg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-aqE1Wi6U.js → statusUtils-C2U8Gs6h.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CN5bhfHB.js → styleChecker-Dd8MgL_V.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B_oX_kvJ.js → useFlexGapSupport-hknqY5NH.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-rCuXHeme.js → useFs-CLTAmcfw.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DZR9Abyd.js → usePersonalDataHub-ByegHbiB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-ConCYQlX.js → vnode-DqlAy5DP.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CUdDxG5M.js → zoom-DiXiztQn.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +115 -3
- package/src/commands/agent.js +6 -0
- package/src/data/changelog.json +13 -0
- package/src/gateways/terminal/PtyManager.js +45 -4
- package/src/gateways/ws/ws-server.js +10 -1
- package/src/harness/background-task-manager.js +70 -2
- package/src/harness/worktree-isolator.js +14 -3
- package/src/lib/agent-core.js +1 -0
- package/src/lib/agent-schedule-store.js +45 -22
- package/src/lib/hook-runner.js +6 -2
- package/src/lib/packer/pkg-config-generator.js +3 -1
- package/src/lib/plugin-runtime/bin.js +1148 -70
- package/src/lib/plugin-runtime/manifest.js +68 -12
- package/src/lib/plugin-runtime/scopes.js +20 -10
- package/src/lib/process-execution-broker/index.js +375 -48
- package/src/lib/process-execution-broker/platform-sandbox.js +3831 -276
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +3856 -0
- package/src/lib/repl-bang-memorize.js +67 -10
- package/src/lib/slash-commands.js +27 -4
- package/src/repl/agent-repl.js +4 -3
- package/src/runtime/agent-core.js +376 -52
- package/src/runtime/agent-runtime.js +13 -1
- package/src/runtime/headless-runner.js +4 -3
- package/src/assets/web-panel/assets/Terminal-Cgb4eLB_.js +0 -3
- package/src/assets/web-panel/assets/devWarning-BzPwHnw8.js +0 -1
- package/src/assets/web-panel/assets/index-DacrAdc-.js +0 -1
- package/src/assets/web-panel/assets/index-EHzNJozo.js +0 -1
- package/src/lib/process-execution-broker/windows-sandbox.ps1 +0 -999
|
@@ -0,0 +1,3856 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.ComponentModel;
|
|
4
|
+
using System.Globalization;
|
|
5
|
+
using System.IO;
|
|
6
|
+
using Microsoft.Win32.SafeHandles;
|
|
7
|
+
using System.Runtime.InteropServices;
|
|
8
|
+
using System.Security.Cryptography;
|
|
9
|
+
using System.Runtime.Serialization;
|
|
10
|
+
using System.Runtime.Serialization.Json;
|
|
11
|
+
using System.Text;
|
|
12
|
+
using System.Threading;
|
|
13
|
+
|
|
14
|
+
namespace ChainlessChain.WindowsSandbox
|
|
15
|
+
{
|
|
16
|
+
public static class JsonCodec
|
|
17
|
+
{
|
|
18
|
+
private static DataContractJsonSerializer CreateSerializer(Type type)
|
|
19
|
+
{
|
|
20
|
+
return new DataContractJsonSerializer(
|
|
21
|
+
type,
|
|
22
|
+
new DataContractJsonSerializerSettings
|
|
23
|
+
{
|
|
24
|
+
UseSimpleDictionaryFormat = true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static string SerializeObject(params object[] pairs)
|
|
29
|
+
{
|
|
30
|
+
if (pairs == null || pairs.Length % 2 != 0)
|
|
31
|
+
throw new ArgumentException(
|
|
32
|
+
"JSON object entries must be key/value pairs");
|
|
33
|
+
Dictionary<string, object> values =
|
|
34
|
+
new Dictionary<string, object>(StringComparer.Ordinal);
|
|
35
|
+
for (int index = 0; index < pairs.Length; index += 2)
|
|
36
|
+
{
|
|
37
|
+
string key = pairs[index] as string;
|
|
38
|
+
if (String.IsNullOrEmpty(key))
|
|
39
|
+
throw new ArgumentException(
|
|
40
|
+
"JSON object keys must be non-empty strings");
|
|
41
|
+
values.Add(key, pairs[index + 1]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
using (MemoryStream stream = new MemoryStream())
|
|
45
|
+
{
|
|
46
|
+
CreateSerializer(typeof(Dictionary<string, object>))
|
|
47
|
+
.WriteObject(stream, values);
|
|
48
|
+
return new UTF8Encoding(false, true).GetString(
|
|
49
|
+
stream.ToArray());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static T Deserialize<T>(string json)
|
|
54
|
+
{
|
|
55
|
+
if (json == null)
|
|
56
|
+
throw new ArgumentNullException("json");
|
|
57
|
+
byte[] bytes = new UTF8Encoding(false, true).GetBytes(json);
|
|
58
|
+
using (MemoryStream stream = new MemoryStream(bytes, false))
|
|
59
|
+
{
|
|
60
|
+
return (T)CreateSerializer(typeof(T)).ReadObject(stream);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public static class Native
|
|
66
|
+
{
|
|
67
|
+
public const string SourceSha256 =
|
|
68
|
+
"__CHAINLESS_WINDOWS_SANDBOX_SOURCE_SHA256__";
|
|
69
|
+
|
|
70
|
+
private const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;
|
|
71
|
+
private const UInt32 TOKEN_DUPLICATE = 0x0002;
|
|
72
|
+
private const UInt32 TOKEN_QUERY = 0x0008;
|
|
73
|
+
private const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;
|
|
74
|
+
private const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
|
|
75
|
+
|
|
76
|
+
private const UInt32 DISABLE_MAX_PRIVILEGE = 0x00000001;
|
|
77
|
+
private const UInt32 LUA_TOKEN = 0x00000004;
|
|
78
|
+
|
|
79
|
+
private const UInt32 CREATE_SUSPENDED = 0x00000004;
|
|
80
|
+
private const UInt32 DETACHED_PROCESS = 0x00000008;
|
|
81
|
+
private const UInt32 CREATE_NEW_PROCESS_GROUP = 0x00000200;
|
|
82
|
+
private const UInt32 CREATE_UNICODE_ENVIRONMENT = 0x00000400;
|
|
83
|
+
private const UInt32 EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
|
|
84
|
+
private const UInt32 STARTF_USESHOWWINDOW = 0x00000001;
|
|
85
|
+
private const UInt32 STARTF_USESTDHANDLES = 0x00000100;
|
|
86
|
+
private const UInt16 SW_HIDE = 0;
|
|
87
|
+
private const UInt32 INFINITE = 0xffffffff;
|
|
88
|
+
private const UInt32 WAIT_OBJECT_0 = 0x00000000;
|
|
89
|
+
private const UInt32 WAIT_TIMEOUT = 0x00000102;
|
|
90
|
+
private const UInt32 HANDLE_FLAG_INHERIT = 0x00000001;
|
|
91
|
+
private const Int32 ERROR_INSUFFICIENT_BUFFER = 122;
|
|
92
|
+
private const Int32 ERROR_IO_PENDING = 997;
|
|
93
|
+
private const Int32 HRESULT_FROM_WIN32_ERROR_ALREADY_EXISTS =
|
|
94
|
+
unchecked((Int32)0x800700B7);
|
|
95
|
+
private const Int32 TokenPrivileges = 3;
|
|
96
|
+
private const Int32 TokenIsAppContainer = 29;
|
|
97
|
+
private const Int32 TokenCapabilities = 30;
|
|
98
|
+
private const Int32 TokenAppContainerSid = 31;
|
|
99
|
+
private static readonly IntPtr PROC_THREAD_ATTRIBUTE_HANDLE_LIST =
|
|
100
|
+
new IntPtr(0x00020002);
|
|
101
|
+
private static readonly IntPtr PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES =
|
|
102
|
+
new IntPtr(0x00020009);
|
|
103
|
+
private const Byte CRT_FOPEN = 0x01;
|
|
104
|
+
private const Byte CRT_FPIPE = 0x08;
|
|
105
|
+
private const Byte CRT_FDEV = 0x40;
|
|
106
|
+
private const UInt32 FILE_TYPE_DISK = 0x0001;
|
|
107
|
+
private const UInt32 FILE_TYPE_CHAR = 0x0002;
|
|
108
|
+
private const UInt32 FILE_TYPE_PIPE = 0x0003;
|
|
109
|
+
private const UInt32 DRIVE_UNKNOWN = 0;
|
|
110
|
+
private const UInt32 DRIVE_NO_ROOT_DIR = 1;
|
|
111
|
+
private const UInt32 DRIVE_REMOTE = 4;
|
|
112
|
+
private const UInt16 IMAGE_FILE_MACHINE_I386 = 0x014c;
|
|
113
|
+
private const UInt16 IMAGE_FILE_MACHINE_ARMNT = 0x01c4;
|
|
114
|
+
private const UInt16 IMAGE_FILE_MACHINE_AMD64 = 0x8664;
|
|
115
|
+
private const UInt16 IMAGE_FILE_MACHINE_ARM64 = 0xaa64;
|
|
116
|
+
private const UInt32 GENERIC_READ = 0x80000000;
|
|
117
|
+
private const UInt32 GENERIC_WRITE = 0x40000000;
|
|
118
|
+
private const UInt32 FILE_SHARE_READ = 0x00000001;
|
|
119
|
+
private const UInt32 FILE_SHARE_WRITE = 0x00000002;
|
|
120
|
+
private const UInt32 OPEN_EXISTING = 3;
|
|
121
|
+
private const UInt32 FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
|
|
122
|
+
private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x00000080;
|
|
123
|
+
private const UInt32 FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
|
|
124
|
+
private const UInt32 FILE_FLAG_OVERLAPPED = 0x40000000;
|
|
125
|
+
private const UInt32 FSCTL_REQUEST_FILTER_OPLOCK = 0x0009005C;
|
|
126
|
+
private const UInt32 VOLUME_NAME_DOS = 0x00000000;
|
|
127
|
+
private const UInt32 FILE_NAME_NORMALIZED = 0x00000000;
|
|
128
|
+
private const Int32 FileIdInfo = 18;
|
|
129
|
+
private const Int32 FileStatInformation = 68;
|
|
130
|
+
private const Int32 FileStatBasicByNameInfo = 3;
|
|
131
|
+
|
|
132
|
+
private const UInt32 JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002;
|
|
133
|
+
private const UInt32 JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008;
|
|
134
|
+
private const UInt32 JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100;
|
|
135
|
+
private const UInt32 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
|
|
136
|
+
private const Int32 JobObjectBasicAccountingInformation = 1;
|
|
137
|
+
private const Int32 JobObjectExtendedLimitInformation = 9;
|
|
138
|
+
|
|
139
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
140
|
+
private struct SECURITY_ATTRIBUTES
|
|
141
|
+
{
|
|
142
|
+
public UInt32 nLength;
|
|
143
|
+
public IntPtr lpSecurityDescriptor;
|
|
144
|
+
[MarshalAs(UnmanagedType.Bool)]
|
|
145
|
+
public bool bInheritHandle;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
149
|
+
private struct STARTUPINFO
|
|
150
|
+
{
|
|
151
|
+
public UInt32 cb;
|
|
152
|
+
public string lpReserved;
|
|
153
|
+
public string lpDesktop;
|
|
154
|
+
public string lpTitle;
|
|
155
|
+
public UInt32 dwX;
|
|
156
|
+
public UInt32 dwY;
|
|
157
|
+
public UInt32 dwXSize;
|
|
158
|
+
public UInt32 dwYSize;
|
|
159
|
+
public UInt32 dwXCountChars;
|
|
160
|
+
public UInt32 dwYCountChars;
|
|
161
|
+
public UInt32 dwFillAttribute;
|
|
162
|
+
public UInt32 dwFlags;
|
|
163
|
+
public UInt16 wShowWindow;
|
|
164
|
+
public UInt16 cbReserved2;
|
|
165
|
+
public IntPtr lpReserved2;
|
|
166
|
+
public IntPtr hStdInput;
|
|
167
|
+
public IntPtr hStdOutput;
|
|
168
|
+
public IntPtr hStdError;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
172
|
+
private struct STARTUPINFOEX
|
|
173
|
+
{
|
|
174
|
+
public STARTUPINFO StartupInfo;
|
|
175
|
+
public IntPtr lpAttributeList;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
179
|
+
private struct SECURITY_CAPABILITIES
|
|
180
|
+
{
|
|
181
|
+
public IntPtr AppContainerSid;
|
|
182
|
+
public IntPtr Capabilities;
|
|
183
|
+
public UInt32 CapabilityCount;
|
|
184
|
+
public UInt32 Reserved;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
188
|
+
private struct LUID
|
|
189
|
+
{
|
|
190
|
+
public UInt32 LowPart;
|
|
191
|
+
public Int32 HighPart;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
195
|
+
private struct LUID_AND_ATTRIBUTES
|
|
196
|
+
{
|
|
197
|
+
public LUID Luid;
|
|
198
|
+
public UInt32 Attributes;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
202
|
+
private struct PROCESS_INFORMATION
|
|
203
|
+
{
|
|
204
|
+
public IntPtr hProcess;
|
|
205
|
+
public IntPtr hThread;
|
|
206
|
+
public UInt32 dwProcessId;
|
|
207
|
+
public UInt32 dwThreadId;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
211
|
+
private struct NATIVE_OVERLAPPED
|
|
212
|
+
{
|
|
213
|
+
public UIntPtr Internal;
|
|
214
|
+
public UIntPtr InternalHigh;
|
|
215
|
+
public UInt32 Offset;
|
|
216
|
+
public UInt32 OffsetHigh;
|
|
217
|
+
public IntPtr EventHandle;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
221
|
+
private struct FILE_TIME
|
|
222
|
+
{
|
|
223
|
+
public UInt32 LowDateTime;
|
|
224
|
+
public UInt32 HighDateTime;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
228
|
+
private struct BY_HANDLE_FILE_INFORMATION
|
|
229
|
+
{
|
|
230
|
+
public UInt32 FileAttributes;
|
|
231
|
+
public FILE_TIME CreationTime;
|
|
232
|
+
public FILE_TIME LastAccessTime;
|
|
233
|
+
public FILE_TIME LastWriteTime;
|
|
234
|
+
public UInt32 VolumeSerialNumber;
|
|
235
|
+
public UInt32 FileSizeHigh;
|
|
236
|
+
public UInt32 FileSizeLow;
|
|
237
|
+
public UInt32 NumberOfLinks;
|
|
238
|
+
public UInt32 FileIndexHigh;
|
|
239
|
+
public UInt32 FileIndexLow;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
243
|
+
private struct FILE_ID_128
|
|
244
|
+
{
|
|
245
|
+
public UInt64 LowPart;
|
|
246
|
+
public UInt64 HighPart;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
250
|
+
private struct FILE_ID_INFO
|
|
251
|
+
{
|
|
252
|
+
public UInt64 VolumeSerialNumber;
|
|
253
|
+
public FILE_ID_128 FileId;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
257
|
+
private struct IO_STATUS_BLOCK
|
|
258
|
+
{
|
|
259
|
+
public IntPtr Status;
|
|
260
|
+
public UIntPtr Information;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
264
|
+
private struct FILE_STAT_INFORMATION
|
|
265
|
+
{
|
|
266
|
+
public Int64 FileId;
|
|
267
|
+
public Int64 CreationTime;
|
|
268
|
+
public Int64 LastAccessTime;
|
|
269
|
+
public Int64 LastWriteTime;
|
|
270
|
+
public Int64 ChangeTime;
|
|
271
|
+
public Int64 AllocationSize;
|
|
272
|
+
public Int64 EndOfFile;
|
|
273
|
+
public UInt32 FileAttributes;
|
|
274
|
+
public UInt32 ReparseTag;
|
|
275
|
+
public UInt32 NumberOfLinks;
|
|
276
|
+
public UInt32 EffectiveAccess;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
280
|
+
private struct FILE_STAT_BASIC_INFORMATION
|
|
281
|
+
{
|
|
282
|
+
public Int64 FileId;
|
|
283
|
+
public Int64 CreationTime;
|
|
284
|
+
public Int64 LastAccessTime;
|
|
285
|
+
public Int64 LastWriteTime;
|
|
286
|
+
public Int64 ChangeTime;
|
|
287
|
+
public Int64 AllocationSize;
|
|
288
|
+
public Int64 EndOfFile;
|
|
289
|
+
public UInt32 FileAttributes;
|
|
290
|
+
public UInt32 ReparseTag;
|
|
291
|
+
public UInt32 NumberOfLinks;
|
|
292
|
+
public UInt32 DeviceType;
|
|
293
|
+
public UInt32 DeviceCharacteristics;
|
|
294
|
+
public UInt32 Reserved;
|
|
295
|
+
public UInt64 VolumeSerialNumber;
|
|
296
|
+
public FILE_ID_128 FileId128;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
300
|
+
private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
301
|
+
{
|
|
302
|
+
public Int64 PerProcessUserTimeLimit;
|
|
303
|
+
public Int64 PerJobUserTimeLimit;
|
|
304
|
+
public UInt32 LimitFlags;
|
|
305
|
+
public UIntPtr MinimumWorkingSetSize;
|
|
306
|
+
public UIntPtr MaximumWorkingSetSize;
|
|
307
|
+
public UInt32 ActiveProcessLimit;
|
|
308
|
+
public UIntPtr Affinity;
|
|
309
|
+
public UInt32 PriorityClass;
|
|
310
|
+
public UInt32 SchedulingClass;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
314
|
+
private struct IO_COUNTERS
|
|
315
|
+
{
|
|
316
|
+
public UInt64 ReadOperationCount;
|
|
317
|
+
public UInt64 WriteOperationCount;
|
|
318
|
+
public UInt64 OtherOperationCount;
|
|
319
|
+
public UInt64 ReadTransferCount;
|
|
320
|
+
public UInt64 WriteTransferCount;
|
|
321
|
+
public UInt64 OtherTransferCount;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
325
|
+
private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
326
|
+
{
|
|
327
|
+
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
|
328
|
+
public IO_COUNTERS IoInfo;
|
|
329
|
+
public UIntPtr ProcessMemoryLimit;
|
|
330
|
+
public UIntPtr JobMemoryLimit;
|
|
331
|
+
public UIntPtr PeakProcessMemoryUsed;
|
|
332
|
+
public UIntPtr PeakJobMemoryUsed;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
336
|
+
private struct JOBOBJECT_BASIC_ACCOUNTING_INFORMATION
|
|
337
|
+
{
|
|
338
|
+
public Int64 TotalUserTime;
|
|
339
|
+
public Int64 TotalKernelTime;
|
|
340
|
+
public Int64 ThisPeriodTotalUserTime;
|
|
341
|
+
public Int64 ThisPeriodTotalKernelTime;
|
|
342
|
+
public UInt32 TotalPageFaultCount;
|
|
343
|
+
public UInt32 TotalProcesses;
|
|
344
|
+
public UInt32 ActiveProcesses;
|
|
345
|
+
public UInt32 TotalTerminatedProcesses;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
[DllImport("kernel32.dll")]
|
|
349
|
+
private static extern IntPtr GetCurrentProcess();
|
|
350
|
+
|
|
351
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
|
352
|
+
private static extern UInt32 GetDriveType(string rootPathName);
|
|
353
|
+
|
|
354
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
355
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
356
|
+
private static extern bool CloseHandle(IntPtr handle);
|
|
357
|
+
|
|
358
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
359
|
+
private static extern IntPtr LocalFree(IntPtr memory);
|
|
360
|
+
|
|
361
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
362
|
+
private static extern IntPtr FreeSid(IntPtr sid);
|
|
363
|
+
|
|
364
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
365
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
366
|
+
private static extern bool EqualSid(IntPtr firstSid, IntPtr secondSid);
|
|
367
|
+
|
|
368
|
+
[DllImport(
|
|
369
|
+
"advapi32.dll",
|
|
370
|
+
CharSet = CharSet.Unicode,
|
|
371
|
+
SetLastError = true)]
|
|
372
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
373
|
+
private static extern bool ConvertSidToStringSid(
|
|
374
|
+
IntPtr sid,
|
|
375
|
+
out IntPtr stringSid);
|
|
376
|
+
|
|
377
|
+
[DllImport("userenv.dll", CharSet = CharSet.Unicode)]
|
|
378
|
+
private static extern Int32 CreateAppContainerProfile(
|
|
379
|
+
string appContainerName,
|
|
380
|
+
string displayName,
|
|
381
|
+
string description,
|
|
382
|
+
IntPtr capabilities,
|
|
383
|
+
UInt32 capabilityCount,
|
|
384
|
+
out IntPtr appContainerSid);
|
|
385
|
+
|
|
386
|
+
[DllImport("userenv.dll", CharSet = CharSet.Unicode)]
|
|
387
|
+
private static extern Int32 DeriveAppContainerSidFromAppContainerName(
|
|
388
|
+
string appContainerName,
|
|
389
|
+
out IntPtr appContainerSid);
|
|
390
|
+
|
|
391
|
+
[DllImport("userenv.dll", CharSet = CharSet.Unicode)]
|
|
392
|
+
private static extern Int32 DeleteAppContainerProfile(
|
|
393
|
+
string appContainerName);
|
|
394
|
+
|
|
395
|
+
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
|
|
396
|
+
private static extern IntPtr _get_osfhandle(Int32 fileDescriptor);
|
|
397
|
+
|
|
398
|
+
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
|
|
399
|
+
private static extern Int32 _close(Int32 fileDescriptor);
|
|
400
|
+
|
|
401
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
402
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
403
|
+
private static extern bool SetHandleInformation(
|
|
404
|
+
IntPtr handle,
|
|
405
|
+
UInt32 mask,
|
|
406
|
+
UInt32 flags);
|
|
407
|
+
|
|
408
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
409
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
410
|
+
private static extern bool InitializeProcThreadAttributeList(
|
|
411
|
+
IntPtr attributeList,
|
|
412
|
+
Int32 attributeCount,
|
|
413
|
+
Int32 flags,
|
|
414
|
+
ref IntPtr size);
|
|
415
|
+
|
|
416
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
417
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
418
|
+
private static extern bool UpdateProcThreadAttribute(
|
|
419
|
+
IntPtr attributeList,
|
|
420
|
+
UInt32 flags,
|
|
421
|
+
IntPtr attribute,
|
|
422
|
+
IntPtr value,
|
|
423
|
+
IntPtr size,
|
|
424
|
+
IntPtr previousValue,
|
|
425
|
+
IntPtr returnSize);
|
|
426
|
+
|
|
427
|
+
[DllImport("kernel32.dll")]
|
|
428
|
+
private static extern void DeleteProcThreadAttributeList(
|
|
429
|
+
IntPtr attributeList);
|
|
430
|
+
|
|
431
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
432
|
+
private static extern UInt32 GetFileType(IntPtr handle);
|
|
433
|
+
|
|
434
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
435
|
+
private static extern IntPtr CreateFile(
|
|
436
|
+
string fileName,
|
|
437
|
+
UInt32 desiredAccess,
|
|
438
|
+
UInt32 shareMode,
|
|
439
|
+
ref SECURITY_ATTRIBUTES securityAttributes,
|
|
440
|
+
UInt32 creationDisposition,
|
|
441
|
+
UInt32 flagsAndAttributes,
|
|
442
|
+
IntPtr templateFile);
|
|
443
|
+
|
|
444
|
+
[DllImport(
|
|
445
|
+
"kernel32.dll",
|
|
446
|
+
CharSet = CharSet.Unicode,
|
|
447
|
+
SetLastError = true)]
|
|
448
|
+
private static extern IntPtr CreateEvent(
|
|
449
|
+
IntPtr eventAttributes,
|
|
450
|
+
[MarshalAs(UnmanagedType.Bool)] bool manualReset,
|
|
451
|
+
[MarshalAs(UnmanagedType.Bool)] bool initialState,
|
|
452
|
+
string name);
|
|
453
|
+
|
|
454
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
455
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
456
|
+
private static extern bool CreatePipe(
|
|
457
|
+
out IntPtr readPipe,
|
|
458
|
+
out IntPtr writePipe,
|
|
459
|
+
ref SECURITY_ATTRIBUTES pipeAttributes,
|
|
460
|
+
UInt32 size);
|
|
461
|
+
|
|
462
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
463
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
464
|
+
private static extern bool DeviceIoControl(
|
|
465
|
+
IntPtr device,
|
|
466
|
+
UInt32 controlCode,
|
|
467
|
+
IntPtr inputBuffer,
|
|
468
|
+
UInt32 inputBufferSize,
|
|
469
|
+
IntPtr outputBuffer,
|
|
470
|
+
UInt32 outputBufferSize,
|
|
471
|
+
out UInt32 bytesReturned,
|
|
472
|
+
IntPtr overlapped);
|
|
473
|
+
|
|
474
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
475
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
476
|
+
private static extern bool CancelIoEx(
|
|
477
|
+
IntPtr handle,
|
|
478
|
+
IntPtr overlapped);
|
|
479
|
+
|
|
480
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
481
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
482
|
+
private static extern bool GetFileInformationByHandle(
|
|
483
|
+
IntPtr file,
|
|
484
|
+
out BY_HANDLE_FILE_INFORMATION information);
|
|
485
|
+
|
|
486
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
487
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
488
|
+
private static extern bool GetFileInformationByHandleEx(
|
|
489
|
+
IntPtr file,
|
|
490
|
+
Int32 informationClass,
|
|
491
|
+
out FILE_ID_INFO information,
|
|
492
|
+
UInt32 informationSize);
|
|
493
|
+
|
|
494
|
+
[DllImport("ntdll.dll")]
|
|
495
|
+
private static extern Int32 NtQueryInformationFile(
|
|
496
|
+
IntPtr file,
|
|
497
|
+
out IO_STATUS_BLOCK ioStatusBlock,
|
|
498
|
+
out FILE_STAT_INFORMATION information,
|
|
499
|
+
UInt32 informationSize,
|
|
500
|
+
Int32 informationClass);
|
|
501
|
+
|
|
502
|
+
[DllImport(
|
|
503
|
+
"api-ms-win-core-file-l2-1-4.dll",
|
|
504
|
+
CharSet = CharSet.Unicode,
|
|
505
|
+
ExactSpelling = true,
|
|
506
|
+
SetLastError = true)]
|
|
507
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
508
|
+
private static extern bool GetFileInformationByName(
|
|
509
|
+
string fileName,
|
|
510
|
+
Int32 informationClass,
|
|
511
|
+
out FILE_STAT_BASIC_INFORMATION information,
|
|
512
|
+
UInt32 informationSize);
|
|
513
|
+
|
|
514
|
+
[DllImport(
|
|
515
|
+
"kernel32.dll",
|
|
516
|
+
CharSet = CharSet.Unicode,
|
|
517
|
+
SetLastError = true)]
|
|
518
|
+
private static extern UInt32 GetFinalPathNameByHandle(
|
|
519
|
+
IntPtr file,
|
|
520
|
+
StringBuilder path,
|
|
521
|
+
UInt32 pathLength,
|
|
522
|
+
UInt32 flags);
|
|
523
|
+
|
|
524
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
525
|
+
private static extern UInt32 ResumeThread(IntPtr thread);
|
|
526
|
+
|
|
527
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
528
|
+
private static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);
|
|
529
|
+
|
|
530
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
531
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
532
|
+
private static extern bool GetExitCodeProcess(IntPtr process, out UInt32 exitCode);
|
|
533
|
+
|
|
534
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
535
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
536
|
+
private static extern bool TerminateProcess(IntPtr process, UInt32 exitCode);
|
|
537
|
+
|
|
538
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
539
|
+
private static extern IntPtr CreateJobObject(IntPtr attributes, string name);
|
|
540
|
+
|
|
541
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
542
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
543
|
+
private static extern bool SetInformationJobObject(
|
|
544
|
+
IntPtr job,
|
|
545
|
+
Int32 infoClass,
|
|
546
|
+
IntPtr info,
|
|
547
|
+
UInt32 infoLength);
|
|
548
|
+
|
|
549
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
550
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
551
|
+
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
|
552
|
+
|
|
553
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
554
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
555
|
+
private static extern bool TerminateJobObject(
|
|
556
|
+
IntPtr job,
|
|
557
|
+
UInt32 exitCode);
|
|
558
|
+
|
|
559
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
560
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
561
|
+
private static extern bool QueryInformationJobObject(
|
|
562
|
+
IntPtr job,
|
|
563
|
+
Int32 infoClass,
|
|
564
|
+
IntPtr info,
|
|
565
|
+
UInt32 infoLength,
|
|
566
|
+
out UInt32 returnLength);
|
|
567
|
+
|
|
568
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
569
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
570
|
+
private static extern bool OpenProcessToken(
|
|
571
|
+
IntPtr process,
|
|
572
|
+
UInt32 desiredAccess,
|
|
573
|
+
out IntPtr token);
|
|
574
|
+
|
|
575
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
576
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
577
|
+
private static extern bool CreateRestrictedToken(
|
|
578
|
+
IntPtr existingToken,
|
|
579
|
+
UInt32 flags,
|
|
580
|
+
UInt32 disableSidCount,
|
|
581
|
+
IntPtr sidsToDisable,
|
|
582
|
+
UInt32 deletePrivilegeCount,
|
|
583
|
+
IntPtr privilegesToDelete,
|
|
584
|
+
UInt32 restrictedSidCount,
|
|
585
|
+
IntPtr sidsToRestrict,
|
|
586
|
+
out IntPtr restrictedToken);
|
|
587
|
+
|
|
588
|
+
[DllImport("advapi32.dll")]
|
|
589
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
590
|
+
private static extern bool IsTokenRestricted(IntPtr token);
|
|
591
|
+
|
|
592
|
+
[DllImport("advapi32.dll", SetLastError = true)]
|
|
593
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
594
|
+
private static extern bool GetTokenInformation(
|
|
595
|
+
IntPtr token,
|
|
596
|
+
Int32 tokenInformationClass,
|
|
597
|
+
IntPtr tokenInformation,
|
|
598
|
+
UInt32 tokenInformationLength,
|
|
599
|
+
out UInt32 returnLength);
|
|
600
|
+
|
|
601
|
+
[DllImport(
|
|
602
|
+
"advapi32.dll",
|
|
603
|
+
CharSet = CharSet.Unicode,
|
|
604
|
+
SetLastError = true)]
|
|
605
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
606
|
+
private static extern bool LookupPrivilegeName(
|
|
607
|
+
string systemName,
|
|
608
|
+
ref LUID luid,
|
|
609
|
+
StringBuilder name,
|
|
610
|
+
ref UInt32 nameLength);
|
|
611
|
+
|
|
612
|
+
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
613
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
614
|
+
private static extern bool CreateProcessAsUser(
|
|
615
|
+
IntPtr token,
|
|
616
|
+
string applicationName,
|
|
617
|
+
StringBuilder commandLine,
|
|
618
|
+
IntPtr processAttributes,
|
|
619
|
+
IntPtr threadAttributes,
|
|
620
|
+
[MarshalAs(UnmanagedType.Bool)] bool inheritHandles,
|
|
621
|
+
UInt32 creationFlags,
|
|
622
|
+
IntPtr environment,
|
|
623
|
+
string currentDirectory,
|
|
624
|
+
ref STARTUPINFOEX startupInfo,
|
|
625
|
+
out PROCESS_INFORMATION processInformation);
|
|
626
|
+
|
|
627
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
628
|
+
private static extern IntPtr GetStdHandle(Int32 standardHandle);
|
|
629
|
+
|
|
630
|
+
private static void ThrowLastError(string operation)
|
|
631
|
+
{
|
|
632
|
+
int code = Marshal.GetLastWin32Error();
|
|
633
|
+
string systemMessage = new Win32Exception(code).Message;
|
|
634
|
+
throw new Win32Exception(
|
|
635
|
+
code,
|
|
636
|
+
String.Format(
|
|
637
|
+
CultureInfo.InvariantCulture,
|
|
638
|
+
"{0} failed (win32={1}: {2})",
|
|
639
|
+
operation,
|
|
640
|
+
code,
|
|
641
|
+
systemMessage));
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private static void ThrowHResult(string operation, Int32 hresult)
|
|
645
|
+
{
|
|
646
|
+
Exception systemError = Marshal.GetExceptionForHR(hresult);
|
|
647
|
+
throw new COMException(
|
|
648
|
+
String.Format(
|
|
649
|
+
CultureInfo.InvariantCulture,
|
|
650
|
+
"{0} failed (hresult=0x{1:X8}: {2})",
|
|
651
|
+
operation,
|
|
652
|
+
unchecked((UInt32)hresult),
|
|
653
|
+
systemError == null ? "unknown error" : systemError.Message),
|
|
654
|
+
hresult);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private static void ValidateAppContainerName(string appContainerName)
|
|
658
|
+
{
|
|
659
|
+
if (
|
|
660
|
+
String.IsNullOrWhiteSpace(appContainerName) ||
|
|
661
|
+
appContainerName.Length > 64)
|
|
662
|
+
{
|
|
663
|
+
throw new ArgumentException(
|
|
664
|
+
"AppContainer profile name must contain 1-64 characters");
|
|
665
|
+
}
|
|
666
|
+
foreach (char current in appContainerName)
|
|
667
|
+
{
|
|
668
|
+
if (
|
|
669
|
+
Char.IsLetterOrDigit(current) ||
|
|
670
|
+
current == '-' ||
|
|
671
|
+
current == '_' ||
|
|
672
|
+
current == '.' ||
|
|
673
|
+
current == ' ')
|
|
674
|
+
{
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
throw new ArgumentException(
|
|
678
|
+
"AppContainer profile name contains an unsupported character");
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
private static IntPtr EnsureAppContainerProfile(string appContainerName)
|
|
683
|
+
{
|
|
684
|
+
ValidateAppContainerName(appContainerName);
|
|
685
|
+
IntPtr appContainerSid;
|
|
686
|
+
Int32 hresult = CreateAppContainerProfile(
|
|
687
|
+
appContainerName,
|
|
688
|
+
"ChainlessChain CLI Sandbox",
|
|
689
|
+
"Zero-capability process sandbox for ChainlessChain CLI",
|
|
690
|
+
IntPtr.Zero,
|
|
691
|
+
0,
|
|
692
|
+
out appContainerSid);
|
|
693
|
+
if (hresult == HRESULT_FROM_WIN32_ERROR_ALREADY_EXISTS)
|
|
694
|
+
{
|
|
695
|
+
hresult = DeriveAppContainerSidFromAppContainerName(
|
|
696
|
+
appContainerName,
|
|
697
|
+
out appContainerSid);
|
|
698
|
+
}
|
|
699
|
+
if (hresult < 0)
|
|
700
|
+
ThrowHResult("CreateAppContainerProfile", hresult);
|
|
701
|
+
if (IsInvalidHandle(appContainerSid))
|
|
702
|
+
throw new InvalidDataException(
|
|
703
|
+
"AppContainer profile did not return a SID");
|
|
704
|
+
return appContainerSid;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private static string SidToString(IntPtr sid)
|
|
708
|
+
{
|
|
709
|
+
if (IsInvalidHandle(sid))
|
|
710
|
+
throw new InvalidDataException("Cannot stringify an invalid SID");
|
|
711
|
+
IntPtr stringSid;
|
|
712
|
+
if (!ConvertSidToStringSid(sid, out stringSid))
|
|
713
|
+
ThrowLastError("ConvertSidToStringSid");
|
|
714
|
+
try
|
|
715
|
+
{
|
|
716
|
+
string value = Marshal.PtrToStringUni(stringSid);
|
|
717
|
+
if (String.IsNullOrWhiteSpace(value))
|
|
718
|
+
throw new InvalidDataException(
|
|
719
|
+
"ConvertSidToStringSid returned an empty SID");
|
|
720
|
+
return value;
|
|
721
|
+
}
|
|
722
|
+
finally
|
|
723
|
+
{
|
|
724
|
+
LocalFree(stringSid);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
public static string PrepareAppContainerProfile(string appContainerName)
|
|
729
|
+
{
|
|
730
|
+
IntPtr appContainerSid = IntPtr.Zero;
|
|
731
|
+
try
|
|
732
|
+
{
|
|
733
|
+
appContainerSid = EnsureAppContainerProfile(appContainerName);
|
|
734
|
+
return SidToString(appContainerSid);
|
|
735
|
+
}
|
|
736
|
+
finally
|
|
737
|
+
{
|
|
738
|
+
if (!IsInvalidHandle(appContainerSid))
|
|
739
|
+
FreeSid(appContainerSid);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
public static void DeletePreparedAppContainerProfile(
|
|
744
|
+
string appContainerName,
|
|
745
|
+
string expectedAppContainerSid)
|
|
746
|
+
{
|
|
747
|
+
// DeleteAppContainerProfile is idempotent for an absent profile.
|
|
748
|
+
// Deriving the deterministic SID first keeps both the native
|
|
749
|
+
// finally path and the broker fallback bound to the same profile.
|
|
750
|
+
ValidateAppContainerName(appContainerName);
|
|
751
|
+
if (!String.IsNullOrWhiteSpace(expectedAppContainerSid))
|
|
752
|
+
{
|
|
753
|
+
IntPtr derivedSid = IntPtr.Zero;
|
|
754
|
+
try
|
|
755
|
+
{
|
|
756
|
+
Int32 deriveResult =
|
|
757
|
+
DeriveAppContainerSidFromAppContainerName(
|
|
758
|
+
appContainerName,
|
|
759
|
+
out derivedSid);
|
|
760
|
+
if (deriveResult < 0)
|
|
761
|
+
{
|
|
762
|
+
ThrowHResult(
|
|
763
|
+
"DeriveAppContainerSidFromAppContainerName",
|
|
764
|
+
deriveResult);
|
|
765
|
+
}
|
|
766
|
+
string actualSid = SidToString(derivedSid);
|
|
767
|
+
if (!String.Equals(
|
|
768
|
+
actualSid,
|
|
769
|
+
expectedAppContainerSid,
|
|
770
|
+
StringComparison.OrdinalIgnoreCase))
|
|
771
|
+
{
|
|
772
|
+
throw new InvalidDataException(
|
|
773
|
+
"Refusing to delete an AppContainer profile whose SID does not match");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
finally
|
|
777
|
+
{
|
|
778
|
+
if (!IsInvalidHandle(derivedSid))
|
|
779
|
+
FreeSid(derivedSid);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
Int32 deleteResult = 0;
|
|
784
|
+
for (int attempt = 0; attempt < 40; attempt++)
|
|
785
|
+
{
|
|
786
|
+
deleteResult = DeleteAppContainerProfile(appContainerName);
|
|
787
|
+
if (deleteResult >= 0)
|
|
788
|
+
return;
|
|
789
|
+
if (attempt < 39)
|
|
790
|
+
System.Threading.Thread.Sleep(25);
|
|
791
|
+
}
|
|
792
|
+
ThrowHResult("DeleteAppContainerProfile", deleteResult);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
public static void AssertAppContainerProfileAbsent(
|
|
796
|
+
string appContainerName)
|
|
797
|
+
{
|
|
798
|
+
ValidateAppContainerName(appContainerName);
|
|
799
|
+
IntPtr verificationSid = IntPtr.Zero;
|
|
800
|
+
bool createdVerificationProfile = false;
|
|
801
|
+
string verificationSidText = null;
|
|
802
|
+
try
|
|
803
|
+
{
|
|
804
|
+
Int32 createResult = CreateAppContainerProfile(
|
|
805
|
+
appContainerName,
|
|
806
|
+
"ChainlessChain CLI Sandbox Cleanup Verification",
|
|
807
|
+
"Ephemeral profile used to verify sandbox cleanup",
|
|
808
|
+
IntPtr.Zero,
|
|
809
|
+
0,
|
|
810
|
+
out verificationSid);
|
|
811
|
+
if (createResult == HRESULT_FROM_WIN32_ERROR_ALREADY_EXISTS)
|
|
812
|
+
{
|
|
813
|
+
throw new InvalidDataException(
|
|
814
|
+
"AppContainer profile still exists after cleanup");
|
|
815
|
+
}
|
|
816
|
+
if (createResult < 0)
|
|
817
|
+
ThrowHResult(
|
|
818
|
+
"CreateAppContainerProfile(cleanup verification)",
|
|
819
|
+
createResult);
|
|
820
|
+
createdVerificationProfile = true;
|
|
821
|
+
verificationSidText = SidToString(verificationSid);
|
|
822
|
+
}
|
|
823
|
+
finally
|
|
824
|
+
{
|
|
825
|
+
if (!IsInvalidHandle(verificationSid))
|
|
826
|
+
FreeSid(verificationSid);
|
|
827
|
+
if (createdVerificationProfile)
|
|
828
|
+
{
|
|
829
|
+
DeletePreparedAppContainerProfile(
|
|
830
|
+
appContainerName,
|
|
831
|
+
verificationSidText);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private static void TerminateAndAwaitEmptyJob(IntPtr job)
|
|
837
|
+
{
|
|
838
|
+
if (job == IntPtr.Zero)
|
|
839
|
+
return;
|
|
840
|
+
if (!TerminateJobObject(job, 125))
|
|
841
|
+
ThrowLastError("TerminateJobObject");
|
|
842
|
+
|
|
843
|
+
int informationSize = Marshal.SizeOf(
|
|
844
|
+
typeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION));
|
|
845
|
+
IntPtr information = Marshal.AllocHGlobal(informationSize);
|
|
846
|
+
try
|
|
847
|
+
{
|
|
848
|
+
for (int attempt = 0; attempt < 100; attempt++)
|
|
849
|
+
{
|
|
850
|
+
UInt32 returnLength;
|
|
851
|
+
if (!QueryInformationJobObject(
|
|
852
|
+
job,
|
|
853
|
+
JobObjectBasicAccountingInformation,
|
|
854
|
+
information,
|
|
855
|
+
checked((UInt32)informationSize),
|
|
856
|
+
out returnLength))
|
|
857
|
+
{
|
|
858
|
+
ThrowLastError("QueryInformationJobObject");
|
|
859
|
+
}
|
|
860
|
+
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accounting =
|
|
861
|
+
(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION)
|
|
862
|
+
Marshal.PtrToStructure(
|
|
863
|
+
information,
|
|
864
|
+
typeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION));
|
|
865
|
+
if (accounting.ActiveProcesses == 0)
|
|
866
|
+
return;
|
|
867
|
+
System.Threading.Thread.Sleep(10);
|
|
868
|
+
}
|
|
869
|
+
throw new TimeoutException(
|
|
870
|
+
"Windows sandbox Job retained active processes after termination");
|
|
871
|
+
}
|
|
872
|
+
finally
|
|
873
|
+
{
|
|
874
|
+
Marshal.FreeHGlobal(information);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
private static IntPtr ReadTokenInformation(
|
|
879
|
+
IntPtr token,
|
|
880
|
+
Int32 informationClass,
|
|
881
|
+
out UInt32 informationLength)
|
|
882
|
+
{
|
|
883
|
+
informationLength = 0;
|
|
884
|
+
bool probeSucceeded = GetTokenInformation(
|
|
885
|
+
token,
|
|
886
|
+
informationClass,
|
|
887
|
+
IntPtr.Zero,
|
|
888
|
+
0,
|
|
889
|
+
out informationLength);
|
|
890
|
+
int probeError = Marshal.GetLastWin32Error();
|
|
891
|
+
if (
|
|
892
|
+
probeSucceeded ||
|
|
893
|
+
probeError != ERROR_INSUFFICIENT_BUFFER ||
|
|
894
|
+
informationLength == 0)
|
|
895
|
+
{
|
|
896
|
+
throw new Win32Exception(
|
|
897
|
+
probeError,
|
|
898
|
+
"GetTokenInformation(size) failed");
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
IntPtr information = Marshal.AllocHGlobal(
|
|
902
|
+
checked((Int32)informationLength));
|
|
903
|
+
try
|
|
904
|
+
{
|
|
905
|
+
for (int offset = 0; offset < checked((Int32)informationLength); offset++)
|
|
906
|
+
Marshal.WriteByte(information, offset, 0);
|
|
907
|
+
if (!GetTokenInformation(
|
|
908
|
+
token,
|
|
909
|
+
informationClass,
|
|
910
|
+
information,
|
|
911
|
+
informationLength,
|
|
912
|
+
out informationLength))
|
|
913
|
+
{
|
|
914
|
+
ThrowLastError("GetTokenInformation");
|
|
915
|
+
}
|
|
916
|
+
return information;
|
|
917
|
+
}
|
|
918
|
+
catch
|
|
919
|
+
{
|
|
920
|
+
Marshal.FreeHGlobal(information);
|
|
921
|
+
throw;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private static string AttestAppContainerTarget(
|
|
926
|
+
IntPtr process,
|
|
927
|
+
IntPtr expectedAppContainerSid)
|
|
928
|
+
{
|
|
929
|
+
IntPtr targetToken = IntPtr.Zero;
|
|
930
|
+
IntPtr isAppContainerBuffer = IntPtr.Zero;
|
|
931
|
+
IntPtr capabilitiesBuffer = IntPtr.Zero;
|
|
932
|
+
IntPtr appContainerBuffer = IntPtr.Zero;
|
|
933
|
+
IntPtr privilegesBuffer = IntPtr.Zero;
|
|
934
|
+
try
|
|
935
|
+
{
|
|
936
|
+
if (!OpenProcessToken(process, TOKEN_QUERY, out targetToken))
|
|
937
|
+
ThrowLastError("OpenProcessToken(target)");
|
|
938
|
+
|
|
939
|
+
UInt32 informationLength;
|
|
940
|
+
isAppContainerBuffer = ReadTokenInformation(
|
|
941
|
+
targetToken,
|
|
942
|
+
TokenIsAppContainer,
|
|
943
|
+
out informationLength);
|
|
944
|
+
if (
|
|
945
|
+
informationLength < sizeof(Int32) ||
|
|
946
|
+
Marshal.ReadInt32(isAppContainerBuffer) == 0)
|
|
947
|
+
{
|
|
948
|
+
throw new InvalidDataException(
|
|
949
|
+
"Target token is not an AppContainer token");
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
capabilitiesBuffer = ReadTokenInformation(
|
|
953
|
+
targetToken,
|
|
954
|
+
TokenCapabilities,
|
|
955
|
+
out informationLength);
|
|
956
|
+
if (
|
|
957
|
+
informationLength < sizeof(Int32) ||
|
|
958
|
+
Marshal.ReadInt32(capabilitiesBuffer) != 0)
|
|
959
|
+
{
|
|
960
|
+
throw new InvalidDataException(
|
|
961
|
+
"Target AppContainer token is not zero-capability");
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
appContainerBuffer = ReadTokenInformation(
|
|
965
|
+
targetToken,
|
|
966
|
+
TokenAppContainerSid,
|
|
967
|
+
out informationLength);
|
|
968
|
+
if (informationLength < IntPtr.Size)
|
|
969
|
+
throw new InvalidDataException(
|
|
970
|
+
"Target AppContainer token omitted its SID");
|
|
971
|
+
IntPtr actualAppContainerSid =
|
|
972
|
+
Marshal.ReadIntPtr(appContainerBuffer);
|
|
973
|
+
if (
|
|
974
|
+
IsInvalidHandle(actualAppContainerSid) ||
|
|
975
|
+
!EqualSid(actualAppContainerSid, expectedAppContainerSid))
|
|
976
|
+
{
|
|
977
|
+
throw new InvalidDataException(
|
|
978
|
+
"Target AppContainer SID does not match the prepared profile");
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
privilegesBuffer = ReadTokenInformation(
|
|
982
|
+
targetToken,
|
|
983
|
+
TokenPrivileges,
|
|
984
|
+
out informationLength);
|
|
985
|
+
if (informationLength < sizeof(Int32))
|
|
986
|
+
throw new InvalidDataException(
|
|
987
|
+
"Target token omitted its privilege list");
|
|
988
|
+
Int32 privilegeCount = Marshal.ReadInt32(privilegesBuffer);
|
|
989
|
+
int privilegeOffset = sizeof(Int32);
|
|
990
|
+
int privilegeSize = Marshal.SizeOf(
|
|
991
|
+
typeof(LUID_AND_ATTRIBUTES));
|
|
992
|
+
for (int index = 0; index < privilegeCount; index++)
|
|
993
|
+
{
|
|
994
|
+
int entryOffset = checked(
|
|
995
|
+
privilegeOffset + index * privilegeSize);
|
|
996
|
+
if (
|
|
997
|
+
checked((UInt32)(entryOffset + privilegeSize)) >
|
|
998
|
+
informationLength)
|
|
999
|
+
throw new InvalidDataException(
|
|
1000
|
+
"Target token privilege list is truncated");
|
|
1001
|
+
LUID_AND_ATTRIBUTES privilege =
|
|
1002
|
+
(LUID_AND_ATTRIBUTES)Marshal.PtrToStructure(
|
|
1003
|
+
IntPtr.Add(privilegesBuffer, entryOffset),
|
|
1004
|
+
typeof(LUID_AND_ATTRIBUTES));
|
|
1005
|
+
UInt32 privilegeNameLength = 0;
|
|
1006
|
+
LookupPrivilegeName(
|
|
1007
|
+
null,
|
|
1008
|
+
ref privilege.Luid,
|
|
1009
|
+
null,
|
|
1010
|
+
ref privilegeNameLength);
|
|
1011
|
+
if (privilegeNameLength == 0)
|
|
1012
|
+
ThrowLastError("LookupPrivilegeName(size)");
|
|
1013
|
+
StringBuilder privilegeName = new StringBuilder(
|
|
1014
|
+
checked((Int32)privilegeNameLength + 1));
|
|
1015
|
+
if (!LookupPrivilegeName(
|
|
1016
|
+
null,
|
|
1017
|
+
ref privilege.Luid,
|
|
1018
|
+
privilegeName,
|
|
1019
|
+
ref privilegeNameLength))
|
|
1020
|
+
{
|
|
1021
|
+
ThrowLastError("LookupPrivilegeName");
|
|
1022
|
+
}
|
|
1023
|
+
if (!String.Equals(
|
|
1024
|
+
privilegeName.ToString(),
|
|
1025
|
+
"SeChangeNotifyPrivilege",
|
|
1026
|
+
StringComparison.Ordinal))
|
|
1027
|
+
{
|
|
1028
|
+
throw new InvalidDataException(
|
|
1029
|
+
"Target AppContainer token retained an unexpected privilege");
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return SidToString(actualAppContainerSid);
|
|
1033
|
+
}
|
|
1034
|
+
finally
|
|
1035
|
+
{
|
|
1036
|
+
if (privilegesBuffer != IntPtr.Zero)
|
|
1037
|
+
Marshal.FreeHGlobal(privilegesBuffer);
|
|
1038
|
+
if (appContainerBuffer != IntPtr.Zero)
|
|
1039
|
+
Marshal.FreeHGlobal(appContainerBuffer);
|
|
1040
|
+
if (capabilitiesBuffer != IntPtr.Zero)
|
|
1041
|
+
Marshal.FreeHGlobal(capabilitiesBuffer);
|
|
1042
|
+
if (isAppContainerBuffer != IntPtr.Zero)
|
|
1043
|
+
Marshal.FreeHGlobal(isAppContainerBuffer);
|
|
1044
|
+
if (targetToken != IntPtr.Zero)
|
|
1045
|
+
CloseHandle(targetToken);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
public static string QuoteArgument(string value)
|
|
1050
|
+
{
|
|
1051
|
+
if (value == null) value = String.Empty;
|
|
1052
|
+
if (value.Length > 0 &&
|
|
1053
|
+
value.IndexOfAny(new[] { ' ', '\t', '\n', '\v', '"' }) < 0)
|
|
1054
|
+
{
|
|
1055
|
+
return value;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
StringBuilder result = new StringBuilder();
|
|
1059
|
+
result.Append('"');
|
|
1060
|
+
int backslashes = 0;
|
|
1061
|
+
foreach (char current in value)
|
|
1062
|
+
{
|
|
1063
|
+
if (current == '\\')
|
|
1064
|
+
{
|
|
1065
|
+
backslashes++;
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (current == '"')
|
|
1069
|
+
{
|
|
1070
|
+
result.Append('\\', backslashes * 2 + 1);
|
|
1071
|
+
result.Append('"');
|
|
1072
|
+
backslashes = 0;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
result.Append('\\', backslashes);
|
|
1076
|
+
backslashes = 0;
|
|
1077
|
+
result.Append(current);
|
|
1078
|
+
}
|
|
1079
|
+
result.Append('\\', backslashes * 2);
|
|
1080
|
+
result.Append('"');
|
|
1081
|
+
return result.ToString();
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
public static string BuildCommandLine(string application, string[] arguments)
|
|
1085
|
+
{
|
|
1086
|
+
StringBuilder line = new StringBuilder(QuoteArgument(application));
|
|
1087
|
+
if (arguments != null)
|
|
1088
|
+
{
|
|
1089
|
+
foreach (string argument in arguments)
|
|
1090
|
+
{
|
|
1091
|
+
line.Append(' ');
|
|
1092
|
+
line.Append(QuoteArgument(argument));
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return line.ToString();
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
private static string BuildCreateProcessCommandLine(
|
|
1099
|
+
string application,
|
|
1100
|
+
string[] arguments)
|
|
1101
|
+
{
|
|
1102
|
+
// cmd.exe /s /c has its own quoting contract: the command after
|
|
1103
|
+
// /c must be surrounded by one literal quote pair while quotes
|
|
1104
|
+
// inside that command remain untouched. Applying the normal CRT
|
|
1105
|
+
// backslash escaping here makes cmd.exe treat those backslashes as
|
|
1106
|
+
// data, which silently breaks commands such as:
|
|
1107
|
+
// node -e "process.stdout.write('value')"
|
|
1108
|
+
if (
|
|
1109
|
+
Path.GetFileName(application).Equals(
|
|
1110
|
+
"cmd.exe",
|
|
1111
|
+
StringComparison.OrdinalIgnoreCase) &&
|
|
1112
|
+
arguments != null &&
|
|
1113
|
+
arguments.Length == 4 &&
|
|
1114
|
+
arguments[0].Equals("/d", StringComparison.OrdinalIgnoreCase) &&
|
|
1115
|
+
arguments[1].Equals("/s", StringComparison.OrdinalIgnoreCase) &&
|
|
1116
|
+
arguments[2].Equals("/c", StringComparison.OrdinalIgnoreCase))
|
|
1117
|
+
{
|
|
1118
|
+
return
|
|
1119
|
+
QuoteArgument(application) +
|
|
1120
|
+
" /d /s /c \"" +
|
|
1121
|
+
arguments[3] +
|
|
1122
|
+
"\"";
|
|
1123
|
+
}
|
|
1124
|
+
return BuildCommandLine(application, arguments);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
private static string GetEnvironmentValue(
|
|
1128
|
+
Dictionary<string, string> environment,
|
|
1129
|
+
string name,
|
|
1130
|
+
string fallback)
|
|
1131
|
+
{
|
|
1132
|
+
if (environment != null)
|
|
1133
|
+
{
|
|
1134
|
+
foreach (KeyValuePair<string, string> entry in environment)
|
|
1135
|
+
{
|
|
1136
|
+
if (String.Equals(
|
|
1137
|
+
entry.Key,
|
|
1138
|
+
name,
|
|
1139
|
+
StringComparison.OrdinalIgnoreCase))
|
|
1140
|
+
{
|
|
1141
|
+
return entry.Value;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return fallback;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
private static Dictionary<string, string> CaptureCurrentEnvironment()
|
|
1149
|
+
{
|
|
1150
|
+
Dictionary<string, string> captured =
|
|
1151
|
+
new Dictionary<string, string>(
|
|
1152
|
+
StringComparer.OrdinalIgnoreCase);
|
|
1153
|
+
foreach (
|
|
1154
|
+
System.Collections.DictionaryEntry entry in
|
|
1155
|
+
Environment.GetEnvironmentVariables())
|
|
1156
|
+
{
|
|
1157
|
+
string name = Convert.ToString(
|
|
1158
|
+
entry.Key,
|
|
1159
|
+
CultureInfo.InvariantCulture);
|
|
1160
|
+
string value = Convert.ToString(
|
|
1161
|
+
entry.Value,
|
|
1162
|
+
CultureInfo.InvariantCulture);
|
|
1163
|
+
if (!String.IsNullOrEmpty(name))
|
|
1164
|
+
captured[name] = value ?? String.Empty;
|
|
1165
|
+
}
|
|
1166
|
+
return captured;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
private static void RemoveEnvironmentValue(
|
|
1170
|
+
Dictionary<string, string> environment,
|
|
1171
|
+
string name)
|
|
1172
|
+
{
|
|
1173
|
+
if (environment == null) return;
|
|
1174
|
+
string matched = null;
|
|
1175
|
+
foreach (string key in environment.Keys)
|
|
1176
|
+
{
|
|
1177
|
+
if (String.Equals(
|
|
1178
|
+
key,
|
|
1179
|
+
name,
|
|
1180
|
+
StringComparison.OrdinalIgnoreCase))
|
|
1181
|
+
{
|
|
1182
|
+
matched = key;
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (matched != null) environment.Remove(matched);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
private static void SetEnvironmentValue(
|
|
1190
|
+
Dictionary<string, string> environment,
|
|
1191
|
+
string name,
|
|
1192
|
+
string value)
|
|
1193
|
+
{
|
|
1194
|
+
if (environment == null)
|
|
1195
|
+
throw new ArgumentNullException("environment");
|
|
1196
|
+
RemoveEnvironmentValue(environment, name);
|
|
1197
|
+
environment[name] = value ?? String.Empty;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
private static void ConfigureAppContainerEnvironment(
|
|
1201
|
+
Dictionary<string, string> environment)
|
|
1202
|
+
{
|
|
1203
|
+
// CreateProcess extends this trusted host location into the
|
|
1204
|
+
// AppContainer profile. Passing the already-extended AC path would
|
|
1205
|
+
// make that transformation ambiguous; never recover this value
|
|
1206
|
+
// from the caller-controlled launch environment.
|
|
1207
|
+
string localAppData = NormalizeLocalDosPath(
|
|
1208
|
+
Environment.GetFolderPath(
|
|
1209
|
+
Environment.SpecialFolder.LocalApplicationData),
|
|
1210
|
+
null,
|
|
1211
|
+
"Host local app data");
|
|
1212
|
+
string temporaryDirectory =
|
|
1213
|
+
Path.Combine(localAppData, "Temp");
|
|
1214
|
+
string systemRoot = Path.GetPathRoot(
|
|
1215
|
+
Environment.SystemDirectory);
|
|
1216
|
+
if (!IsLocalDosDriveRoot(systemRoot))
|
|
1217
|
+
throw new InvalidDataException(
|
|
1218
|
+
"Windows system directory is not on a local DOS drive");
|
|
1219
|
+
SetEnvironmentValue(
|
|
1220
|
+
environment,
|
|
1221
|
+
"LOCALAPPDATA",
|
|
1222
|
+
localAppData);
|
|
1223
|
+
SetEnvironmentValue(
|
|
1224
|
+
environment,
|
|
1225
|
+
"TEMP",
|
|
1226
|
+
temporaryDirectory);
|
|
1227
|
+
SetEnvironmentValue(
|
|
1228
|
+
environment,
|
|
1229
|
+
"TMP",
|
|
1230
|
+
temporaryDirectory);
|
|
1231
|
+
SetEnvironmentValue(
|
|
1232
|
+
environment,
|
|
1233
|
+
"SystemDrive",
|
|
1234
|
+
systemRoot.Substring(0, 2));
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
private static IntPtr BuildEnvironmentBlock(
|
|
1238
|
+
Dictionary<string, string> environment)
|
|
1239
|
+
{
|
|
1240
|
+
SortedDictionary<string, string> sorted =
|
|
1241
|
+
new SortedDictionary<string, string>(
|
|
1242
|
+
StringComparer.OrdinalIgnoreCase);
|
|
1243
|
+
if (environment != null)
|
|
1244
|
+
{
|
|
1245
|
+
foreach (KeyValuePair<string, string> entry in environment)
|
|
1246
|
+
{
|
|
1247
|
+
if (
|
|
1248
|
+
String.IsNullOrEmpty(entry.Key) ||
|
|
1249
|
+
entry.Key.IndexOf('=') >= 0 ||
|
|
1250
|
+
entry.Key.IndexOf('\0') >= 0 ||
|
|
1251
|
+
(entry.Value ?? String.Empty).IndexOf('\0') >= 0)
|
|
1252
|
+
{
|
|
1253
|
+
throw new InvalidDataException(
|
|
1254
|
+
"Target environment contains an invalid entry");
|
|
1255
|
+
}
|
|
1256
|
+
if (sorted.ContainsKey(entry.Key))
|
|
1257
|
+
throw new InvalidDataException(
|
|
1258
|
+
"Target environment contains duplicate keys");
|
|
1259
|
+
sorted.Add(entry.Key, entry.Value ?? String.Empty);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
StringBuilder block = new StringBuilder();
|
|
1264
|
+
foreach (KeyValuePair<string, string> entry in sorted)
|
|
1265
|
+
{
|
|
1266
|
+
block.Append(entry.Key);
|
|
1267
|
+
block.Append('=');
|
|
1268
|
+
block.Append(entry.Value);
|
|
1269
|
+
block.Append('\0');
|
|
1270
|
+
}
|
|
1271
|
+
if (sorted.Count == 0) block.Append('\0');
|
|
1272
|
+
block.Append('\0');
|
|
1273
|
+
if (block.Length > 32767)
|
|
1274
|
+
throw new InvalidDataException(
|
|
1275
|
+
"Target environment exceeds the Windows environment-block limit");
|
|
1276
|
+
return Marshal.StringToHGlobalUni(block.ToString());
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
private static bool IsLocalDosDriveRoot(string root)
|
|
1280
|
+
{
|
|
1281
|
+
return
|
|
1282
|
+
!String.IsNullOrEmpty(root) &&
|
|
1283
|
+
root.Length == 3 &&
|
|
1284
|
+
Char.IsLetter(root[0]) &&
|
|
1285
|
+
root[1] == ':' &&
|
|
1286
|
+
(root[2] == '\\' || root[2] == '/');
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
private static string NormalizeLocalDosPath(
|
|
1290
|
+
string value,
|
|
1291
|
+
string baseDirectory,
|
|
1292
|
+
string description)
|
|
1293
|
+
{
|
|
1294
|
+
if (
|
|
1295
|
+
String.IsNullOrWhiteSpace(value) ||
|
|
1296
|
+
value.IndexOf('\0') >= 0)
|
|
1297
|
+
{
|
|
1298
|
+
throw new InvalidDataException(
|
|
1299
|
+
description + " is empty or invalid");
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
string fullPath;
|
|
1303
|
+
if (Path.IsPathRooted(value))
|
|
1304
|
+
{
|
|
1305
|
+
string suppliedRoot = Path.GetPathRoot(value);
|
|
1306
|
+
if (!IsLocalDosDriveRoot(suppliedRoot))
|
|
1307
|
+
{
|
|
1308
|
+
throw new InvalidDataException(
|
|
1309
|
+
description + " must use a local DOS drive");
|
|
1310
|
+
}
|
|
1311
|
+
fullPath = Path.GetFullPath(value);
|
|
1312
|
+
}
|
|
1313
|
+
else
|
|
1314
|
+
{
|
|
1315
|
+
if (String.IsNullOrWhiteSpace(baseDirectory))
|
|
1316
|
+
{
|
|
1317
|
+
throw new InvalidDataException(
|
|
1318
|
+
description + " must be an absolute local path");
|
|
1319
|
+
}
|
|
1320
|
+
fullPath = Path.GetFullPath(
|
|
1321
|
+
Path.Combine(baseDirectory, value));
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
string root = Path.GetPathRoot(fullPath);
|
|
1325
|
+
if (!IsLocalDosDriveRoot(root))
|
|
1326
|
+
{
|
|
1327
|
+
throw new InvalidDataException(
|
|
1328
|
+
description + " must use a local DOS drive");
|
|
1329
|
+
}
|
|
1330
|
+
UInt32 driveType = GetDriveType(root);
|
|
1331
|
+
if (
|
|
1332
|
+
driveType == DRIVE_UNKNOWN ||
|
|
1333
|
+
driveType == DRIVE_NO_ROOT_DIR ||
|
|
1334
|
+
driveType == DRIVE_REMOTE)
|
|
1335
|
+
{
|
|
1336
|
+
throw new InvalidDataException(
|
|
1337
|
+
description + " must not use a remote or unavailable drive");
|
|
1338
|
+
}
|
|
1339
|
+
return fullPath;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
private static FileAttributes ValidateExistingLocalNonReparsePath(
|
|
1343
|
+
string fullPath,
|
|
1344
|
+
string description)
|
|
1345
|
+
{
|
|
1346
|
+
string root = Path.GetPathRoot(fullPath);
|
|
1347
|
+
string current = root;
|
|
1348
|
+
FileAttributes attributes = File.GetAttributes(root);
|
|
1349
|
+
string relative = fullPath.Substring(root.Length);
|
|
1350
|
+
string[] components = relative.Split(
|
|
1351
|
+
new[] { '\\', '/' },
|
|
1352
|
+
StringSplitOptions.RemoveEmptyEntries);
|
|
1353
|
+
foreach (string component in components)
|
|
1354
|
+
{
|
|
1355
|
+
current = Path.Combine(current, component);
|
|
1356
|
+
attributes = File.GetAttributes(current);
|
|
1357
|
+
if ((attributes & FileAttributes.ReparsePoint) != 0)
|
|
1358
|
+
{
|
|
1359
|
+
throw new InvalidDataException(
|
|
1360
|
+
description + " contains a reparse component");
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return attributes;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
private static void ValidateExistingLocalNonReparseFile(
|
|
1367
|
+
string fullPath,
|
|
1368
|
+
string description)
|
|
1369
|
+
{
|
|
1370
|
+
FileAttributes attributes =
|
|
1371
|
+
ValidateExistingLocalNonReparsePath(fullPath, description);
|
|
1372
|
+
if ((attributes & FileAttributes.Directory) != 0)
|
|
1373
|
+
{
|
|
1374
|
+
throw new FileNotFoundException(
|
|
1375
|
+
description + " is not a regular file",
|
|
1376
|
+
fullPath);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
private static FileAttributes ValidateExistingLocalPath(
|
|
1381
|
+
string fullPath,
|
|
1382
|
+
string description,
|
|
1383
|
+
bool allowReparsePaths)
|
|
1384
|
+
{
|
|
1385
|
+
return allowReparsePaths
|
|
1386
|
+
? File.GetAttributes(fullPath)
|
|
1387
|
+
: ValidateExistingLocalNonReparsePath(fullPath, description);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
private static void ValidateExistingLocalFile(
|
|
1391
|
+
string fullPath,
|
|
1392
|
+
string description,
|
|
1393
|
+
bool allowReparsePaths)
|
|
1394
|
+
{
|
|
1395
|
+
FileAttributes attributes = ValidateExistingLocalPath(
|
|
1396
|
+
fullPath,
|
|
1397
|
+
description,
|
|
1398
|
+
allowReparsePaths);
|
|
1399
|
+
if ((attributes & FileAttributes.Directory) != 0)
|
|
1400
|
+
{
|
|
1401
|
+
throw new FileNotFoundException(
|
|
1402
|
+
description + " is not a regular file",
|
|
1403
|
+
fullPath);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
private static bool TryValidateExistingLocalNonReparseDirectory(
|
|
1408
|
+
string fullPath,
|
|
1409
|
+
bool allowReparsePaths)
|
|
1410
|
+
{
|
|
1411
|
+
try
|
|
1412
|
+
{
|
|
1413
|
+
FileAttributes attributes = ValidateExistingLocalPath(
|
|
1414
|
+
fullPath,
|
|
1415
|
+
"Target PATH directory",
|
|
1416
|
+
allowReparsePaths);
|
|
1417
|
+
return (attributes & FileAttributes.Directory) != 0;
|
|
1418
|
+
}
|
|
1419
|
+
catch (FileNotFoundException)
|
|
1420
|
+
{
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
catch (DirectoryNotFoundException)
|
|
1424
|
+
{
|
|
1425
|
+
return false;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
private static string ResolveApplication(
|
|
1430
|
+
string application,
|
|
1431
|
+
string workingDirectory,
|
|
1432
|
+
Dictionary<string, string> environment,
|
|
1433
|
+
bool allowReparsePaths)
|
|
1434
|
+
{
|
|
1435
|
+
if (
|
|
1436
|
+
String.IsNullOrWhiteSpace(application) ||
|
|
1437
|
+
application.IndexOf('\0') >= 0)
|
|
1438
|
+
throw new ArgumentException("Target application is empty");
|
|
1439
|
+
|
|
1440
|
+
if (Path.IsPathRooted(application) || application.IndexOf('\\') >= 0 ||
|
|
1441
|
+
application.IndexOf('/') >= 0)
|
|
1442
|
+
{
|
|
1443
|
+
string rooted = NormalizeLocalDosPath(
|
|
1444
|
+
application,
|
|
1445
|
+
workingDirectory,
|
|
1446
|
+
"Target application");
|
|
1447
|
+
ValidateExistingLocalFile(
|
|
1448
|
+
rooted,
|
|
1449
|
+
"Target application",
|
|
1450
|
+
allowReparsePaths);
|
|
1451
|
+
return rooted;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
string[] extensions;
|
|
1455
|
+
if (Path.HasExtension(application))
|
|
1456
|
+
{
|
|
1457
|
+
extensions = new[] { String.Empty };
|
|
1458
|
+
}
|
|
1459
|
+
else
|
|
1460
|
+
{
|
|
1461
|
+
string pathExt = GetEnvironmentValue(
|
|
1462
|
+
environment,
|
|
1463
|
+
"PATHEXT",
|
|
1464
|
+
".COM;.EXE;.BAT;.CMD");
|
|
1465
|
+
string[] configured = pathExt.Split(
|
|
1466
|
+
new[] { ';' },
|
|
1467
|
+
StringSplitOptions.RemoveEmptyEntries);
|
|
1468
|
+
extensions = new string[configured.Length + 1];
|
|
1469
|
+
extensions[0] = String.Empty;
|
|
1470
|
+
for (int index = 0; index < configured.Length; index++)
|
|
1471
|
+
{
|
|
1472
|
+
string extension = configured[index].Trim();
|
|
1473
|
+
if (
|
|
1474
|
+
extension.Length < 2 ||
|
|
1475
|
+
extension[0] != '.' ||
|
|
1476
|
+
extension.IndexOfAny(
|
|
1477
|
+
new[] { '\\', '/', ':', '"', '\0' }) >= 0)
|
|
1478
|
+
{
|
|
1479
|
+
throw new InvalidDataException(
|
|
1480
|
+
"Target PATHEXT contains an invalid extension");
|
|
1481
|
+
}
|
|
1482
|
+
extensions[index + 1] = extension;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
string searchPath = GetEnvironmentValue(
|
|
1487
|
+
environment,
|
|
1488
|
+
"PATH",
|
|
1489
|
+
String.Empty);
|
|
1490
|
+
foreach (string directory in searchPath.Split(Path.PathSeparator))
|
|
1491
|
+
{
|
|
1492
|
+
if (String.IsNullOrWhiteSpace(directory)) continue;
|
|
1493
|
+
string searchDirectory = directory.Trim().Trim('"');
|
|
1494
|
+
searchDirectory = NormalizeLocalDosPath(
|
|
1495
|
+
searchDirectory,
|
|
1496
|
+
workingDirectory,
|
|
1497
|
+
"Target PATH directory");
|
|
1498
|
+
if (!TryValidateExistingLocalNonReparseDirectory(
|
|
1499
|
+
searchDirectory,
|
|
1500
|
+
allowReparsePaths))
|
|
1501
|
+
{
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
foreach (string extension in extensions)
|
|
1505
|
+
{
|
|
1506
|
+
string candidate = Path.Combine(
|
|
1507
|
+
searchDirectory,
|
|
1508
|
+
application + extension);
|
|
1509
|
+
try
|
|
1510
|
+
{
|
|
1511
|
+
ValidateExistingLocalFile(
|
|
1512
|
+
candidate,
|
|
1513
|
+
"Target application",
|
|
1514
|
+
allowReparsePaths);
|
|
1515
|
+
return candidate;
|
|
1516
|
+
}
|
|
1517
|
+
catch (FileNotFoundException)
|
|
1518
|
+
{
|
|
1519
|
+
// Continue searching the remaining local PATH entries.
|
|
1520
|
+
}
|
|
1521
|
+
catch (DirectoryNotFoundException)
|
|
1522
|
+
{
|
|
1523
|
+
// Continue searching the remaining local PATH entries.
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
throw new FileNotFoundException(
|
|
1528
|
+
"Target application was not found on PATH",
|
|
1529
|
+
application);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
private static Byte GetCrtFlags(IntPtr handle)
|
|
1533
|
+
{
|
|
1534
|
+
UInt32 fileType = GetFileType(handle);
|
|
1535
|
+
if (fileType == FILE_TYPE_DISK) return CRT_FOPEN;
|
|
1536
|
+
if (fileType == FILE_TYPE_PIPE)
|
|
1537
|
+
return CRT_FOPEN | CRT_FPIPE;
|
|
1538
|
+
if (fileType == FILE_TYPE_CHAR)
|
|
1539
|
+
return CRT_FOPEN | CRT_FDEV;
|
|
1540
|
+
throw new InvalidDataException(
|
|
1541
|
+
"Inherited descriptor has an unsupported handle type");
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
private static void AssertSnapshotRuntimeBitness(string application)
|
|
1545
|
+
{
|
|
1546
|
+
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES();
|
|
1547
|
+
attributes.nLength = checked(
|
|
1548
|
+
(UInt32)Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
|
|
1549
|
+
attributes.bInheritHandle = false;
|
|
1550
|
+
IntPtr runtimeHandle = CreateFile(
|
|
1551
|
+
application,
|
|
1552
|
+
GENERIC_READ,
|
|
1553
|
+
FILE_SHARE_READ,
|
|
1554
|
+
ref attributes,
|
|
1555
|
+
OPEN_EXISTING,
|
|
1556
|
+
FILE_ATTRIBUTE_NORMAL,
|
|
1557
|
+
IntPtr.Zero);
|
|
1558
|
+
if (IsInvalidHandle(runtimeHandle))
|
|
1559
|
+
ThrowLastError("CreateFile(entry snapshot runtime)");
|
|
1560
|
+
|
|
1561
|
+
UInt16 machine;
|
|
1562
|
+
try
|
|
1563
|
+
{
|
|
1564
|
+
using (SafeFileHandle safeHandle =
|
|
1565
|
+
new SafeFileHandle(runtimeHandle, false))
|
|
1566
|
+
using (FileStream stream =
|
|
1567
|
+
new FileStream(safeHandle, FileAccess.Read))
|
|
1568
|
+
using (BinaryReader reader = new BinaryReader(stream))
|
|
1569
|
+
{
|
|
1570
|
+
if (stream.Length < 64 || reader.ReadUInt16() != 0x5a4d)
|
|
1571
|
+
throw new InvalidDataException(
|
|
1572
|
+
"Entry snapshot runtime is not a PE image");
|
|
1573
|
+
stream.Position = 0x3c;
|
|
1574
|
+
Int32 peOffset = reader.ReadInt32();
|
|
1575
|
+
if (peOffset < 64 || peOffset > stream.Length - 6)
|
|
1576
|
+
throw new InvalidDataException(
|
|
1577
|
+
"Entry snapshot runtime has an invalid PE header");
|
|
1578
|
+
stream.Position = peOffset;
|
|
1579
|
+
if (reader.ReadUInt32() != 0x00004550)
|
|
1580
|
+
throw new InvalidDataException(
|
|
1581
|
+
"Entry snapshot runtime has an invalid PE signature");
|
|
1582
|
+
machine = reader.ReadUInt16();
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
finally
|
|
1586
|
+
{
|
|
1587
|
+
CloseHandle(runtimeHandle);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
bool compatible =
|
|
1591
|
+
((machine == IMAGE_FILE_MACHINE_I386 ||
|
|
1592
|
+
machine == IMAGE_FILE_MACHINE_ARMNT) &&
|
|
1593
|
+
IntPtr.Size == sizeof(Int32)) ||
|
|
1594
|
+
((machine == IMAGE_FILE_MACHINE_AMD64 ||
|
|
1595
|
+
machine == IMAGE_FILE_MACHINE_ARM64) &&
|
|
1596
|
+
IntPtr.Size == sizeof(Int64));
|
|
1597
|
+
if (!compatible)
|
|
1598
|
+
{
|
|
1599
|
+
throw new InvalidDataException(
|
|
1600
|
+
"Entry snapshot runtime bitness does not match the Windows sandbox helper");
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
private static bool IsInvalidHandle(IntPtr handle)
|
|
1605
|
+
{
|
|
1606
|
+
return
|
|
1607
|
+
handle == IntPtr.Zero ||
|
|
1608
|
+
handle == new IntPtr(-1) ||
|
|
1609
|
+
handle == new IntPtr(-2);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
[DataContract]
|
|
1613
|
+
public sealed class LaunchPathLockSpec
|
|
1614
|
+
{
|
|
1615
|
+
[DataMember]
|
|
1616
|
+
public string role { get; set; }
|
|
1617
|
+
[DataMember]
|
|
1618
|
+
public string path { get; set; }
|
|
1619
|
+
[DataMember]
|
|
1620
|
+
public string sha256 { get; set; }
|
|
1621
|
+
[DataMember]
|
|
1622
|
+
public long bytes { get; set; }
|
|
1623
|
+
[DataMember]
|
|
1624
|
+
public string dev { get; set; }
|
|
1625
|
+
[DataMember]
|
|
1626
|
+
public string ino { get; set; }
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
private sealed class NodeFileIdentityProjection
|
|
1630
|
+
{
|
|
1631
|
+
public readonly UInt64 Device;
|
|
1632
|
+
public readonly UInt64 FileId;
|
|
1633
|
+
|
|
1634
|
+
public NodeFileIdentityProjection(UInt64 device, UInt64 fileId)
|
|
1635
|
+
{
|
|
1636
|
+
Device = device;
|
|
1637
|
+
FileId = fileId;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
private sealed class LaunchPathFileIdentity
|
|
1642
|
+
{
|
|
1643
|
+
public UInt64 VolumeSerialNumber;
|
|
1644
|
+
public UInt64 FileIdLow;
|
|
1645
|
+
public UInt64 FileIdHigh;
|
|
1646
|
+
public NodeFileIdentityProjection LegacyNodeIdentity;
|
|
1647
|
+
public NodeFileIdentityProjection FixedFastNodeIdentity;
|
|
1648
|
+
public NodeFileIdentityProjection Node22FastNodeIdentity;
|
|
1649
|
+
public UInt64 Bytes;
|
|
1650
|
+
public UInt32 Links;
|
|
1651
|
+
public UInt32 Attributes;
|
|
1652
|
+
public string FinalPath;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
private sealed class LaunchPathLock : IDisposable
|
|
1656
|
+
{
|
|
1657
|
+
public readonly string Role;
|
|
1658
|
+
public readonly string ExpectedPath;
|
|
1659
|
+
public readonly IntPtr BreakEvent;
|
|
1660
|
+
public readonly LaunchPathFileIdentity Identity;
|
|
1661
|
+
public readonly byte[] SnapshotContent;
|
|
1662
|
+
private IntPtr lockingHandle;
|
|
1663
|
+
private IntPtr readHandle;
|
|
1664
|
+
private IntPtr eventHandle;
|
|
1665
|
+
private IntPtr overlapped;
|
|
1666
|
+
private bool oplockPending;
|
|
1667
|
+
private bool disposed;
|
|
1668
|
+
|
|
1669
|
+
public LaunchPathLock(
|
|
1670
|
+
LaunchPathLockSpec spec,
|
|
1671
|
+
IntPtr lockingHandleValue,
|
|
1672
|
+
IntPtr readHandleValue,
|
|
1673
|
+
IntPtr eventHandleValue,
|
|
1674
|
+
IntPtr overlappedValue,
|
|
1675
|
+
LaunchPathFileIdentity identity,
|
|
1676
|
+
byte[] snapshotContent)
|
|
1677
|
+
{
|
|
1678
|
+
Role = spec.role;
|
|
1679
|
+
ExpectedPath = spec.path;
|
|
1680
|
+
lockingHandle = lockingHandleValue;
|
|
1681
|
+
readHandle = readHandleValue;
|
|
1682
|
+
eventHandle = eventHandleValue;
|
|
1683
|
+
BreakEvent = eventHandleValue;
|
|
1684
|
+
overlapped = overlappedValue;
|
|
1685
|
+
oplockPending = true;
|
|
1686
|
+
Identity = identity;
|
|
1687
|
+
SnapshotContent = snapshotContent;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
public void Dispose()
|
|
1691
|
+
{
|
|
1692
|
+
if (disposed) return;
|
|
1693
|
+
disposed = true;
|
|
1694
|
+
ReleaseLaunchPathLockHandles(
|
|
1695
|
+
ref readHandle,
|
|
1696
|
+
ref lockingHandle,
|
|
1697
|
+
ref eventHandle,
|
|
1698
|
+
ref overlapped,
|
|
1699
|
+
oplockPending);
|
|
1700
|
+
oplockPending = false;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
private static void ReleaseLaunchPathLockHandles(
|
|
1705
|
+
ref IntPtr readHandle,
|
|
1706
|
+
ref IntPtr lockingHandle,
|
|
1707
|
+
ref IntPtr eventHandle,
|
|
1708
|
+
ref IntPtr overlapped,
|
|
1709
|
+
bool oplockPending)
|
|
1710
|
+
{
|
|
1711
|
+
if (!IsInvalidHandle(readHandle))
|
|
1712
|
+
{
|
|
1713
|
+
CloseHandle(readHandle);
|
|
1714
|
+
readHandle = IntPtr.Zero;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
bool abandonOverlappedStorage = false;
|
|
1718
|
+
if (!IsInvalidHandle(lockingHandle) && oplockPending)
|
|
1719
|
+
{
|
|
1720
|
+
// A Filter oplock owns one pending OVERLAPPED operation until
|
|
1721
|
+
// it breaks or is cancelled. Cancel and wait before freeing
|
|
1722
|
+
// that fixed native storage. On the defensive timeout path,
|
|
1723
|
+
// keep the event/storage allocated until this short-lived
|
|
1724
|
+
// helper exits so the kernel can never write into freed memory.
|
|
1725
|
+
CancelIoEx(lockingHandle, overlapped);
|
|
1726
|
+
UInt32 cancelWait = IsInvalidHandle(eventHandle)
|
|
1727
|
+
? WAIT_TIMEOUT
|
|
1728
|
+
: WaitForSingleObject(eventHandle, 10000);
|
|
1729
|
+
abandonOverlappedStorage = cancelWait != WAIT_OBJECT_0;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
if (!IsInvalidHandle(lockingHandle))
|
|
1733
|
+
{
|
|
1734
|
+
CloseHandle(lockingHandle);
|
|
1735
|
+
lockingHandle = IntPtr.Zero;
|
|
1736
|
+
}
|
|
1737
|
+
if (!abandonOverlappedStorage)
|
|
1738
|
+
{
|
|
1739
|
+
if (!IsInvalidHandle(eventHandle))
|
|
1740
|
+
{
|
|
1741
|
+
CloseHandle(eventHandle);
|
|
1742
|
+
eventHandle = IntPtr.Zero;
|
|
1743
|
+
}
|
|
1744
|
+
if (overlapped != IntPtr.Zero)
|
|
1745
|
+
{
|
|
1746
|
+
Marshal.FreeHGlobal(overlapped);
|
|
1747
|
+
overlapped = IntPtr.Zero;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
private static bool SameLaunchPathFile(
|
|
1753
|
+
LaunchPathFileIdentity left,
|
|
1754
|
+
LaunchPathFileIdentity right)
|
|
1755
|
+
{
|
|
1756
|
+
return
|
|
1757
|
+
left.VolumeSerialNumber == right.VolumeSerialNumber &&
|
|
1758
|
+
left.FileIdLow == right.FileIdLow &&
|
|
1759
|
+
left.FileIdHigh == right.FileIdHigh;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
private static bool SameNodeFileIdentityProjection(
|
|
1763
|
+
NodeFileIdentityProjection left,
|
|
1764
|
+
NodeFileIdentityProjection right)
|
|
1765
|
+
{
|
|
1766
|
+
if (left == null || right == null) return left == right;
|
|
1767
|
+
return left.Device == right.Device && left.FileId == right.FileId;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
private static bool SameNodeFileIdentityObservations(
|
|
1771
|
+
LaunchPathFileIdentity left,
|
|
1772
|
+
LaunchPathFileIdentity right)
|
|
1773
|
+
{
|
|
1774
|
+
return
|
|
1775
|
+
SameNodeFileIdentityProjection(
|
|
1776
|
+
left.LegacyNodeIdentity,
|
|
1777
|
+
right.LegacyNodeIdentity) &&
|
|
1778
|
+
SameNodeFileIdentityProjection(
|
|
1779
|
+
left.FixedFastNodeIdentity,
|
|
1780
|
+
right.FixedFastNodeIdentity) &&
|
|
1781
|
+
SameNodeFileIdentityProjection(
|
|
1782
|
+
left.Node22FastNodeIdentity,
|
|
1783
|
+
right.Node22FastNodeIdentity);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
private static bool MatchesNodeFileIdentityProjection(
|
|
1787
|
+
NodeFileIdentityProjection projection,
|
|
1788
|
+
UInt64 expectedDevice,
|
|
1789
|
+
UInt64 expectedFileId)
|
|
1790
|
+
{
|
|
1791
|
+
return
|
|
1792
|
+
projection != null &&
|
|
1793
|
+
projection.Device == expectedDevice &&
|
|
1794
|
+
projection.FileId == expectedFileId;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
private static bool MatchesExpectedNodeFileIdentity(
|
|
1798
|
+
LaunchPathFileIdentity identity,
|
|
1799
|
+
UInt64 expectedDevice,
|
|
1800
|
+
UInt64 expectedFileId)
|
|
1801
|
+
{
|
|
1802
|
+
// Keep each libuv projection paired. In particular, never combine
|
|
1803
|
+
// a device value from one stat implementation with a file ID from
|
|
1804
|
+
// another implementation.
|
|
1805
|
+
return
|
|
1806
|
+
MatchesNodeFileIdentityProjection(
|
|
1807
|
+
identity.LegacyNodeIdentity,
|
|
1808
|
+
expectedDevice,
|
|
1809
|
+
expectedFileId) ||
|
|
1810
|
+
MatchesNodeFileIdentityProjection(
|
|
1811
|
+
identity.FixedFastNodeIdentity,
|
|
1812
|
+
expectedDevice,
|
|
1813
|
+
expectedFileId) ||
|
|
1814
|
+
MatchesNodeFileIdentityProjection(
|
|
1815
|
+
identity.Node22FastNodeIdentity,
|
|
1816
|
+
expectedDevice,
|
|
1817
|
+
expectedFileId);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
private static string NormalizeFinalPath(string value)
|
|
1821
|
+
{
|
|
1822
|
+
string normalized = value;
|
|
1823
|
+
if (normalized.StartsWith(
|
|
1824
|
+
@"\\?\UNC\",
|
|
1825
|
+
StringComparison.OrdinalIgnoreCase))
|
|
1826
|
+
{
|
|
1827
|
+
normalized = @"\\" + normalized.Substring(8);
|
|
1828
|
+
}
|
|
1829
|
+
else if (normalized.StartsWith(
|
|
1830
|
+
@"\\?\",
|
|
1831
|
+
StringComparison.OrdinalIgnoreCase))
|
|
1832
|
+
{
|
|
1833
|
+
normalized = normalized.Substring(4);
|
|
1834
|
+
}
|
|
1835
|
+
return Path.GetFullPath(normalized);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
private static string GetHandleFinalPath(IntPtr handle)
|
|
1839
|
+
{
|
|
1840
|
+
UInt32 capacity = 512;
|
|
1841
|
+
while (capacity <= 32768)
|
|
1842
|
+
{
|
|
1843
|
+
StringBuilder output = new StringBuilder(checked((int)capacity));
|
|
1844
|
+
UInt32 written = GetFinalPathNameByHandle(
|
|
1845
|
+
handle,
|
|
1846
|
+
output,
|
|
1847
|
+
capacity,
|
|
1848
|
+
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
|
1849
|
+
if (written == 0) ThrowLastError("GetFinalPathNameByHandle");
|
|
1850
|
+
if (written < capacity)
|
|
1851
|
+
return NormalizeFinalPath(output.ToString());
|
|
1852
|
+
capacity = checked(written + 1);
|
|
1853
|
+
}
|
|
1854
|
+
throw new PathTooLongException(
|
|
1855
|
+
"Launch path exceeds the supported final-path limit");
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
private static bool TryReadHandlePathStatFileId(
|
|
1859
|
+
IntPtr handle,
|
|
1860
|
+
out UInt64 fileId)
|
|
1861
|
+
{
|
|
1862
|
+
IO_STATUS_BLOCK ioStatusBlock;
|
|
1863
|
+
FILE_STAT_INFORMATION information;
|
|
1864
|
+
Int32 status = NtQueryInformationFile(
|
|
1865
|
+
handle,
|
|
1866
|
+
out ioStatusBlock,
|
|
1867
|
+
out information,
|
|
1868
|
+
checked((UInt32)Marshal.SizeOf(typeof(FILE_STAT_INFORMATION))),
|
|
1869
|
+
FileStatInformation);
|
|
1870
|
+
if (status < 0)
|
|
1871
|
+
{
|
|
1872
|
+
fileId = 0;
|
|
1873
|
+
return false;
|
|
1874
|
+
}
|
|
1875
|
+
fileId = unchecked((UInt64)information.FileId);
|
|
1876
|
+
return true;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
private static bool TryReadFastNodeFileIdentityProjections(
|
|
1880
|
+
string finalPath,
|
|
1881
|
+
FILE_ID_INFO handleIdentity,
|
|
1882
|
+
UInt64 handlePathStatFileId,
|
|
1883
|
+
out NodeFileIdentityProjection fixedProjection,
|
|
1884
|
+
out NodeFileIdentityProjection node22Projection)
|
|
1885
|
+
{
|
|
1886
|
+
fixedProjection = null;
|
|
1887
|
+
node22Projection = null;
|
|
1888
|
+
FILE_STAT_BASIC_INFORMATION information;
|
|
1889
|
+
try
|
|
1890
|
+
{
|
|
1891
|
+
if (!GetFileInformationByName(
|
|
1892
|
+
finalPath,
|
|
1893
|
+
FileStatBasicByNameInfo,
|
|
1894
|
+
out information,
|
|
1895
|
+
checked(
|
|
1896
|
+
(UInt32)Marshal.SizeOf(
|
|
1897
|
+
typeof(FILE_STAT_BASIC_INFORMATION)))))
|
|
1898
|
+
{
|
|
1899
|
+
return false;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
catch (DllNotFoundException)
|
|
1903
|
+
{
|
|
1904
|
+
return false;
|
|
1905
|
+
}
|
|
1906
|
+
catch (EntryPointNotFoundException)
|
|
1907
|
+
{
|
|
1908
|
+
return false;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
UInt64 pathStatFileId = unchecked((UInt64)information.FileId);
|
|
1912
|
+
if (
|
|
1913
|
+
(information.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ||
|
|
1914
|
+
(information.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 ||
|
|
1915
|
+
information.VolumeSerialNumber !=
|
|
1916
|
+
handleIdentity.VolumeSerialNumber ||
|
|
1917
|
+
information.FileId128.LowPart !=
|
|
1918
|
+
handleIdentity.FileId.LowPart ||
|
|
1919
|
+
information.FileId128.HighPart !=
|
|
1920
|
+
handleIdentity.FileId.HighPart ||
|
|
1921
|
+
pathStatFileId != handlePathStatFileId)
|
|
1922
|
+
{
|
|
1923
|
+
throw new InvalidDataException(
|
|
1924
|
+
"Path stat identity does not match the locked launch handle");
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// libuv 1.51+ intentionally exposes only the low DWORD of the
|
|
1928
|
+
// volume serial so its handle and path stat implementations agree.
|
|
1929
|
+
fixedProjection = new NodeFileIdentityProjection(
|
|
1930
|
+
information.VolumeSerialNumber & UInt32.MaxValue,
|
|
1931
|
+
pathStatFileId);
|
|
1932
|
+
|
|
1933
|
+
// Node 22.12 ships libuv's original FILE_STAT_BASIC_INFORMATION
|
|
1934
|
+
// declaration with FileId128 and VolumeSerialNumber reversed.
|
|
1935
|
+
// With the documented layout, that declaration reads the high
|
|
1936
|
+
// half of FileId128 as st_dev. This compatibility projection is
|
|
1937
|
+
// safe only after the correctly laid-out path result above has
|
|
1938
|
+
// been tied back to this same handle's 128-bit identity.
|
|
1939
|
+
node22Projection = new NodeFileIdentityProjection(
|
|
1940
|
+
information.FileId128.HighPart,
|
|
1941
|
+
pathStatFileId);
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
private static LaunchPathFileIdentity ReadLaunchPathFileIdentity(
|
|
1946
|
+
IntPtr handle)
|
|
1947
|
+
{
|
|
1948
|
+
BY_HANDLE_FILE_INFORMATION basic;
|
|
1949
|
+
if (!GetFileInformationByHandle(handle, out basic))
|
|
1950
|
+
ThrowLastError("GetFileInformationByHandle(launch path)");
|
|
1951
|
+
if (
|
|
1952
|
+
(basic.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ||
|
|
1953
|
+
(basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0)
|
|
1954
|
+
{
|
|
1955
|
+
throw new InvalidDataException(
|
|
1956
|
+
"Launch path handle is not a regular non-reparse file");
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
FILE_ID_INFO fileId;
|
|
1960
|
+
if (!GetFileInformationByHandleEx(
|
|
1961
|
+
handle,
|
|
1962
|
+
FileIdInfo,
|
|
1963
|
+
out fileId,
|
|
1964
|
+
checked((UInt32)Marshal.SizeOf(typeof(FILE_ID_INFO)))))
|
|
1965
|
+
{
|
|
1966
|
+
ThrowLastError("GetFileInformationByHandleEx(FileIdInfo)");
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
string finalPath = GetHandleFinalPath(handle);
|
|
1970
|
+
UInt64 legacyFileId =
|
|
1971
|
+
((UInt64)basic.FileIndexHigh << 32) |
|
|
1972
|
+
(UInt64)basic.FileIndexLow;
|
|
1973
|
+
NodeFileIdentityProjection fixedFastNodeIdentity = null;
|
|
1974
|
+
NodeFileIdentityProjection node22FastNodeIdentity = null;
|
|
1975
|
+
UInt64 handlePathStatFileId;
|
|
1976
|
+
if (TryReadHandlePathStatFileId(handle, out handlePathStatFileId))
|
|
1977
|
+
{
|
|
1978
|
+
TryReadFastNodeFileIdentityProjections(
|
|
1979
|
+
finalPath,
|
|
1980
|
+
fileId,
|
|
1981
|
+
handlePathStatFileId,
|
|
1982
|
+
out fixedFastNodeIdentity,
|
|
1983
|
+
out node22FastNodeIdentity);
|
|
1984
|
+
}
|
|
1985
|
+
return new LaunchPathFileIdentity
|
|
1986
|
+
{
|
|
1987
|
+
VolumeSerialNumber = fileId.VolumeSerialNumber,
|
|
1988
|
+
FileIdLow = fileId.FileId.LowPart,
|
|
1989
|
+
FileIdHigh = fileId.FileId.HighPart,
|
|
1990
|
+
LegacyNodeIdentity = new NodeFileIdentityProjection(
|
|
1991
|
+
basic.VolumeSerialNumber,
|
|
1992
|
+
legacyFileId),
|
|
1993
|
+
FixedFastNodeIdentity = fixedFastNodeIdentity,
|
|
1994
|
+
Node22FastNodeIdentity = node22FastNodeIdentity,
|
|
1995
|
+
Bytes =
|
|
1996
|
+
((UInt64)basic.FileSizeHigh << 32) |
|
|
1997
|
+
(UInt64)basic.FileSizeLow,
|
|
1998
|
+
Links = basic.NumberOfLinks,
|
|
1999
|
+
Attributes = basic.FileAttributes,
|
|
2000
|
+
FinalPath = finalPath
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
private static string ValidateLocalNonReparsePath(string value)
|
|
2005
|
+
{
|
|
2006
|
+
if (String.IsNullOrWhiteSpace(value) || value.IndexOf('\0') >= 0)
|
|
2007
|
+
throw new InvalidDataException("Launch path is empty or invalid");
|
|
2008
|
+
string fullPath = NormalizeLocalDosPath(
|
|
2009
|
+
NormalizeFinalPath(value),
|
|
2010
|
+
null,
|
|
2011
|
+
"Launch path");
|
|
2012
|
+
ValidateExistingLocalNonReparseFile(fullPath, "Launch path");
|
|
2013
|
+
return fullPath;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
private static bool IsLowercaseSha256(string value)
|
|
2017
|
+
{
|
|
2018
|
+
if (String.IsNullOrEmpty(value) || value.Length != 64) return false;
|
|
2019
|
+
foreach (char character in value)
|
|
2020
|
+
{
|
|
2021
|
+
bool digit = character >= '0' && character <= '9';
|
|
2022
|
+
bool lowerHex = character >= 'a' && character <= 'f';
|
|
2023
|
+
if (!digit && !lowerHex) return false;
|
|
2024
|
+
}
|
|
2025
|
+
return true;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
private static string HashLaunchPathHandle(IntPtr handle)
|
|
2029
|
+
{
|
|
2030
|
+
using (SafeFileHandle safeHandle = new SafeFileHandle(handle, false))
|
|
2031
|
+
using (FileStream stream = new FileStream(
|
|
2032
|
+
safeHandle,
|
|
2033
|
+
FileAccess.Read,
|
|
2034
|
+
1024 * 1024,
|
|
2035
|
+
false))
|
|
2036
|
+
using (SHA256 algorithm = SHA256.Create())
|
|
2037
|
+
{
|
|
2038
|
+
byte[] digest = algorithm.ComputeHash(stream);
|
|
2039
|
+
StringBuilder encoded = new StringBuilder(digest.Length * 2);
|
|
2040
|
+
foreach (byte value in digest)
|
|
2041
|
+
encoded.Append(value.ToString("x2", CultureInfo.InvariantCulture));
|
|
2042
|
+
return encoded.ToString();
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
private static byte[] SnapshotLaunchPathHandle(
|
|
2047
|
+
IntPtr handle,
|
|
2048
|
+
UInt64 expectedBytes)
|
|
2049
|
+
{
|
|
2050
|
+
if (expectedBytes > Int32.MaxValue)
|
|
2051
|
+
throw new InvalidDataException(
|
|
2052
|
+
"Launch path snapshot exceeds the supported memory limit");
|
|
2053
|
+
byte[] content = new byte[checked((Int32)expectedBytes)];
|
|
2054
|
+
using (SafeFileHandle safeHandle = new SafeFileHandle(handle, false))
|
|
2055
|
+
using (FileStream stream = new FileStream(
|
|
2056
|
+
safeHandle,
|
|
2057
|
+
FileAccess.Read,
|
|
2058
|
+
1024 * 1024,
|
|
2059
|
+
false))
|
|
2060
|
+
{
|
|
2061
|
+
stream.Position = 0;
|
|
2062
|
+
int offset = 0;
|
|
2063
|
+
while (offset < content.Length)
|
|
2064
|
+
{
|
|
2065
|
+
int read = stream.Read(
|
|
2066
|
+
content,
|
|
2067
|
+
offset,
|
|
2068
|
+
content.Length - offset);
|
|
2069
|
+
if (read == 0)
|
|
2070
|
+
throw new EndOfStreamException(
|
|
2071
|
+
"Launch path ended while taking its content snapshot");
|
|
2072
|
+
offset += read;
|
|
2073
|
+
}
|
|
2074
|
+
if (stream.ReadByte() != -1)
|
|
2075
|
+
throw new InvalidDataException(
|
|
2076
|
+
"Launch path grew while taking its content snapshot");
|
|
2077
|
+
}
|
|
2078
|
+
return content;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
private static string HashBytes(byte[] content)
|
|
2082
|
+
{
|
|
2083
|
+
using (SHA256 algorithm = SHA256.Create())
|
|
2084
|
+
{
|
|
2085
|
+
byte[] digest = algorithm.ComputeHash(content);
|
|
2086
|
+
StringBuilder encoded = new StringBuilder(digest.Length * 2);
|
|
2087
|
+
foreach (byte value in digest)
|
|
2088
|
+
encoded.Append(value.ToString("x2", CultureInfo.InvariantCulture));
|
|
2089
|
+
return encoded.ToString();
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
private static LaunchPathLock AcquireLaunchPathLock(
|
|
2094
|
+
LaunchPathLockSpec spec)
|
|
2095
|
+
{
|
|
2096
|
+
UInt64 expectedDevice;
|
|
2097
|
+
UInt64 expectedFileId;
|
|
2098
|
+
if (
|
|
2099
|
+
spec == null ||
|
|
2100
|
+
(spec.role != "runtime" && spec.role != "entry") ||
|
|
2101
|
+
!IsLowercaseSha256(spec.sha256) ||
|
|
2102
|
+
!UInt64.TryParse(
|
|
2103
|
+
spec.dev,
|
|
2104
|
+
NumberStyles.None,
|
|
2105
|
+
CultureInfo.InvariantCulture,
|
|
2106
|
+
out expectedDevice) ||
|
|
2107
|
+
!UInt64.TryParse(
|
|
2108
|
+
spec.ino,
|
|
2109
|
+
NumberStyles.None,
|
|
2110
|
+
CultureInfo.InvariantCulture,
|
|
2111
|
+
out expectedFileId) ||
|
|
2112
|
+
spec.bytes < 0 ||
|
|
2113
|
+
(spec.role == "runtime" && spec.bytes > 256L * 1024L * 1024L) ||
|
|
2114
|
+
(spec.role == "entry" && spec.bytes > 64L * 1024L * 1024L))
|
|
2115
|
+
{
|
|
2116
|
+
throw new InvalidDataException(
|
|
2117
|
+
"Launch path lock specification is invalid");
|
|
2118
|
+
}
|
|
2119
|
+
string expectedPath = ValidateLocalNonReparsePath(spec.path);
|
|
2120
|
+
spec.path = expectedPath;
|
|
2121
|
+
|
|
2122
|
+
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES();
|
|
2123
|
+
attributes.nLength = checked(
|
|
2124
|
+
(UInt32)Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
|
|
2125
|
+
attributes.bInheritHandle = false;
|
|
2126
|
+
IntPtr lockingHandle = IntPtr.Zero;
|
|
2127
|
+
IntPtr readHandle = IntPtr.Zero;
|
|
2128
|
+
IntPtr eventHandle = IntPtr.Zero;
|
|
2129
|
+
IntPtr overlapped = IntPtr.Zero;
|
|
2130
|
+
bool oplockPending = false;
|
|
2131
|
+
try
|
|
2132
|
+
{
|
|
2133
|
+
lockingHandle = CreateFile(
|
|
2134
|
+
expectedPath,
|
|
2135
|
+
0,
|
|
2136
|
+
FILE_SHARE_READ,
|
|
2137
|
+
ref attributes,
|
|
2138
|
+
OPEN_EXISTING,
|
|
2139
|
+
FILE_FLAG_OVERLAPPED,
|
|
2140
|
+
IntPtr.Zero);
|
|
2141
|
+
if (IsInvalidHandle(lockingHandle))
|
|
2142
|
+
ThrowLastError("CreateFile(launch path locking handle)");
|
|
2143
|
+
|
|
2144
|
+
eventHandle = CreateEvent(IntPtr.Zero, true, false, null);
|
|
2145
|
+
if (IsInvalidHandle(eventHandle))
|
|
2146
|
+
ThrowLastError("CreateEvent(launch path oplock)");
|
|
2147
|
+
NATIVE_OVERLAPPED nativeOverlapped = new NATIVE_OVERLAPPED();
|
|
2148
|
+
nativeOverlapped.EventHandle = eventHandle;
|
|
2149
|
+
overlapped = Marshal.AllocHGlobal(
|
|
2150
|
+
Marshal.SizeOf(typeof(NATIVE_OVERLAPPED)));
|
|
2151
|
+
Marshal.StructureToPtr(nativeOverlapped, overlapped, false);
|
|
2152
|
+
|
|
2153
|
+
UInt32 returned;
|
|
2154
|
+
bool completed = DeviceIoControl(
|
|
2155
|
+
lockingHandle,
|
|
2156
|
+
FSCTL_REQUEST_FILTER_OPLOCK,
|
|
2157
|
+
IntPtr.Zero,
|
|
2158
|
+
0,
|
|
2159
|
+
IntPtr.Zero,
|
|
2160
|
+
0,
|
|
2161
|
+
out returned,
|
|
2162
|
+
overlapped);
|
|
2163
|
+
int oplockError = Marshal.GetLastWin32Error();
|
|
2164
|
+
if (completed || oplockError != ERROR_IO_PENDING)
|
|
2165
|
+
{
|
|
2166
|
+
throw new Win32Exception(
|
|
2167
|
+
oplockError,
|
|
2168
|
+
"FSCTL_REQUEST_FILTER_OPLOCK was not granted");
|
|
2169
|
+
}
|
|
2170
|
+
oplockPending = true;
|
|
2171
|
+
|
|
2172
|
+
readHandle = CreateFile(
|
|
2173
|
+
expectedPath,
|
|
2174
|
+
GENERIC_READ,
|
|
2175
|
+
FILE_SHARE_READ,
|
|
2176
|
+
ref attributes,
|
|
2177
|
+
OPEN_EXISTING,
|
|
2178
|
+
FILE_ATTRIBUTE_NORMAL,
|
|
2179
|
+
IntPtr.Zero);
|
|
2180
|
+
if (IsInvalidHandle(readHandle))
|
|
2181
|
+
ThrowLastError("CreateFile(launch path read handle)");
|
|
2182
|
+
|
|
2183
|
+
LaunchPathFileIdentity lockingIdentity =
|
|
2184
|
+
ReadLaunchPathFileIdentity(lockingHandle);
|
|
2185
|
+
LaunchPathFileIdentity before =
|
|
2186
|
+
ReadLaunchPathFileIdentity(readHandle);
|
|
2187
|
+
if (
|
|
2188
|
+
!SameLaunchPathFile(lockingIdentity, before) ||
|
|
2189
|
+
!String.Equals(
|
|
2190
|
+
lockingIdentity.FinalPath,
|
|
2191
|
+
expectedPath,
|
|
2192
|
+
StringComparison.OrdinalIgnoreCase) ||
|
|
2193
|
+
!String.Equals(
|
|
2194
|
+
before.FinalPath,
|
|
2195
|
+
expectedPath,
|
|
2196
|
+
StringComparison.OrdinalIgnoreCase))
|
|
2197
|
+
{
|
|
2198
|
+
throw new InvalidDataException(
|
|
2199
|
+
"Launch path changed while acquiring its Filter oplock");
|
|
2200
|
+
}
|
|
2201
|
+
if (before.Bytes != checked((UInt64)spec.bytes))
|
|
2202
|
+
throw new InvalidDataException(
|
|
2203
|
+
"Launch path size does not match its execution contract");
|
|
2204
|
+
if (!MatchesExpectedNodeFileIdentity(
|
|
2205
|
+
before,
|
|
2206
|
+
expectedDevice,
|
|
2207
|
+
expectedFileId))
|
|
2208
|
+
{
|
|
2209
|
+
throw new InvalidDataException(
|
|
2210
|
+
"Launch path file ID does not match its execution contract");
|
|
2211
|
+
}
|
|
2212
|
+
string actualSha256 = HashLaunchPathHandle(readHandle);
|
|
2213
|
+
byte[] snapshotContent =
|
|
2214
|
+
spec.role == "entry"
|
|
2215
|
+
? SnapshotLaunchPathHandle(readHandle, before.Bytes)
|
|
2216
|
+
: null;
|
|
2217
|
+
LaunchPathFileIdentity afterLocking =
|
|
2218
|
+
ReadLaunchPathFileIdentity(lockingHandle);
|
|
2219
|
+
LaunchPathFileIdentity after =
|
|
2220
|
+
ReadLaunchPathFileIdentity(readHandle);
|
|
2221
|
+
if (
|
|
2222
|
+
!SameLaunchPathFile(lockingIdentity, afterLocking) ||
|
|
2223
|
+
!SameLaunchPathFile(before, after) ||
|
|
2224
|
+
!SameLaunchPathFile(afterLocking, after) ||
|
|
2225
|
+
!SameNodeFileIdentityObservations(before, after) ||
|
|
2226
|
+
before.Bytes != after.Bytes ||
|
|
2227
|
+
before.Links != after.Links ||
|
|
2228
|
+
before.Attributes != after.Attributes ||
|
|
2229
|
+
!String.Equals(
|
|
2230
|
+
after.FinalPath,
|
|
2231
|
+
expectedPath,
|
|
2232
|
+
StringComparison.OrdinalIgnoreCase) ||
|
|
2233
|
+
!String.Equals(
|
|
2234
|
+
actualSha256,
|
|
2235
|
+
spec.sha256,
|
|
2236
|
+
StringComparison.Ordinal) ||
|
|
2237
|
+
(snapshotContent != null &&
|
|
2238
|
+
!String.Equals(
|
|
2239
|
+
HashBytes(snapshotContent),
|
|
2240
|
+
spec.sha256,
|
|
2241
|
+
StringComparison.Ordinal)))
|
|
2242
|
+
{
|
|
2243
|
+
throw new InvalidDataException(
|
|
2244
|
+
"Launch path identity or content changed during attestation");
|
|
2245
|
+
}
|
|
2246
|
+
UInt32 breakState = WaitForSingleObject(eventHandle, 0);
|
|
2247
|
+
if (breakState != WAIT_TIMEOUT)
|
|
2248
|
+
{
|
|
2249
|
+
if (breakState == UInt32.MaxValue)
|
|
2250
|
+
ThrowLastError("WaitForSingleObject(launch path oplock)");
|
|
2251
|
+
throw new InvalidDataException(
|
|
2252
|
+
"Launch path Filter oplock broke during attestation");
|
|
2253
|
+
}
|
|
2254
|
+
ValidateLocalNonReparsePath(expectedPath);
|
|
2255
|
+
|
|
2256
|
+
LaunchPathLock result = new LaunchPathLock(
|
|
2257
|
+
spec,
|
|
2258
|
+
lockingHandle,
|
|
2259
|
+
readHandle,
|
|
2260
|
+
eventHandle,
|
|
2261
|
+
overlapped,
|
|
2262
|
+
after,
|
|
2263
|
+
snapshotContent);
|
|
2264
|
+
lockingHandle = IntPtr.Zero;
|
|
2265
|
+
readHandle = IntPtr.Zero;
|
|
2266
|
+
eventHandle = IntPtr.Zero;
|
|
2267
|
+
overlapped = IntPtr.Zero;
|
|
2268
|
+
oplockPending = false;
|
|
2269
|
+
return result;
|
|
2270
|
+
}
|
|
2271
|
+
finally
|
|
2272
|
+
{
|
|
2273
|
+
ReleaseLaunchPathLockHandles(
|
|
2274
|
+
ref readHandle,
|
|
2275
|
+
ref lockingHandle,
|
|
2276
|
+
ref eventHandle,
|
|
2277
|
+
ref overlapped,
|
|
2278
|
+
oplockPending);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
private static List<LaunchPathLock> AcquireLaunchPathLocks(
|
|
2283
|
+
LaunchPathLockSpec[] specs,
|
|
2284
|
+
string application,
|
|
2285
|
+
string[] arguments)
|
|
2286
|
+
{
|
|
2287
|
+
List<LaunchPathLock> locks = new List<LaunchPathLock>();
|
|
2288
|
+
if (specs == null) return locks;
|
|
2289
|
+
try
|
|
2290
|
+
{
|
|
2291
|
+
if (specs.Length != 2)
|
|
2292
|
+
throw new InvalidDataException(
|
|
2293
|
+
"Plugin Node launch requires runtime and entry path locks");
|
|
2294
|
+
LaunchPathLockSpec runtimeSpec = null;
|
|
2295
|
+
LaunchPathLockSpec entrySpec = null;
|
|
2296
|
+
foreach (LaunchPathLockSpec spec in specs)
|
|
2297
|
+
{
|
|
2298
|
+
if (spec != null && spec.role == "runtime" && runtimeSpec == null)
|
|
2299
|
+
runtimeSpec = spec;
|
|
2300
|
+
else if (spec != null && spec.role == "entry" && entrySpec == null)
|
|
2301
|
+
entrySpec = spec;
|
|
2302
|
+
else
|
|
2303
|
+
throw new InvalidDataException(
|
|
2304
|
+
"Launch path lock roles must be unique");
|
|
2305
|
+
}
|
|
2306
|
+
if (
|
|
2307
|
+
runtimeSpec == null ||
|
|
2308
|
+
entrySpec == null ||
|
|
2309
|
+
!String.Equals(
|
|
2310
|
+
NormalizeFinalPath(application),
|
|
2311
|
+
NormalizeFinalPath(runtimeSpec.path),
|
|
2312
|
+
StringComparison.OrdinalIgnoreCase) ||
|
|
2313
|
+
arguments == null ||
|
|
2314
|
+
arguments.Length == 0 ||
|
|
2315
|
+
!String.Equals(
|
|
2316
|
+
NormalizeFinalPath(arguments[0]),
|
|
2317
|
+
NormalizeFinalPath(entrySpec.path),
|
|
2318
|
+
StringComparison.OrdinalIgnoreCase))
|
|
2319
|
+
{
|
|
2320
|
+
throw new InvalidDataException(
|
|
2321
|
+
"Launch command does not match its path lock contract");
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
LaunchPathLock runtimeLock = AcquireLaunchPathLock(runtimeSpec);
|
|
2325
|
+
locks.Add(runtimeLock);
|
|
2326
|
+
LaunchPathLock entryLock = AcquireLaunchPathLock(entrySpec);
|
|
2327
|
+
locks.Add(entryLock);
|
|
2328
|
+
if (SameLaunchPathFile(runtimeLock.Identity, entryLock.Identity))
|
|
2329
|
+
throw new InvalidDataException(
|
|
2330
|
+
"Runtime and entry path locks unexpectedly name one file");
|
|
2331
|
+
return locks;
|
|
2332
|
+
}
|
|
2333
|
+
catch
|
|
2334
|
+
{
|
|
2335
|
+
foreach (LaunchPathLock launchLock in locks)
|
|
2336
|
+
launchLock.Dispose();
|
|
2337
|
+
throw;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
private static void AssertLaunchPathLocksIntact(
|
|
2342
|
+
List<LaunchPathLock> locks)
|
|
2343
|
+
{
|
|
2344
|
+
foreach (LaunchPathLock launchLock in locks)
|
|
2345
|
+
{
|
|
2346
|
+
UInt32 state = WaitForSingleObject(launchLock.BreakEvent, 0);
|
|
2347
|
+
if (state == WAIT_TIMEOUT) continue;
|
|
2348
|
+
if (state == UInt32.MaxValue)
|
|
2349
|
+
ThrowLastError("WaitForSingleObject(launch path lock)");
|
|
2350
|
+
throw new InvalidDataException(
|
|
2351
|
+
"Launch path Filter oplock broke before target resume (" +
|
|
2352
|
+
launchLock.Role +
|
|
2353
|
+
")");
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
private static void ReattestLaunchPaths(
|
|
2358
|
+
List<LaunchPathLock> locks)
|
|
2359
|
+
{
|
|
2360
|
+
AssertLaunchPathLocksIntact(locks);
|
|
2361
|
+
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES();
|
|
2362
|
+
attributes.nLength = checked(
|
|
2363
|
+
(UInt32)Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
|
|
2364
|
+
attributes.bInheritHandle = false;
|
|
2365
|
+
foreach (LaunchPathLock launchLock in locks)
|
|
2366
|
+
{
|
|
2367
|
+
IntPtr pathHandle = CreateFile(
|
|
2368
|
+
launchLock.ExpectedPath,
|
|
2369
|
+
0,
|
|
2370
|
+
FILE_SHARE_READ,
|
|
2371
|
+
ref attributes,
|
|
2372
|
+
OPEN_EXISTING,
|
|
2373
|
+
FILE_ATTRIBUTE_NORMAL,
|
|
2374
|
+
IntPtr.Zero);
|
|
2375
|
+
if (IsInvalidHandle(pathHandle))
|
|
2376
|
+
ThrowLastError("CreateFile(launch path reattestation)");
|
|
2377
|
+
try
|
|
2378
|
+
{
|
|
2379
|
+
LaunchPathFileIdentity current =
|
|
2380
|
+
ReadLaunchPathFileIdentity(pathHandle);
|
|
2381
|
+
if (
|
|
2382
|
+
!SameLaunchPathFile(launchLock.Identity, current) ||
|
|
2383
|
+
!String.Equals(
|
|
2384
|
+
current.FinalPath,
|
|
2385
|
+
launchLock.ExpectedPath,
|
|
2386
|
+
StringComparison.OrdinalIgnoreCase))
|
|
2387
|
+
{
|
|
2388
|
+
throw new InvalidDataException(
|
|
2389
|
+
"Launch path identity changed before target resume (" +
|
|
2390
|
+
launchLock.Role +
|
|
2391
|
+
")");
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
finally
|
|
2395
|
+
{
|
|
2396
|
+
CloseHandle(pathHandle);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
AssertLaunchPathLocksIntact(locks);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
private static void ReleaseLaunchPathLocks(
|
|
2403
|
+
List<LaunchPathLock> locks)
|
|
2404
|
+
{
|
|
2405
|
+
foreach (LaunchPathLock launchLock in locks)
|
|
2406
|
+
launchLock.Dispose();
|
|
2407
|
+
locks.Clear();
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
private static byte[] TakeEntrySnapshot(
|
|
2411
|
+
List<LaunchPathLock> locks)
|
|
2412
|
+
{
|
|
2413
|
+
byte[] snapshot = null;
|
|
2414
|
+
for (int index = locks.Count - 1; index >= 0; index--)
|
|
2415
|
+
{
|
|
2416
|
+
LaunchPathLock launchLock = locks[index];
|
|
2417
|
+
if (launchLock.Role != "entry") continue;
|
|
2418
|
+
if (snapshot != null || launchLock.SnapshotContent == null)
|
|
2419
|
+
throw new InvalidDataException(
|
|
2420
|
+
"Plugin Node launch has an invalid entry snapshot");
|
|
2421
|
+
snapshot = launchLock.SnapshotContent;
|
|
2422
|
+
// Once copied into helper-owned memory, the Plugin Node entry
|
|
2423
|
+
// no longer needs a pathname or open file handle. Release its
|
|
2424
|
+
// stream guard before CreateProcess; the target receives only
|
|
2425
|
+
// the verified bytes through the private inherited pipe.
|
|
2426
|
+
launchLock.Dispose();
|
|
2427
|
+
locks.RemoveAt(index);
|
|
2428
|
+
}
|
|
2429
|
+
if (locks.Count > 0 && snapshot == null)
|
|
2430
|
+
throw new InvalidDataException(
|
|
2431
|
+
"Plugin Node launch has no verified entry snapshot");
|
|
2432
|
+
return snapshot;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
private static void AwaitSnapshotTestGate(
|
|
2436
|
+
byte[] entrySnapshot,
|
|
2437
|
+
LaunchPathLockSpec[] launchPathLockSpecs,
|
|
2438
|
+
string gateToken,
|
|
2439
|
+
string releasePath)
|
|
2440
|
+
{
|
|
2441
|
+
bool hasGateToken = !String.IsNullOrWhiteSpace(gateToken);
|
|
2442
|
+
bool hasReleasePath = !String.IsNullOrWhiteSpace(releasePath);
|
|
2443
|
+
if (!hasGateToken && !hasReleasePath) return;
|
|
2444
|
+
if (
|
|
2445
|
+
!hasGateToken ||
|
|
2446
|
+
!hasReleasePath ||
|
|
2447
|
+
entrySnapshot == null ||
|
|
2448
|
+
launchPathLockSpecs == null ||
|
|
2449
|
+
!IsLowercaseSha256(gateToken))
|
|
2450
|
+
{
|
|
2451
|
+
throw new InvalidDataException(
|
|
2452
|
+
"Entry snapshot test gate is incomplete");
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
string release = Path.GetFullPath(releasePath);
|
|
2456
|
+
if (
|
|
2457
|
+
!Path.IsPathRooted(release) ||
|
|
2458
|
+
File.Exists(release))
|
|
2459
|
+
{
|
|
2460
|
+
throw new InvalidDataException(
|
|
2461
|
+
"Entry snapshot test gate paths are invalid");
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
Console.Out.WriteLine(
|
|
2465
|
+
JsonCodec.SerializeObject(
|
|
2466
|
+
"eventName",
|
|
2467
|
+
"SNAPSHOT_CAPTURED",
|
|
2468
|
+
"token",
|
|
2469
|
+
gateToken));
|
|
2470
|
+
Console.Out.Flush();
|
|
2471
|
+
|
|
2472
|
+
DateTime deadline = DateTime.UtcNow.AddSeconds(30);
|
|
2473
|
+
while (!File.Exists(release))
|
|
2474
|
+
{
|
|
2475
|
+
if (DateTime.UtcNow >= deadline)
|
|
2476
|
+
throw new TimeoutException(
|
|
2477
|
+
"Timed out waiting for the entry snapshot test gate");
|
|
2478
|
+
Thread.Sleep(10);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
private static string[] BuildSnapshotNodeArguments(
|
|
2483
|
+
string[] arguments,
|
|
2484
|
+
int snapshotFd,
|
|
2485
|
+
byte[] snapshot)
|
|
2486
|
+
{
|
|
2487
|
+
if (arguments == null || arguments.Length == 0)
|
|
2488
|
+
throw new InvalidDataException(
|
|
2489
|
+
"Plugin Node entry snapshot has no original entry path");
|
|
2490
|
+
string expectedSha256 = HashBytes(snapshot);
|
|
2491
|
+
string bootstrap = String.Format(
|
|
2492
|
+
CultureInfo.InvariantCulture,
|
|
2493
|
+
"const fs=require('node:fs'),crypto=require('node:crypto')," +
|
|
2494
|
+
"Module=require('node:module')," +
|
|
2495
|
+
"path=require('node:path');" +
|
|
2496
|
+
"const filename=process.argv[1];let source;" +
|
|
2497
|
+
"try{{source=fs.readFileSync({0});}}" +
|
|
2498
|
+
"finally{{fs.closeSync({0});}}" +
|
|
2499
|
+
"if(source.length!=={1}||" +
|
|
2500
|
+
"crypto.createHash('sha256').update(source).digest('hex')!==" +
|
|
2501
|
+
"'{2}')throw new Error('entry snapshot pipe integrity failed');" +
|
|
2502
|
+
"const main=new Module(filename,null);main.id='.';" +
|
|
2503
|
+
"main.filename=filename;" +
|
|
2504
|
+
"main.paths=Module._nodeModulePaths(path.dirname(filename));" +
|
|
2505
|
+
"process.mainModule=main;process.execArgv.length=0;" +
|
|
2506
|
+
"delete process._eval;" +
|
|
2507
|
+
"Module._cache[filename]=main;" +
|
|
2508
|
+
"try{{main._compile(source.toString('utf8'),filename);" +
|
|
2509
|
+
"main.loaded=true;}}" +
|
|
2510
|
+
"catch(error){{delete Module._cache[filename];throw error;}}",
|
|
2511
|
+
snapshotFd,
|
|
2512
|
+
snapshot.Length,
|
|
2513
|
+
expectedSha256);
|
|
2514
|
+
string[] snapshotArguments = new string[arguments.Length + 3];
|
|
2515
|
+
snapshotArguments[0] = "--eval";
|
|
2516
|
+
snapshotArguments[1] = bootstrap;
|
|
2517
|
+
snapshotArguments[2] = "--";
|
|
2518
|
+
Array.Copy(
|
|
2519
|
+
arguments,
|
|
2520
|
+
0,
|
|
2521
|
+
snapshotArguments,
|
|
2522
|
+
3,
|
|
2523
|
+
arguments.Length);
|
|
2524
|
+
return snapshotArguments;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
private static void CreateEntrySnapshotPipe(
|
|
2528
|
+
int nodeIpcFd,
|
|
2529
|
+
out int snapshotFd,
|
|
2530
|
+
out IntPtr snapshotReadHandle,
|
|
2531
|
+
out IntPtr snapshotWriteHandle)
|
|
2532
|
+
{
|
|
2533
|
+
snapshotFd = 4;
|
|
2534
|
+
if (nodeIpcFd == snapshotFd)
|
|
2535
|
+
throw new InvalidDataException(
|
|
2536
|
+
"Node IPC descriptor conflicts with the entry snapshot pipe");
|
|
2537
|
+
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES();
|
|
2538
|
+
attributes.nLength = checked(
|
|
2539
|
+
(UInt32)Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
|
|
2540
|
+
attributes.bInheritHandle = true;
|
|
2541
|
+
if (!CreatePipe(
|
|
2542
|
+
out snapshotReadHandle,
|
|
2543
|
+
out snapshotWriteHandle,
|
|
2544
|
+
ref attributes,
|
|
2545
|
+
64 * 1024))
|
|
2546
|
+
{
|
|
2547
|
+
ThrowLastError("CreatePipe(entry snapshot)");
|
|
2548
|
+
}
|
|
2549
|
+
if (!SetHandleInformation(
|
|
2550
|
+
snapshotWriteHandle,
|
|
2551
|
+
HANDLE_FLAG_INHERIT,
|
|
2552
|
+
0))
|
|
2553
|
+
{
|
|
2554
|
+
CloseHandle(snapshotReadHandle);
|
|
2555
|
+
snapshotReadHandle = IntPtr.Zero;
|
|
2556
|
+
CloseHandle(snapshotWriteHandle);
|
|
2557
|
+
snapshotWriteHandle = IntPtr.Zero;
|
|
2558
|
+
ThrowLastError("SetHandleInformation(entry snapshot writer)");
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
private static void WriteEntrySnapshot(
|
|
2563
|
+
IntPtr snapshotWriteHandle,
|
|
2564
|
+
byte[] snapshot)
|
|
2565
|
+
{
|
|
2566
|
+
using (SafeFileHandle safeHandle =
|
|
2567
|
+
new SafeFileHandle(snapshotWriteHandle, false))
|
|
2568
|
+
using (FileStream stream = new FileStream(
|
|
2569
|
+
safeHandle,
|
|
2570
|
+
FileAccess.Write,
|
|
2571
|
+
64 * 1024,
|
|
2572
|
+
false))
|
|
2573
|
+
{
|
|
2574
|
+
stream.Write(snapshot, 0, snapshot.Length);
|
|
2575
|
+
stream.Flush();
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
private static IntPtr PrepareStandardHandle(
|
|
2580
|
+
Int32 standardHandle,
|
|
2581
|
+
UInt32 fallbackAccess,
|
|
2582
|
+
List<IntPtr> ownedHandles)
|
|
2583
|
+
{
|
|
2584
|
+
IntPtr handle = GetStdHandle(standardHandle);
|
|
2585
|
+
if (
|
|
2586
|
+
!IsInvalidHandle(handle) &&
|
|
2587
|
+
SetHandleInformation(
|
|
2588
|
+
handle,
|
|
2589
|
+
HANDLE_FLAG_INHERIT,
|
|
2590
|
+
HANDLE_FLAG_INHERIT))
|
|
2591
|
+
{
|
|
2592
|
+
return handle;
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES();
|
|
2596
|
+
attributes.nLength = checked(
|
|
2597
|
+
(UInt32)Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
|
|
2598
|
+
attributes.bInheritHandle = true;
|
|
2599
|
+
IntPtr fallback = CreateFile(
|
|
2600
|
+
"NUL",
|
|
2601
|
+
fallbackAccess,
|
|
2602
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
2603
|
+
ref attributes,
|
|
2604
|
+
OPEN_EXISTING,
|
|
2605
|
+
FILE_ATTRIBUTE_NORMAL,
|
|
2606
|
+
IntPtr.Zero);
|
|
2607
|
+
if (IsInvalidHandle(fallback))
|
|
2608
|
+
ThrowLastError("CreateFile(NUL)");
|
|
2609
|
+
ownedHandles.Add(fallback);
|
|
2610
|
+
return fallback;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
private static IntPtr BuildNodeDescriptorTable(
|
|
2614
|
+
int nodeIpcFd,
|
|
2615
|
+
int entrySnapshotFd,
|
|
2616
|
+
IntPtr entrySnapshotHandle,
|
|
2617
|
+
IntPtr standardInput,
|
|
2618
|
+
IntPtr standardOutput,
|
|
2619
|
+
IntPtr standardError,
|
|
2620
|
+
out UInt16 descriptorBytes)
|
|
2621
|
+
{
|
|
2622
|
+
descriptorBytes = 0;
|
|
2623
|
+
// stdin/stdout/stderr are transferred by STARTUPINFO. Higher CRT
|
|
2624
|
+
// descriptors carry the optional Node IPC channel and the verified
|
|
2625
|
+
// Plugin Node entry snapshot pipe.
|
|
2626
|
+
if (nodeIpcFd < 0 && entrySnapshotFd < 0) return IntPtr.Zero;
|
|
2627
|
+
if (nodeIpcFd > 255 || entrySnapshotFd > 255)
|
|
2628
|
+
throw new InvalidDataException(
|
|
2629
|
+
"Node descriptor is outside the CRT table range");
|
|
2630
|
+
|
|
2631
|
+
int descriptorCount = Math.Max(
|
|
2632
|
+
3,
|
|
2633
|
+
Math.Max(nodeIpcFd, entrySnapshotFd) + 1);
|
|
2634
|
+
int bufferSize = checked(
|
|
2635
|
+
sizeof(Int32) +
|
|
2636
|
+
descriptorCount +
|
|
2637
|
+
IntPtr.Size * descriptorCount);
|
|
2638
|
+
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
|
|
2639
|
+
try
|
|
2640
|
+
{
|
|
2641
|
+
for (int offset = 0; offset < bufferSize; offset++)
|
|
2642
|
+
Marshal.WriteByte(buffer, offset, 0);
|
|
2643
|
+
Marshal.WriteInt32(buffer, descriptorCount);
|
|
2644
|
+
for (int fd = 0; fd < descriptorCount; fd++)
|
|
2645
|
+
{
|
|
2646
|
+
IntPtr handle;
|
|
2647
|
+
if (fd == 0)
|
|
2648
|
+
handle = standardInput;
|
|
2649
|
+
else if (fd == 1)
|
|
2650
|
+
handle = standardOutput;
|
|
2651
|
+
else if (fd == 2)
|
|
2652
|
+
handle = standardError;
|
|
2653
|
+
else if (fd == nodeIpcFd)
|
|
2654
|
+
handle = _get_osfhandle(fd);
|
|
2655
|
+
else if (fd == entrySnapshotFd)
|
|
2656
|
+
handle = entrySnapshotHandle;
|
|
2657
|
+
else
|
|
2658
|
+
handle = new IntPtr(-1);
|
|
2659
|
+
|
|
2660
|
+
int handleOffset = checked(
|
|
2661
|
+
sizeof(Int32) +
|
|
2662
|
+
descriptorCount +
|
|
2663
|
+
IntPtr.Size * fd);
|
|
2664
|
+
if (
|
|
2665
|
+
handle == IntPtr.Zero ||
|
|
2666
|
+
handle == new IntPtr(-1) ||
|
|
2667
|
+
handle == new IntPtr(-2))
|
|
2668
|
+
{
|
|
2669
|
+
if (fd == nodeIpcFd || fd == entrySnapshotFd)
|
|
2670
|
+
throw new InvalidDataException(
|
|
2671
|
+
"Node descriptor has no inherited OS handle");
|
|
2672
|
+
if (IntPtr.Size == sizeof(Int64))
|
|
2673
|
+
Marshal.WriteInt64(buffer, handleOffset, -1);
|
|
2674
|
+
else
|
|
2675
|
+
Marshal.WriteInt32(buffer, handleOffset, -1);
|
|
2676
|
+
continue;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
if (!SetHandleInformation(
|
|
2680
|
+
handle,
|
|
2681
|
+
HANDLE_FLAG_INHERIT,
|
|
2682
|
+
HANDLE_FLAG_INHERIT))
|
|
2683
|
+
ThrowLastError("SetHandleInformation(CRT descriptor)");
|
|
2684
|
+
Marshal.WriteByte(
|
|
2685
|
+
buffer,
|
|
2686
|
+
sizeof(Int32) + fd,
|
|
2687
|
+
GetCrtFlags(handle));
|
|
2688
|
+
if (IntPtr.Size == sizeof(Int64))
|
|
2689
|
+
Marshal.WriteInt64(
|
|
2690
|
+
buffer,
|
|
2691
|
+
handleOffset,
|
|
2692
|
+
handle.ToInt64());
|
|
2693
|
+
else
|
|
2694
|
+
Marshal.WriteInt32(
|
|
2695
|
+
buffer,
|
|
2696
|
+
handleOffset,
|
|
2697
|
+
handle.ToInt32());
|
|
2698
|
+
}
|
|
2699
|
+
descriptorBytes = checked((UInt16)bufferSize);
|
|
2700
|
+
return buffer;
|
|
2701
|
+
}
|
|
2702
|
+
catch
|
|
2703
|
+
{
|
|
2704
|
+
Marshal.FreeHGlobal(buffer);
|
|
2705
|
+
throw;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
private static IntPtr BuildInheritedHandleList(
|
|
2710
|
+
IntPtr standardInput,
|
|
2711
|
+
IntPtr standardOutput,
|
|
2712
|
+
IntPtr standardError,
|
|
2713
|
+
int nodeIpcFd,
|
|
2714
|
+
IntPtr entrySnapshotHandle,
|
|
2715
|
+
out IntPtr handleBytes)
|
|
2716
|
+
{
|
|
2717
|
+
List<IntPtr> handles = new List<IntPtr>();
|
|
2718
|
+
handles.Add(standardInput);
|
|
2719
|
+
if (!handles.Contains(standardOutput))
|
|
2720
|
+
handles.Add(standardOutput);
|
|
2721
|
+
if (!handles.Contains(standardError))
|
|
2722
|
+
handles.Add(standardError);
|
|
2723
|
+
|
|
2724
|
+
if (nodeIpcFd >= 0)
|
|
2725
|
+
{
|
|
2726
|
+
IntPtr ipcHandle = _get_osfhandle(nodeIpcFd);
|
|
2727
|
+
if (IsInvalidHandle(ipcHandle))
|
|
2728
|
+
throw new InvalidDataException(
|
|
2729
|
+
"Node IPC descriptor has no inherited OS handle");
|
|
2730
|
+
if (!handles.Contains(ipcHandle))
|
|
2731
|
+
handles.Add(ipcHandle);
|
|
2732
|
+
}
|
|
2733
|
+
if (!IsInvalidHandle(entrySnapshotHandle) &&
|
|
2734
|
+
!handles.Contains(entrySnapshotHandle))
|
|
2735
|
+
{
|
|
2736
|
+
handles.Add(entrySnapshotHandle);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
foreach (IntPtr handle in handles)
|
|
2740
|
+
{
|
|
2741
|
+
if (IsInvalidHandle(handle))
|
|
2742
|
+
throw new InvalidDataException(
|
|
2743
|
+
"Inherited standard handle is invalid");
|
|
2744
|
+
if (!SetHandleInformation(
|
|
2745
|
+
handle,
|
|
2746
|
+
HANDLE_FLAG_INHERIT,
|
|
2747
|
+
HANDLE_FLAG_INHERIT))
|
|
2748
|
+
ThrowLastError("SetHandleInformation(handle list)");
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
int bufferSize = checked(handles.Count * IntPtr.Size);
|
|
2752
|
+
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
|
|
2753
|
+
try
|
|
2754
|
+
{
|
|
2755
|
+
for (int index = 0; index < handles.Count; index++)
|
|
2756
|
+
Marshal.WriteIntPtr(
|
|
2757
|
+
buffer,
|
|
2758
|
+
checked(index * IntPtr.Size),
|
|
2759
|
+
handles[index]);
|
|
2760
|
+
handleBytes = new IntPtr(bufferSize);
|
|
2761
|
+
return buffer;
|
|
2762
|
+
}
|
|
2763
|
+
catch
|
|
2764
|
+
{
|
|
2765
|
+
Marshal.FreeHGlobal(buffer);
|
|
2766
|
+
throw;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
private static IntPtr BuildProcessAttributeList(
|
|
2771
|
+
IntPtr inheritedHandles,
|
|
2772
|
+
IntPtr inheritedHandleBytes,
|
|
2773
|
+
IntPtr appContainerSid,
|
|
2774
|
+
out IntPtr securityCapabilitiesBuffer)
|
|
2775
|
+
{
|
|
2776
|
+
securityCapabilitiesBuffer = IntPtr.Zero;
|
|
2777
|
+
bool useAppContainer = !IsInvalidHandle(appContainerSid);
|
|
2778
|
+
IntPtr attributeBytes = IntPtr.Zero;
|
|
2779
|
+
bool probeSucceeded = InitializeProcThreadAttributeList(
|
|
2780
|
+
IntPtr.Zero,
|
|
2781
|
+
useAppContainer ? 2 : 1,
|
|
2782
|
+
0,
|
|
2783
|
+
ref attributeBytes);
|
|
2784
|
+
int probeError = Marshal.GetLastWin32Error();
|
|
2785
|
+
if (probeSucceeded || probeError != ERROR_INSUFFICIENT_BUFFER)
|
|
2786
|
+
throw new Win32Exception(
|
|
2787
|
+
probeError,
|
|
2788
|
+
"InitializeProcThreadAttributeList(size) failed");
|
|
2789
|
+
|
|
2790
|
+
IntPtr attributeList = Marshal.AllocHGlobal(attributeBytes);
|
|
2791
|
+
bool initialized = false;
|
|
2792
|
+
try
|
|
2793
|
+
{
|
|
2794
|
+
if (!InitializeProcThreadAttributeList(
|
|
2795
|
+
attributeList,
|
|
2796
|
+
useAppContainer ? 2 : 1,
|
|
2797
|
+
0,
|
|
2798
|
+
ref attributeBytes))
|
|
2799
|
+
ThrowLastError("InitializeProcThreadAttributeList");
|
|
2800
|
+
initialized = true;
|
|
2801
|
+
if (!UpdateProcThreadAttribute(
|
|
2802
|
+
attributeList,
|
|
2803
|
+
0,
|
|
2804
|
+
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
|
2805
|
+
inheritedHandles,
|
|
2806
|
+
inheritedHandleBytes,
|
|
2807
|
+
IntPtr.Zero,
|
|
2808
|
+
IntPtr.Zero))
|
|
2809
|
+
ThrowLastError("UpdateProcThreadAttribute(handle list)");
|
|
2810
|
+
|
|
2811
|
+
if (useAppContainer)
|
|
2812
|
+
{
|
|
2813
|
+
SECURITY_CAPABILITIES securityCapabilities =
|
|
2814
|
+
new SECURITY_CAPABILITIES();
|
|
2815
|
+
securityCapabilities.AppContainerSid = appContainerSid;
|
|
2816
|
+
securityCapabilities.Capabilities = IntPtr.Zero;
|
|
2817
|
+
securityCapabilities.CapabilityCount = 0;
|
|
2818
|
+
securityCapabilities.Reserved = 0;
|
|
2819
|
+
int securityCapabilitiesSize = Marshal.SizeOf(
|
|
2820
|
+
typeof(SECURITY_CAPABILITIES));
|
|
2821
|
+
securityCapabilitiesBuffer = Marshal.AllocHGlobal(
|
|
2822
|
+
securityCapabilitiesSize);
|
|
2823
|
+
Marshal.StructureToPtr(
|
|
2824
|
+
securityCapabilities,
|
|
2825
|
+
securityCapabilitiesBuffer,
|
|
2826
|
+
false);
|
|
2827
|
+
if (!UpdateProcThreadAttribute(
|
|
2828
|
+
attributeList,
|
|
2829
|
+
0,
|
|
2830
|
+
PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES,
|
|
2831
|
+
securityCapabilitiesBuffer,
|
|
2832
|
+
new IntPtr(securityCapabilitiesSize),
|
|
2833
|
+
IntPtr.Zero,
|
|
2834
|
+
IntPtr.Zero))
|
|
2835
|
+
{
|
|
2836
|
+
ThrowLastError(
|
|
2837
|
+
"UpdateProcThreadAttribute(security capabilities)");
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
return attributeList;
|
|
2841
|
+
}
|
|
2842
|
+
catch
|
|
2843
|
+
{
|
|
2844
|
+
if (securityCapabilitiesBuffer != IntPtr.Zero)
|
|
2845
|
+
{
|
|
2846
|
+
Marshal.FreeHGlobal(securityCapabilitiesBuffer);
|
|
2847
|
+
securityCapabilitiesBuffer = IntPtr.Zero;
|
|
2848
|
+
}
|
|
2849
|
+
if (initialized)
|
|
2850
|
+
DeleteProcThreadAttributeList(attributeList);
|
|
2851
|
+
Marshal.FreeHGlobal(attributeList);
|
|
2852
|
+
throw;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
public static int RunNodeSnapshotProbe(
|
|
2857
|
+
string application,
|
|
2858
|
+
string appContainerProfileName,
|
|
2859
|
+
string expectedAppContainerSid)
|
|
2860
|
+
{
|
|
2861
|
+
string probeDirectory = Path.Combine(
|
|
2862
|
+
Path.GetTempPath(),
|
|
2863
|
+
"chainless-node-entry-snapshot-probe-" +
|
|
2864
|
+
Guid.NewGuid().ToString("N"));
|
|
2865
|
+
string probeEntry = Path.Combine(probeDirectory, "entry.cjs");
|
|
2866
|
+
if (Directory.Exists(probeDirectory) || File.Exists(probeEntry))
|
|
2867
|
+
throw new IOException(
|
|
2868
|
+
"Node entry snapshot probe path unexpectedly exists");
|
|
2869
|
+
byte[] probeSource = Encoding.UTF8.GetBytes(
|
|
2870
|
+
"if(require.main!==module||process.mainModule!==module||" +
|
|
2871
|
+
"process.execArgv.length!==0||" +
|
|
2872
|
+
"Object.prototype.hasOwnProperty.call(process,'_eval')||" +
|
|
2873
|
+
"__filename!==process.argv[1])" +
|
|
2874
|
+
"throw new Error('entry snapshot transport probe failed');" +
|
|
2875
|
+
(String.IsNullOrWhiteSpace(appContainerProfileName)
|
|
2876
|
+
? String.Empty
|
|
2877
|
+
: "const local=process.env.LOCALAPPDATA;" +
|
|
2878
|
+
"const expectedTemp=local&&local+'\\\\Temp';" +
|
|
2879
|
+
"if(!local||!process.env.TEMP||!process.env.TMP||" +
|
|
2880
|
+
"process.env.TEMP.toLowerCase()!==" +
|
|
2881
|
+
"expectedTemp.toLowerCase()||" +
|
|
2882
|
+
"process.env.TMP.toLowerCase()!==" +
|
|
2883
|
+
"expectedTemp.toLowerCase())" +
|
|
2884
|
+
"throw new Error('appcontainer environment probe failed');") +
|
|
2885
|
+
"process.exitCode=73;");
|
|
2886
|
+
int probeExitCode = Run(
|
|
2887
|
+
application,
|
|
2888
|
+
new[] { probeEntry },
|
|
2889
|
+
0,
|
|
2890
|
+
256L * 1024L * 1024L,
|
|
2891
|
+
1,
|
|
2892
|
+
false,
|
|
2893
|
+
true,
|
|
2894
|
+
-1,
|
|
2895
|
+
false,
|
|
2896
|
+
true,
|
|
2897
|
+
Environment.SystemDirectory,
|
|
2898
|
+
null,
|
|
2899
|
+
null,
|
|
2900
|
+
appContainerProfileName,
|
|
2901
|
+
expectedAppContainerSid,
|
|
2902
|
+
null,
|
|
2903
|
+
probeSource,
|
|
2904
|
+
null,
|
|
2905
|
+
null);
|
|
2906
|
+
if (probeExitCode != 73)
|
|
2907
|
+
throw new InvalidDataException(
|
|
2908
|
+
"Node entry snapshot probe did not execute its verified source");
|
|
2909
|
+
return 0;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
public static int Run(
|
|
2913
|
+
string application,
|
|
2914
|
+
string[] arguments,
|
|
2915
|
+
int cpuSeconds,
|
|
2916
|
+
long processMemoryBytes,
|
|
2917
|
+
int activeProcessLimit,
|
|
2918
|
+
bool allowReparsePaths,
|
|
2919
|
+
bool disableAdministratorSids,
|
|
2920
|
+
int nodeIpcFd,
|
|
2921
|
+
bool detached,
|
|
2922
|
+
bool windowsHide,
|
|
2923
|
+
string workingDirectory,
|
|
2924
|
+
Dictionary<string, string> targetEnvironment,
|
|
2925
|
+
string identityPath,
|
|
2926
|
+
string appContainerProfileName,
|
|
2927
|
+
string expectedAppContainerSid,
|
|
2928
|
+
LaunchPathLockSpec[] launchPathLockSpecs,
|
|
2929
|
+
byte[] entrySnapshotOverride,
|
|
2930
|
+
string snapshotTestGateToken,
|
|
2931
|
+
string snapshotTestGateReleasePath)
|
|
2932
|
+
{
|
|
2933
|
+
targetEnvironment =
|
|
2934
|
+
targetEnvironment ?? CaptureCurrentEnvironment();
|
|
2935
|
+
workingDirectory = NormalizeLocalDosPath(
|
|
2936
|
+
String.IsNullOrWhiteSpace(workingDirectory)
|
|
2937
|
+
? Environment.SystemDirectory
|
|
2938
|
+
: workingDirectory,
|
|
2939
|
+
null,
|
|
2940
|
+
"Target working directory");
|
|
2941
|
+
FileAttributes workingDirectoryAttributes =
|
|
2942
|
+
ValidateExistingLocalPath(
|
|
2943
|
+
workingDirectory,
|
|
2944
|
+
"Target working directory",
|
|
2945
|
+
allowReparsePaths);
|
|
2946
|
+
if (
|
|
2947
|
+
(workingDirectoryAttributes & FileAttributes.Directory) == 0)
|
|
2948
|
+
{
|
|
2949
|
+
throw new DirectoryNotFoundException(
|
|
2950
|
+
"Target working directory was not found: " +
|
|
2951
|
+
workingDirectory);
|
|
2952
|
+
}
|
|
2953
|
+
application = ResolveApplication(
|
|
2954
|
+
application,
|
|
2955
|
+
workingDirectory,
|
|
2956
|
+
targetEnvironment,
|
|
2957
|
+
allowReparsePaths);
|
|
2958
|
+
string extension = Path.GetExtension(application);
|
|
2959
|
+
if (extension.Equals(".cmd", StringComparison.OrdinalIgnoreCase) ||
|
|
2960
|
+
extension.Equals(".bat", StringComparison.OrdinalIgnoreCase))
|
|
2961
|
+
{
|
|
2962
|
+
string commandText = BuildCommandLine(application, arguments);
|
|
2963
|
+
application = ResolveApplication(
|
|
2964
|
+
GetEnvironmentValue(
|
|
2965
|
+
targetEnvironment,
|
|
2966
|
+
"ComSpec",
|
|
2967
|
+
Path.Combine(
|
|
2968
|
+
Environment.SystemDirectory,
|
|
2969
|
+
"cmd.exe")),
|
|
2970
|
+
workingDirectory,
|
|
2971
|
+
targetEnvironment,
|
|
2972
|
+
allowReparsePaths);
|
|
2973
|
+
arguments = new[] { "/d", "/s", "/c", commandText };
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
IntPtr sourceToken = IntPtr.Zero;
|
|
2977
|
+
IntPtr restrictedToken = IntPtr.Zero;
|
|
2978
|
+
IntPtr appContainerSid = IntPtr.Zero;
|
|
2979
|
+
IntPtr job = IntPtr.Zero;
|
|
2980
|
+
IntPtr limitBuffer = IntPtr.Zero;
|
|
2981
|
+
IntPtr descriptorBuffer = IntPtr.Zero;
|
|
2982
|
+
IntPtr inheritedHandleBuffer = IntPtr.Zero;
|
|
2983
|
+
IntPtr environmentBuffer = IntPtr.Zero;
|
|
2984
|
+
IntPtr attributeList = IntPtr.Zero;
|
|
2985
|
+
IntPtr securityCapabilitiesBuffer = IntPtr.Zero;
|
|
2986
|
+
IntPtr entrySnapshotReadHandle = IntPtr.Zero;
|
|
2987
|
+
IntPtr entrySnapshotWriteHandle = IntPtr.Zero;
|
|
2988
|
+
int entrySnapshotFd = -1;
|
|
2989
|
+
byte[] entrySnapshot = null;
|
|
2990
|
+
List<IntPtr> ownedStandardHandles = new List<IntPtr>();
|
|
2991
|
+
List<LaunchPathLock> launchPathLocks = new List<LaunchPathLock>();
|
|
2992
|
+
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
|
|
2993
|
+
bool useAppContainer =
|
|
2994
|
+
!String.IsNullOrWhiteSpace(appContainerProfileName);
|
|
2995
|
+
string attestedAppContainerSid = null;
|
|
2996
|
+
bool targetExited = false;
|
|
2997
|
+
int targetExitCode = 125;
|
|
2998
|
+
Exception launchError = null;
|
|
2999
|
+
|
|
3000
|
+
try
|
|
3001
|
+
{
|
|
3002
|
+
launchPathLocks = AcquireLaunchPathLocks(
|
|
3003
|
+
launchPathLockSpecs,
|
|
3004
|
+
application,
|
|
3005
|
+
arguments);
|
|
3006
|
+
entrySnapshot = TakeEntrySnapshot(launchPathLocks);
|
|
3007
|
+
if (entrySnapshotOverride != null)
|
|
3008
|
+
{
|
|
3009
|
+
if (entrySnapshot != null || launchPathLocks.Count != 0)
|
|
3010
|
+
throw new InvalidDataException(
|
|
3011
|
+
"Entry snapshot probe cannot use launch path locks");
|
|
3012
|
+
entrySnapshot = entrySnapshotOverride;
|
|
3013
|
+
}
|
|
3014
|
+
AwaitSnapshotTestGate(
|
|
3015
|
+
entrySnapshot,
|
|
3016
|
+
launchPathLockSpecs,
|
|
3017
|
+
snapshotTestGateToken,
|
|
3018
|
+
snapshotTestGateReleasePath);
|
|
3019
|
+
if (entrySnapshot != null)
|
|
3020
|
+
{
|
|
3021
|
+
RemoveEnvironmentValue(
|
|
3022
|
+
targetEnvironment,
|
|
3023
|
+
"NODE_OPTIONS");
|
|
3024
|
+
RemoveEnvironmentValue(
|
|
3025
|
+
targetEnvironment,
|
|
3026
|
+
"NODE_CHANNEL_FD");
|
|
3027
|
+
RemoveEnvironmentValue(
|
|
3028
|
+
targetEnvironment,
|
|
3029
|
+
"OPENSSL_CONF");
|
|
3030
|
+
RemoveEnvironmentValue(
|
|
3031
|
+
targetEnvironment,
|
|
3032
|
+
"OPENSSL_CONF_INCLUDE");
|
|
3033
|
+
RemoveEnvironmentValue(
|
|
3034
|
+
targetEnvironment,
|
|
3035
|
+
"OPENSSL_ENGINES");
|
|
3036
|
+
RemoveEnvironmentValue(
|
|
3037
|
+
targetEnvironment,
|
|
3038
|
+
"OPENSSL_MODULES");
|
|
3039
|
+
AssertSnapshotRuntimeBitness(application);
|
|
3040
|
+
CreateEntrySnapshotPipe(
|
|
3041
|
+
nodeIpcFd,
|
|
3042
|
+
out entrySnapshotFd,
|
|
3043
|
+
out entrySnapshotReadHandle,
|
|
3044
|
+
out entrySnapshotWriteHandle);
|
|
3045
|
+
arguments = BuildSnapshotNodeArguments(
|
|
3046
|
+
arguments,
|
|
3047
|
+
entrySnapshotFd,
|
|
3048
|
+
entrySnapshot);
|
|
3049
|
+
}
|
|
3050
|
+
if (useAppContainer)
|
|
3051
|
+
{
|
|
3052
|
+
if (String.IsNullOrWhiteSpace(expectedAppContainerSid))
|
|
3053
|
+
throw new ArgumentException(
|
|
3054
|
+
"AppContainer launch omitted its expected profile SID");
|
|
3055
|
+
appContainerSid = EnsureAppContainerProfile(
|
|
3056
|
+
appContainerProfileName);
|
|
3057
|
+
string preparedSid = SidToString(appContainerSid);
|
|
3058
|
+
if (!String.Equals(
|
|
3059
|
+
preparedSid,
|
|
3060
|
+
expectedAppContainerSid,
|
|
3061
|
+
StringComparison.OrdinalIgnoreCase))
|
|
3062
|
+
{
|
|
3063
|
+
throw new InvalidDataException(
|
|
3064
|
+
"Prepared AppContainer SID does not match launch payload");
|
|
3065
|
+
}
|
|
3066
|
+
ConfigureAppContainerEnvironment(targetEnvironment);
|
|
3067
|
+
}
|
|
3068
|
+
environmentBuffer =
|
|
3069
|
+
BuildEnvironmentBlock(targetEnvironment);
|
|
3070
|
+
|
|
3071
|
+
UInt32 tokenAccess =
|
|
3072
|
+
TOKEN_ASSIGN_PRIMARY |
|
|
3073
|
+
TOKEN_DUPLICATE |
|
|
3074
|
+
TOKEN_QUERY |
|
|
3075
|
+
TOKEN_ADJUST_DEFAULT |
|
|
3076
|
+
TOKEN_ADJUST_SESSIONID;
|
|
3077
|
+
if (!OpenProcessToken(
|
|
3078
|
+
GetCurrentProcess(),
|
|
3079
|
+
tokenAccess,
|
|
3080
|
+
out sourceToken))
|
|
3081
|
+
{
|
|
3082
|
+
ThrowLastError("OpenProcessToken");
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
UInt32 restrictedTokenFlags = DISABLE_MAX_PRIVILEGE;
|
|
3086
|
+
// The ordinary compatibility profile removes privileges while
|
|
3087
|
+
// retaining the caller's enabled SIDs so nested tools such as
|
|
3088
|
+
// Git for Windows can still create their helper processes and
|
|
3089
|
+
// anonymous pipes. Strong profiles additionally deny enabled
|
|
3090
|
+
// administrator SIDs with LUA_TOKEN. Re-applying LUA_TOKEN to
|
|
3091
|
+
// an already restricted parent can fail with
|
|
3092
|
+
// ERROR_INVALID_PARAMETER.
|
|
3093
|
+
if (disableAdministratorSids && !IsTokenRestricted(sourceToken))
|
|
3094
|
+
restrictedTokenFlags |= LUA_TOKEN;
|
|
3095
|
+
if (!CreateRestrictedToken(
|
|
3096
|
+
sourceToken,
|
|
3097
|
+
restrictedTokenFlags,
|
|
3098
|
+
0,
|
|
3099
|
+
IntPtr.Zero,
|
|
3100
|
+
0,
|
|
3101
|
+
IntPtr.Zero,
|
|
3102
|
+
0,
|
|
3103
|
+
IntPtr.Zero,
|
|
3104
|
+
out restrictedToken))
|
|
3105
|
+
{
|
|
3106
|
+
ThrowLastError("CreateRestrictedToken");
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
job = CreateJobObject(IntPtr.Zero, null);
|
|
3110
|
+
if (job == IntPtr.Zero) ThrowLastError("CreateJobObject");
|
|
3111
|
+
|
|
3112
|
+
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits =
|
|
3113
|
+
new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
|
3114
|
+
limits.BasicLimitInformation.LimitFlags =
|
|
3115
|
+
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
3116
|
+
if (cpuSeconds > 0)
|
|
3117
|
+
{
|
|
3118
|
+
limits.BasicLimitInformation.LimitFlags |=
|
|
3119
|
+
JOB_OBJECT_LIMIT_PROCESS_TIME;
|
|
3120
|
+
limits.BasicLimitInformation.PerProcessUserTimeLimit =
|
|
3121
|
+
checked((long)cpuSeconds * 10000000L);
|
|
3122
|
+
}
|
|
3123
|
+
if (activeProcessLimit > 0)
|
|
3124
|
+
{
|
|
3125
|
+
limits.BasicLimitInformation.LimitFlags |=
|
|
3126
|
+
JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
|
|
3127
|
+
limits.BasicLimitInformation.ActiveProcessLimit =
|
|
3128
|
+
checked((UInt32)activeProcessLimit);
|
|
3129
|
+
}
|
|
3130
|
+
if (processMemoryBytes > 0)
|
|
3131
|
+
{
|
|
3132
|
+
limits.BasicLimitInformation.LimitFlags |=
|
|
3133
|
+
JOB_OBJECT_LIMIT_PROCESS_MEMORY;
|
|
3134
|
+
limits.ProcessMemoryLimit =
|
|
3135
|
+
new UIntPtr(checked((UInt64)processMemoryBytes));
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
int limitSize = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
3139
|
+
limitBuffer = Marshal.AllocHGlobal(limitSize);
|
|
3140
|
+
Marshal.StructureToPtr(limits, limitBuffer, false);
|
|
3141
|
+
if (!SetInformationJobObject(
|
|
3142
|
+
job,
|
|
3143
|
+
JobObjectExtendedLimitInformation,
|
|
3144
|
+
limitBuffer,
|
|
3145
|
+
checked((UInt32)limitSize)))
|
|
3146
|
+
ThrowLastError("SetInformationJobObject");
|
|
3147
|
+
|
|
3148
|
+
// Node/libuv communicates Windows stdio entries above fd 2
|
|
3149
|
+
// (including its fd 3 IPC channel) through the CRT descriptor
|
|
3150
|
+
// table in STARTUPINFO.cbReserved2/lpReserved2. Standard
|
|
3151
|
+
// descriptors remain in STARTUPINFO's dedicated handle fields.
|
|
3152
|
+
STARTUPINFOEX startup = new STARTUPINFOEX();
|
|
3153
|
+
startup.StartupInfo.cb = checked(
|
|
3154
|
+
(UInt32)Marshal.SizeOf(typeof(STARTUPINFOEX)));
|
|
3155
|
+
startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
|
3156
|
+
if (windowsHide)
|
|
3157
|
+
{
|
|
3158
|
+
startup.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
|
|
3159
|
+
startup.StartupInfo.wShowWindow = SW_HIDE;
|
|
3160
|
+
}
|
|
3161
|
+
startup.StartupInfo.hStdInput = PrepareStandardHandle(
|
|
3162
|
+
-10,
|
|
3163
|
+
GENERIC_READ,
|
|
3164
|
+
ownedStandardHandles);
|
|
3165
|
+
startup.StartupInfo.hStdOutput = PrepareStandardHandle(
|
|
3166
|
+
-11,
|
|
3167
|
+
GENERIC_WRITE,
|
|
3168
|
+
ownedStandardHandles);
|
|
3169
|
+
startup.StartupInfo.hStdError = PrepareStandardHandle(
|
|
3170
|
+
-12,
|
|
3171
|
+
GENERIC_WRITE,
|
|
3172
|
+
ownedStandardHandles);
|
|
3173
|
+
UInt16 descriptorBytes;
|
|
3174
|
+
descriptorBuffer = BuildNodeDescriptorTable(
|
|
3175
|
+
nodeIpcFd,
|
|
3176
|
+
entrySnapshotFd,
|
|
3177
|
+
entrySnapshotReadHandle,
|
|
3178
|
+
startup.StartupInfo.hStdInput,
|
|
3179
|
+
startup.StartupInfo.hStdOutput,
|
|
3180
|
+
startup.StartupInfo.hStdError,
|
|
3181
|
+
out descriptorBytes);
|
|
3182
|
+
startup.StartupInfo.cbReserved2 = descriptorBytes;
|
|
3183
|
+
startup.StartupInfo.lpReserved2 = descriptorBuffer;
|
|
3184
|
+
|
|
3185
|
+
IntPtr inheritedHandleBytes;
|
|
3186
|
+
inheritedHandleBuffer = BuildInheritedHandleList(
|
|
3187
|
+
startup.StartupInfo.hStdInput,
|
|
3188
|
+
startup.StartupInfo.hStdOutput,
|
|
3189
|
+
startup.StartupInfo.hStdError,
|
|
3190
|
+
nodeIpcFd,
|
|
3191
|
+
entrySnapshotReadHandle,
|
|
3192
|
+
out inheritedHandleBytes);
|
|
3193
|
+
attributeList = BuildProcessAttributeList(
|
|
3194
|
+
inheritedHandleBuffer,
|
|
3195
|
+
inheritedHandleBytes,
|
|
3196
|
+
appContainerSid,
|
|
3197
|
+
out securityCapabilitiesBuffer);
|
|
3198
|
+
startup.lpAttributeList = attributeList;
|
|
3199
|
+
|
|
3200
|
+
StringBuilder commandLine =
|
|
3201
|
+
new StringBuilder(
|
|
3202
|
+
BuildCreateProcessCommandLine(application, arguments));
|
|
3203
|
+
UInt32 creationFlags =
|
|
3204
|
+
CREATE_SUSPENDED |
|
|
3205
|
+
CREATE_UNICODE_ENVIRONMENT |
|
|
3206
|
+
EXTENDED_STARTUPINFO_PRESENT;
|
|
3207
|
+
if (detached)
|
|
3208
|
+
creationFlags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
|
|
3209
|
+
AssertLaunchPathLocksIntact(launchPathLocks);
|
|
3210
|
+
// Keep privilege reduction independent from the AppContainer
|
|
3211
|
+
// boundary: SECURITY_CAPABILITIES derives the LowBox token
|
|
3212
|
+
// from this already restricted primary token.
|
|
3213
|
+
bool processCreated = CreateProcessAsUser(
|
|
3214
|
+
restrictedToken,
|
|
3215
|
+
application,
|
|
3216
|
+
commandLine,
|
|
3217
|
+
IntPtr.Zero,
|
|
3218
|
+
IntPtr.Zero,
|
|
3219
|
+
true,
|
|
3220
|
+
creationFlags,
|
|
3221
|
+
environmentBuffer,
|
|
3222
|
+
workingDirectory,
|
|
3223
|
+
ref startup,
|
|
3224
|
+
out processInfo);
|
|
3225
|
+
if (!processCreated)
|
|
3226
|
+
{
|
|
3227
|
+
ThrowLastError(
|
|
3228
|
+
useAppContainer
|
|
3229
|
+
? "CreateProcessAsUser(AppContainer)"
|
|
3230
|
+
: "CreateProcessAsUser");
|
|
3231
|
+
}
|
|
3232
|
+
AssertLaunchPathLocksIntact(launchPathLocks);
|
|
3233
|
+
|
|
3234
|
+
if (useAppContainer)
|
|
3235
|
+
{
|
|
3236
|
+
try
|
|
3237
|
+
{
|
|
3238
|
+
attestedAppContainerSid = AttestAppContainerTarget(
|
|
3239
|
+
processInfo.hProcess,
|
|
3240
|
+
appContainerSid);
|
|
3241
|
+
}
|
|
3242
|
+
catch
|
|
3243
|
+
{
|
|
3244
|
+
TerminateProcess(processInfo.hProcess, 125);
|
|
3245
|
+
throw;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
AssertLaunchPathLocksIntact(launchPathLocks);
|
|
3249
|
+
|
|
3250
|
+
if (!IsInvalidHandle(entrySnapshotReadHandle))
|
|
3251
|
+
{
|
|
3252
|
+
CloseHandle(entrySnapshotReadHandle);
|
|
3253
|
+
entrySnapshotReadHandle = IntPtr.Zero;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
// The target now owns its inherited copy of the duplex IPC
|
|
3257
|
+
// pipe. Close the helper's CRT descriptor so process.disconnect
|
|
3258
|
+
// and channel EOF retain native Node child semantics even while
|
|
3259
|
+
// the helper continues waiting on the target process handle.
|
|
3260
|
+
if (nodeIpcFd >= 0)
|
|
3261
|
+
{
|
|
3262
|
+
int ipcDescriptor = nodeIpcFd;
|
|
3263
|
+
nodeIpcFd = -1;
|
|
3264
|
+
if (_close(ipcDescriptor) != 0)
|
|
3265
|
+
{
|
|
3266
|
+
TerminateProcess(processInfo.hProcess, 125);
|
|
3267
|
+
throw new IOException(
|
|
3268
|
+
"Closing the Windows sandbox IPC relay descriptor failed");
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
if (!AssignProcessToJobObject(job, processInfo.hProcess))
|
|
3273
|
+
{
|
|
3274
|
+
TerminateProcess(processInfo.hProcess, 125);
|
|
3275
|
+
ThrowLastError("AssignProcessToJobObject");
|
|
3276
|
+
}
|
|
3277
|
+
ReattestLaunchPaths(launchPathLocks);
|
|
3278
|
+
// The suspended process has already created its image section.
|
|
3279
|
+
// Release all remaining runtime stream guards before resume so
|
|
3280
|
+
// target code can never deadlock waiting for a break owned by
|
|
3281
|
+
// the helper. This is not a pathname-atomic runtime launch.
|
|
3282
|
+
ReleaseLaunchPathLocks(launchPathLocks);
|
|
3283
|
+
if (ResumeThread(processInfo.hThread) == UInt32.MaxValue)
|
|
3284
|
+
{
|
|
3285
|
+
TerminateProcess(processInfo.hProcess, 125);
|
|
3286
|
+
ThrowLastError("ResumeThread");
|
|
3287
|
+
}
|
|
3288
|
+
if (entrySnapshot != null)
|
|
3289
|
+
{
|
|
3290
|
+
try
|
|
3291
|
+
{
|
|
3292
|
+
WriteEntrySnapshot(
|
|
3293
|
+
entrySnapshotWriteHandle,
|
|
3294
|
+
entrySnapshot);
|
|
3295
|
+
}
|
|
3296
|
+
finally
|
|
3297
|
+
{
|
|
3298
|
+
if (!IsInvalidHandle(entrySnapshotWriteHandle))
|
|
3299
|
+
{
|
|
3300
|
+
CloseHandle(entrySnapshotWriteHandle);
|
|
3301
|
+
entrySnapshotWriteHandle = IntPtr.Zero;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
if (!String.IsNullOrWhiteSpace(identityPath))
|
|
3307
|
+
{
|
|
3308
|
+
string identity = JsonCodec.SerializeObject(
|
|
3309
|
+
"targetPid",
|
|
3310
|
+
processInfo.dwProcessId,
|
|
3311
|
+
"helperPid",
|
|
3312
|
+
System.Diagnostics.Process.GetCurrentProcess().Id,
|
|
3313
|
+
"appContainer",
|
|
3314
|
+
useAppContainer,
|
|
3315
|
+
"appContainerSid",
|
|
3316
|
+
attestedAppContainerSid,
|
|
3317
|
+
"capabilityCount",
|
|
3318
|
+
useAppContainer ? 0 : -1);
|
|
3319
|
+
using (FileStream stream = new FileStream(
|
|
3320
|
+
identityPath,
|
|
3321
|
+
FileMode.CreateNew,
|
|
3322
|
+
FileAccess.Write,
|
|
3323
|
+
FileShare.Read))
|
|
3324
|
+
using (StreamWriter writer = new StreamWriter(
|
|
3325
|
+
stream,
|
|
3326
|
+
new UTF8Encoding(false)))
|
|
3327
|
+
{
|
|
3328
|
+
writer.Write(identity);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
UInt32 targetWait = WaitForSingleObject(
|
|
3333
|
+
processInfo.hProcess,
|
|
3334
|
+
INFINITE);
|
|
3335
|
+
if (targetWait != WAIT_OBJECT_0)
|
|
3336
|
+
ThrowLastError("WaitForSingleObject(target)");
|
|
3337
|
+
targetExited = true;
|
|
3338
|
+
UInt32 exitCode;
|
|
3339
|
+
if (!GetExitCodeProcess(processInfo.hProcess, out exitCode))
|
|
3340
|
+
ThrowLastError("GetExitCodeProcess");
|
|
3341
|
+
targetExitCode = unchecked((int)exitCode);
|
|
3342
|
+
}
|
|
3343
|
+
catch (Exception error)
|
|
3344
|
+
{
|
|
3345
|
+
launchError = error;
|
|
3346
|
+
}
|
|
3347
|
+
finally
|
|
3348
|
+
{
|
|
3349
|
+
Exception cleanupError = null;
|
|
3350
|
+
if (processInfo.hProcess != IntPtr.Zero && !targetExited)
|
|
3351
|
+
TerminateProcess(processInfo.hProcess, 125);
|
|
3352
|
+
if (job != IntPtr.Zero)
|
|
3353
|
+
{
|
|
3354
|
+
try
|
|
3355
|
+
{
|
|
3356
|
+
TerminateAndAwaitEmptyJob(job);
|
|
3357
|
+
}
|
|
3358
|
+
catch (Exception error)
|
|
3359
|
+
{
|
|
3360
|
+
cleanupError = error;
|
|
3361
|
+
}
|
|
3362
|
+
CloseHandle(job);
|
|
3363
|
+
job = IntPtr.Zero;
|
|
3364
|
+
}
|
|
3365
|
+
if (processInfo.hProcess != IntPtr.Zero && !targetExited)
|
|
3366
|
+
{
|
|
3367
|
+
UInt32 cleanupWait = WaitForSingleObject(
|
|
3368
|
+
processInfo.hProcess,
|
|
3369
|
+
10000);
|
|
3370
|
+
if (cleanupWait != WAIT_OBJECT_0)
|
|
3371
|
+
{
|
|
3372
|
+
Exception waitError = new IOException(
|
|
3373
|
+
"Timed out terminating the Windows sandbox target");
|
|
3374
|
+
cleanupError =
|
|
3375
|
+
cleanupError == null
|
|
3376
|
+
? waitError
|
|
3377
|
+
: new AggregateException(
|
|
3378
|
+
"Windows sandbox process-tree cleanup failed",
|
|
3379
|
+
cleanupError,
|
|
3380
|
+
waitError);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
if (processInfo.hThread != IntPtr.Zero) CloseHandle(processInfo.hThread);
|
|
3384
|
+
if (processInfo.hProcess != IntPtr.Zero) CloseHandle(processInfo.hProcess);
|
|
3385
|
+
if (!IsInvalidHandle(entrySnapshotReadHandle))
|
|
3386
|
+
CloseHandle(entrySnapshotReadHandle);
|
|
3387
|
+
if (!IsInvalidHandle(entrySnapshotWriteHandle))
|
|
3388
|
+
CloseHandle(entrySnapshotWriteHandle);
|
|
3389
|
+
foreach (LaunchPathLock launchPathLock in launchPathLocks)
|
|
3390
|
+
launchPathLock.Dispose();
|
|
3391
|
+
launchPathLocks.Clear();
|
|
3392
|
+
if (attributeList != IntPtr.Zero)
|
|
3393
|
+
{
|
|
3394
|
+
DeleteProcThreadAttributeList(attributeList);
|
|
3395
|
+
Marshal.FreeHGlobal(attributeList);
|
|
3396
|
+
}
|
|
3397
|
+
if (securityCapabilitiesBuffer != IntPtr.Zero)
|
|
3398
|
+
Marshal.FreeHGlobal(securityCapabilitiesBuffer);
|
|
3399
|
+
if (inheritedHandleBuffer != IntPtr.Zero)
|
|
3400
|
+
Marshal.FreeHGlobal(inheritedHandleBuffer);
|
|
3401
|
+
if (environmentBuffer != IntPtr.Zero)
|
|
3402
|
+
Marshal.FreeHGlobal(environmentBuffer);
|
|
3403
|
+
if (descriptorBuffer != IntPtr.Zero)
|
|
3404
|
+
Marshal.FreeHGlobal(descriptorBuffer);
|
|
3405
|
+
foreach (IntPtr handle in ownedStandardHandles)
|
|
3406
|
+
CloseHandle(handle);
|
|
3407
|
+
if (limitBuffer != IntPtr.Zero) Marshal.FreeHGlobal(limitBuffer);
|
|
3408
|
+
if (!IsInvalidHandle(appContainerSid)) FreeSid(appContainerSid);
|
|
3409
|
+
if (restrictedToken != IntPtr.Zero) CloseHandle(restrictedToken);
|
|
3410
|
+
if (sourceToken != IntPtr.Zero) CloseHandle(sourceToken);
|
|
3411
|
+
if (nodeIpcFd >= 0)
|
|
3412
|
+
_close(nodeIpcFd);
|
|
3413
|
+
if (useAppContainer)
|
|
3414
|
+
{
|
|
3415
|
+
try
|
|
3416
|
+
{
|
|
3417
|
+
DeletePreparedAppContainerProfile(
|
|
3418
|
+
appContainerProfileName,
|
|
3419
|
+
expectedAppContainerSid);
|
|
3420
|
+
}
|
|
3421
|
+
catch (Exception error)
|
|
3422
|
+
{
|
|
3423
|
+
cleanupError =
|
|
3424
|
+
cleanupError == null
|
|
3425
|
+
? error
|
|
3426
|
+
: new AggregateException(
|
|
3427
|
+
"AppContainer target and profile cleanup failed",
|
|
3428
|
+
cleanupError,
|
|
3429
|
+
error);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
if (cleanupError != null)
|
|
3433
|
+
{
|
|
3434
|
+
launchError =
|
|
3435
|
+
launchError == null
|
|
3436
|
+
? cleanupError
|
|
3437
|
+
: new AggregateException(
|
|
3438
|
+
"AppContainer launch and cleanup failed",
|
|
3439
|
+
launchError,
|
|
3440
|
+
cleanupError);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
if (launchError != null)
|
|
3444
|
+
throw launchError;
|
|
3445
|
+
return targetExitCode;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
public static class Program
|
|
3450
|
+
{
|
|
3451
|
+
private const string InvocationHeader =
|
|
3452
|
+
"CC_WINDOWS_SANDBOX_INVOCATION_V1";
|
|
3453
|
+
|
|
3454
|
+
[DataContract]
|
|
3455
|
+
public sealed class LaunchSpec
|
|
3456
|
+
{
|
|
3457
|
+
[DataMember]
|
|
3458
|
+
public int cpuSeconds { get; set; }
|
|
3459
|
+
[DataMember]
|
|
3460
|
+
public long processMemoryBytes { get; set; }
|
|
3461
|
+
[DataMember]
|
|
3462
|
+
public int activeProcessLimit { get; set; }
|
|
3463
|
+
[DataMember]
|
|
3464
|
+
public bool allowReparsePaths { get; set; }
|
|
3465
|
+
[DataMember]
|
|
3466
|
+
public bool disableAdministratorSids { get; set; }
|
|
3467
|
+
[DataMember]
|
|
3468
|
+
public string command { get; set; }
|
|
3469
|
+
[DataMember]
|
|
3470
|
+
public string[] args { get; set; }
|
|
3471
|
+
[DataMember]
|
|
3472
|
+
public int nodeIpcFd { get; set; }
|
|
3473
|
+
[DataMember]
|
|
3474
|
+
public bool detached { get; set; }
|
|
3475
|
+
[DataMember]
|
|
3476
|
+
public bool windowsHide { get; set; }
|
|
3477
|
+
[DataMember]
|
|
3478
|
+
public string workingDirectory { get; set; }
|
|
3479
|
+
[DataMember]
|
|
3480
|
+
public Dictionary<string, string> environment { get; set; }
|
|
3481
|
+
[DataMember]
|
|
3482
|
+
public string identityPath { get; set; }
|
|
3483
|
+
[DataMember]
|
|
3484
|
+
public string appContainerProfileName { get; set; }
|
|
3485
|
+
[DataMember]
|
|
3486
|
+
public string appContainerSid { get; set; }
|
|
3487
|
+
[DataMember]
|
|
3488
|
+
public Native.LaunchPathLockSpec[] launchPathLocks { get; set; }
|
|
3489
|
+
[DataMember]
|
|
3490
|
+
public string snapshotTestGateToken { get; set; }
|
|
3491
|
+
[DataMember]
|
|
3492
|
+
public string snapshotTestGateReleasePath { get; set; }
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
private static string Sha256Hex(byte[] value)
|
|
3496
|
+
{
|
|
3497
|
+
using (SHA256 algorithm = SHA256.Create())
|
|
3498
|
+
{
|
|
3499
|
+
byte[] digest = algorithm.ComputeHash(value);
|
|
3500
|
+
StringBuilder result = new StringBuilder(
|
|
3501
|
+
checked(digest.Length * 2));
|
|
3502
|
+
foreach (byte item in digest)
|
|
3503
|
+
result.Append(item.ToString("x2", CultureInfo.InvariantCulture));
|
|
3504
|
+
return result.ToString();
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
private static string[] DecodeInvocationFile(string[] args)
|
|
3509
|
+
{
|
|
3510
|
+
if (
|
|
3511
|
+
args == null ||
|
|
3512
|
+
args.Length != 5 ||
|
|
3513
|
+
!String.Equals(
|
|
3514
|
+
args[0],
|
|
3515
|
+
"--adapter-path",
|
|
3516
|
+
StringComparison.Ordinal) ||
|
|
3517
|
+
!String.Equals(
|
|
3518
|
+
args[2],
|
|
3519
|
+
"--invocation-file",
|
|
3520
|
+
StringComparison.Ordinal))
|
|
3521
|
+
{
|
|
3522
|
+
return args;
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
string expectedAdapterPath = Path.GetFullPath(args[1]);
|
|
3526
|
+
string currentAdapterPath = Path.GetFullPath(
|
|
3527
|
+
System.Diagnostics.Process
|
|
3528
|
+
.GetCurrentProcess()
|
|
3529
|
+
.MainModule
|
|
3530
|
+
.FileName);
|
|
3531
|
+
if (!String.Equals(
|
|
3532
|
+
expectedAdapterPath,
|
|
3533
|
+
currentAdapterPath,
|
|
3534
|
+
StringComparison.OrdinalIgnoreCase))
|
|
3535
|
+
{
|
|
3536
|
+
throw new InvalidDataException(
|
|
3537
|
+
"Windows sandbox adapter path does not match the running helper");
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
string payloadPath = Path.GetFullPath(args[3]);
|
|
3541
|
+
string expectedDigest = args[4] ?? String.Empty;
|
|
3542
|
+
if (
|
|
3543
|
+
expectedDigest.Length != 64 ||
|
|
3544
|
+
!System.Text.RegularExpressions.Regex.IsMatch(
|
|
3545
|
+
expectedDigest,
|
|
3546
|
+
"\\A[a-f0-9]{64}\\z",
|
|
3547
|
+
System.Text.RegularExpressions.RegexOptions.CultureInvariant))
|
|
3548
|
+
{
|
|
3549
|
+
throw new InvalidDataException(
|
|
3550
|
+
"Windows sandbox adapter invocation digest is invalid");
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
byte[] payload = File.ReadAllBytes(payloadPath);
|
|
3554
|
+
if (payload.Length > 8 * 1024 * 1024)
|
|
3555
|
+
throw new InvalidDataException(
|
|
3556
|
+
"Windows sandbox adapter invocation exceeds its limit");
|
|
3557
|
+
if (!String.Equals(
|
|
3558
|
+
Sha256Hex(payload),
|
|
3559
|
+
expectedDigest,
|
|
3560
|
+
StringComparison.Ordinal))
|
|
3561
|
+
{
|
|
3562
|
+
throw new InvalidDataException(
|
|
3563
|
+
"Windows sandbox adapter invocation digest mismatch");
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
File.Delete(payloadPath);
|
|
3567
|
+
if (File.Exists(payloadPath))
|
|
3568
|
+
throw new IOException(
|
|
3569
|
+
"Windows sandbox adapter invocation cleanup failed");
|
|
3570
|
+
|
|
3571
|
+
string text = new UTF8Encoding(false, true).GetString(payload);
|
|
3572
|
+
string[] lines = text.Split(
|
|
3573
|
+
new[] { '\n' },
|
|
3574
|
+
StringSplitOptions.None);
|
|
3575
|
+
if (
|
|
3576
|
+
lines.Length < 2 ||
|
|
3577
|
+
!String.Equals(
|
|
3578
|
+
lines[0],
|
|
3579
|
+
InvocationHeader,
|
|
3580
|
+
StringComparison.Ordinal) ||
|
|
3581
|
+
lines[lines.Length - 1].Length != 0)
|
|
3582
|
+
{
|
|
3583
|
+
throw new InvalidDataException(
|
|
3584
|
+
"Windows sandbox adapter invocation is invalid");
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
string[] decoded = new string[lines.Length - 2];
|
|
3588
|
+
UTF8Encoding strictUtf8 = new UTF8Encoding(false, true);
|
|
3589
|
+
for (int index = 0; index < decoded.Length; index++)
|
|
3590
|
+
{
|
|
3591
|
+
decoded[index] = strictUtf8.GetString(
|
|
3592
|
+
Convert.FromBase64String(lines[index + 1]));
|
|
3593
|
+
}
|
|
3594
|
+
return decoded;
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
public static int Main(string[] args)
|
|
3598
|
+
{
|
|
3599
|
+
LaunchSpec spec = null;
|
|
3600
|
+
try
|
|
3601
|
+
{
|
|
3602
|
+
args = DecodeInvocationFile(args);
|
|
3603
|
+
if (
|
|
3604
|
+
args != null &&
|
|
3605
|
+
args.Length == 1 &&
|
|
3606
|
+
String.Equals(
|
|
3607
|
+
args[0],
|
|
3608
|
+
"--probe-helper",
|
|
3609
|
+
StringComparison.Ordinal))
|
|
3610
|
+
{
|
|
3611
|
+
Console.Out.Write(
|
|
3612
|
+
JsonCodec.SerializeObject(
|
|
3613
|
+
"ready",
|
|
3614
|
+
true,
|
|
3615
|
+
"hostRuntime",
|
|
3616
|
+
System.Reflection.Assembly
|
|
3617
|
+
.GetExecutingAssembly()
|
|
3618
|
+
.EntryPoint == null
|
|
3619
|
+
? "powershell-byte-assembly-v1"
|
|
3620
|
+
: "managed-executable-v1",
|
|
3621
|
+
"sourceSha256",
|
|
3622
|
+
Native.SourceSha256));
|
|
3623
|
+
return 0;
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
if (
|
|
3627
|
+
args != null &&
|
|
3628
|
+
args.Length == 2 &&
|
|
3629
|
+
String.Equals(
|
|
3630
|
+
args[0],
|
|
3631
|
+
"--probe-node-snapshot",
|
|
3632
|
+
StringComparison.Ordinal))
|
|
3633
|
+
{
|
|
3634
|
+
int probeExitCode = Native.RunNodeSnapshotProbe(
|
|
3635
|
+
args[1],
|
|
3636
|
+
null,
|
|
3637
|
+
null);
|
|
3638
|
+
if (probeExitCode != 0)
|
|
3639
|
+
throw new InvalidDataException(
|
|
3640
|
+
"Node entry snapshot probe returned a non-zero exit code");
|
|
3641
|
+
Console.Out.Write(
|
|
3642
|
+
JsonCodec.SerializeObject(
|
|
3643
|
+
"ready",
|
|
3644
|
+
true,
|
|
3645
|
+
"targetRuntime",
|
|
3646
|
+
"node",
|
|
3647
|
+
"contentSnapshot",
|
|
3648
|
+
true));
|
|
3649
|
+
return 0;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
if (
|
|
3653
|
+
args != null &&
|
|
3654
|
+
(args.Length == 2 || args.Length == 3) &&
|
|
3655
|
+
String.Equals(
|
|
3656
|
+
args[0],
|
|
3657
|
+
"--prepare-appcontainer",
|
|
3658
|
+
StringComparison.Ordinal))
|
|
3659
|
+
{
|
|
3660
|
+
string profileName = args[1];
|
|
3661
|
+
string nodeRuntime =
|
|
3662
|
+
args.Length == 3 ? args[2] : null;
|
|
3663
|
+
string probeSid = null;
|
|
3664
|
+
string preparedSid = null;
|
|
3665
|
+
bool leavePreparedProfile = false;
|
|
3666
|
+
try
|
|
3667
|
+
{
|
|
3668
|
+
probeSid = Native.PrepareAppContainerProfile(
|
|
3669
|
+
profileName);
|
|
3670
|
+
preparedSid = probeSid;
|
|
3671
|
+
int probeExitCode =
|
|
3672
|
+
String.IsNullOrWhiteSpace(nodeRuntime)
|
|
3673
|
+
? Native.Run(
|
|
3674
|
+
Path.Combine(
|
|
3675
|
+
Environment.SystemDirectory,
|
|
3676
|
+
"cmd.exe"),
|
|
3677
|
+
new[] { "/d", "/s", "/c", "exit 0" },
|
|
3678
|
+
0,
|
|
3679
|
+
0,
|
|
3680
|
+
1,
|
|
3681
|
+
false,
|
|
3682
|
+
true,
|
|
3683
|
+
-1,
|
|
3684
|
+
false,
|
|
3685
|
+
true,
|
|
3686
|
+
Environment.SystemDirectory,
|
|
3687
|
+
null,
|
|
3688
|
+
null,
|
|
3689
|
+
profileName,
|
|
3690
|
+
probeSid,
|
|
3691
|
+
null,
|
|
3692
|
+
null,
|
|
3693
|
+
null,
|
|
3694
|
+
null)
|
|
3695
|
+
: Native.RunNodeSnapshotProbe(
|
|
3696
|
+
nodeRuntime,
|
|
3697
|
+
profileName,
|
|
3698
|
+
probeSid);
|
|
3699
|
+
if (probeExitCode != 0)
|
|
3700
|
+
throw new InvalidDataException(
|
|
3701
|
+
"AppContainer readiness target returned a non-zero exit code");
|
|
3702
|
+
|
|
3703
|
+
preparedSid = Native.PrepareAppContainerProfile(
|
|
3704
|
+
profileName);
|
|
3705
|
+
if (!String.Equals(
|
|
3706
|
+
preparedSid,
|
|
3707
|
+
probeSid,
|
|
3708
|
+
StringComparison.OrdinalIgnoreCase))
|
|
3709
|
+
{
|
|
3710
|
+
throw new InvalidDataException(
|
|
3711
|
+
"AppContainer SID changed during readiness attestation");
|
|
3712
|
+
}
|
|
3713
|
+
string probeRuntime =
|
|
3714
|
+
String.IsNullOrWhiteSpace(nodeRuntime)
|
|
3715
|
+
? "cmd"
|
|
3716
|
+
: "node";
|
|
3717
|
+
string readiness = JsonCodec.SerializeObject(
|
|
3718
|
+
"ready",
|
|
3719
|
+
true,
|
|
3720
|
+
"profileName",
|
|
3721
|
+
profileName,
|
|
3722
|
+
"appContainerSid",
|
|
3723
|
+
preparedSid,
|
|
3724
|
+
"capabilityCount",
|
|
3725
|
+
0,
|
|
3726
|
+
"tokenAttested",
|
|
3727
|
+
true,
|
|
3728
|
+
"restrictedTokenAttested",
|
|
3729
|
+
true,
|
|
3730
|
+
"probeRuntime",
|
|
3731
|
+
probeRuntime,
|
|
3732
|
+
"targetRuntime",
|
|
3733
|
+
probeRuntime);
|
|
3734
|
+
Console.Out.Write(readiness);
|
|
3735
|
+
leavePreparedProfile = true;
|
|
3736
|
+
return 0;
|
|
3737
|
+
}
|
|
3738
|
+
finally
|
|
3739
|
+
{
|
|
3740
|
+
if (
|
|
3741
|
+
!leavePreparedProfile &&
|
|
3742
|
+
!String.IsNullOrWhiteSpace(preparedSid))
|
|
3743
|
+
{
|
|
3744
|
+
Native.DeletePreparedAppContainerProfile(
|
|
3745
|
+
profileName,
|
|
3746
|
+
preparedSid);
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
if (
|
|
3752
|
+
args != null &&
|
|
3753
|
+
(args.Length == 2 || args.Length == 3) &&
|
|
3754
|
+
String.Equals(
|
|
3755
|
+
args[0],
|
|
3756
|
+
"--delete-appcontainer",
|
|
3757
|
+
StringComparison.Ordinal))
|
|
3758
|
+
{
|
|
3759
|
+
string expectedSid = args.Length == 3 ? args[2] : null;
|
|
3760
|
+
Native.DeletePreparedAppContainerProfile(
|
|
3761
|
+
args[1],
|
|
3762
|
+
expectedSid);
|
|
3763
|
+
Native.AssertAppContainerProfileAbsent(args[1]);
|
|
3764
|
+
Console.Out.Write(
|
|
3765
|
+
JsonCodec.SerializeObject(
|
|
3766
|
+
"deleted",
|
|
3767
|
+
true,
|
|
3768
|
+
"absent",
|
|
3769
|
+
true,
|
|
3770
|
+
"profileName",
|
|
3771
|
+
args[1]));
|
|
3772
|
+
return 0;
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
if (
|
|
3776
|
+
args != null &&
|
|
3777
|
+
args.Length == 2 &&
|
|
3778
|
+
String.Equals(
|
|
3779
|
+
args[0],
|
|
3780
|
+
"--assert-appcontainer-absent",
|
|
3781
|
+
StringComparison.Ordinal))
|
|
3782
|
+
{
|
|
3783
|
+
Native.AssertAppContainerProfileAbsent(args[1]);
|
|
3784
|
+
Console.Out.Write(
|
|
3785
|
+
JsonCodec.SerializeObject(
|
|
3786
|
+
"absent",
|
|
3787
|
+
true,
|
|
3788
|
+
"profileName",
|
|
3789
|
+
args[1]));
|
|
3790
|
+
return 0;
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
if (args == null || args.Length != 1)
|
|
3794
|
+
throw new ArgumentException("Expected one encoded launch payload");
|
|
3795
|
+
|
|
3796
|
+
string json = Encoding.UTF8.GetString(
|
|
3797
|
+
Convert.FromBase64String(args[0]));
|
|
3798
|
+
spec = JsonCodec.Deserialize<LaunchSpec>(json);
|
|
3799
|
+
if (
|
|
3800
|
+
spec == null ||
|
|
3801
|
+
String.IsNullOrWhiteSpace(spec.command) ||
|
|
3802
|
+
spec.environment == null)
|
|
3803
|
+
{
|
|
3804
|
+
throw new ArgumentException("Launch payload is incomplete");
|
|
3805
|
+
}
|
|
3806
|
+
return Native.Run(
|
|
3807
|
+
spec.command,
|
|
3808
|
+
spec.args ?? new string[0],
|
|
3809
|
+
spec.cpuSeconds,
|
|
3810
|
+
spec.processMemoryBytes,
|
|
3811
|
+
spec.activeProcessLimit,
|
|
3812
|
+
spec.allowReparsePaths,
|
|
3813
|
+
spec.disableAdministratorSids,
|
|
3814
|
+
spec.nodeIpcFd,
|
|
3815
|
+
spec.detached,
|
|
3816
|
+
spec.windowsHide,
|
|
3817
|
+
spec.workingDirectory,
|
|
3818
|
+
spec.environment,
|
|
3819
|
+
spec.identityPath,
|
|
3820
|
+
spec.appContainerProfileName,
|
|
3821
|
+
spec.appContainerSid,
|
|
3822
|
+
spec.launchPathLocks,
|
|
3823
|
+
null,
|
|
3824
|
+
spec.snapshotTestGateToken,
|
|
3825
|
+
spec.snapshotTestGateReleasePath);
|
|
3826
|
+
}
|
|
3827
|
+
catch (Exception error)
|
|
3828
|
+
{
|
|
3829
|
+
if (!String.IsNullOrWhiteSpace(spec == null ? null : spec.identityPath))
|
|
3830
|
+
{
|
|
3831
|
+
try
|
|
3832
|
+
{
|
|
3833
|
+
string failure = JsonCodec.SerializeObject(
|
|
3834
|
+
"error",
|
|
3835
|
+
error.ToString(),
|
|
3836
|
+
"helperPid",
|
|
3837
|
+
System.Diagnostics.Process
|
|
3838
|
+
.GetCurrentProcess()
|
|
3839
|
+
.Id);
|
|
3840
|
+
File.WriteAllText(
|
|
3841
|
+
spec.identityPath,
|
|
3842
|
+
failure,
|
|
3843
|
+
new UTF8Encoding(false));
|
|
3844
|
+
}
|
|
3845
|
+
catch
|
|
3846
|
+
{
|
|
3847
|
+
// The original native failure remains authoritative.
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
Console.Error.WriteLine(
|
|
3851
|
+
"CC_WINDOWS_SANDBOX_ERROR: " + error.Message);
|
|
3852
|
+
return 125;
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|