chainlesschain 0.162.182 → 0.162.184
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 +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DzZ5_xoj.js → AIOps-DzAl5fGx.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DeLXddIg.js → ActionButton-DAHtMGeC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-rvDCmet1.js → Analytics-BHQaexv5.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Yt6QVDnp.js → AppLayout-Phz_pD0n.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BS112K5L.js → Artifacts-DHt47kCA.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-0wJuQrNu.js → Audit--Y_8FULl.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-B90PMgbn.js → BackgroundAgents-N1zchH7D.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-TkgMglQQ.js → Backup-B_BoyN30.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CQVS9p1l.js → BaseInput-Gl4uQHcT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-YTjQHoC7.js → Chat-Cg-eIyAZ.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-5bwaOabq.js → Checkbox-D0MGR2wA.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BFmNQ_Xe.js → Codegen-BdObJEFn.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BT_u4Dzp.js → Col--ghxXT3z.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CJP52u5b.js → Community-BUFeXOqz.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BpzhJE9i.js → Compact-DhQO2_Mr.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Cg7rfslp.js → Compliance-3HoTqKYd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-JisxL6Yy.js → Cowork-B3zmO4VW.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Bu7Ug_MC.js → Cron-CJDrZOcc.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CvuqoLdj.js → Crosschain-SMl15obd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C2u9G8Pu.js → DID-CgTrTWrl.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BFt5Yv3d.js → Dashboard-dXFKaKol.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DIaT9Wxq.js → Dropdown-qQHEhCvI.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D_A601Y4.js → EmailListRenderer-C_ej0MWH.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard--IyQ8I8w.js → FamilyGuardDashboard-D6ajoE7I.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BihWHxuY.js → Federation-Cio2y49h.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BB8dOXc_.js → FormItemContext-ClJz1cYp.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DQYhj1HH.js → GenericCardRenderer-Dr_8Maca.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B-2QZxGf.js → Git-CevLwp5u.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CusSb-nG.js → Governance-CW9YyQUY.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BAjmvPPL.js → Inference-CQqeAyER.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DoNYWKYq.js → KnowledgeGraph-cuGVxHrw.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B8fKWD-n.js → Logs-DIuHri5d.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BlJm1J7E.js → MarkdownRenderer-R91Qw5JA.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CyOscbSL.js → Marketplace-CY1VhFGx.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Ex6Om5da.js → McpTools-BBAbi1vt.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-CDcD8l8r.js → Memory-BWYGHxJA.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-xfBqoFDg.js → MobileBridge-CPIFkW7A.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BwObMMA2.js → MobileProjects-DQyWxCho.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CIWd36OV.js → Mtc-Bo3GnQbx.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CPX0ouuA.js → MtcAudit-C8-c4rSW.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BDJsIUuW.js → Multisig-ChqMTtoq.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DEkbuD3N.js → NLProgramming-C48TyhvI.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DJpHRhH1.js → Notes-zogUFWxp.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C1UZ-Qd5.js → NotificationSettings-v-K9XpSB.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Bbhj0fac.js → Organization-Boi1dQfR.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-1H04x3kx.js → Overflow-DM5YWwgA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CDpPe3et.js → P2P-C57LKpKK.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-MUX0Bpvw.js → PdhVaultBrowser-BFkPqu6Q.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-A07-0Ci1.js → Permissions-D6fB9YSp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DhhDWxAZ.js → PersonalDataHub-Bp7EefEo.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-CyfbJfe-.js → Pipeline-BVln3hZf.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CNFps3ED.js → Privacy-Bf8P3nF6.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit--T-u3Dr5.js → ProjectInit-DM6wA2E2.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DJmrK2fC.js → ProjectSettings-CGbc5Iho.js} +2 -2
- package/src/assets/web-panel/assets/Projects-D0p9EOxm.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DLzzbOZd.js → Providers-B2kJcxS6.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CexOomKS.js → QrScannerModal-Cxu7dXYy.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-QklGi19_.js → QuickAsk-DTlrTPms.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bb9kwSw8.js → Recommend-Cm3X-cCj.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DZuVNNN5.js → RemoteSession-DNb2kjvt.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Dr5cLP0N.js → Reputation-CZUFFjc1.js} +1 -1
- package/src/assets/web-panel/assets/{Row-oCQHYv5t.js → Row-DAPgloxA.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-IFDEykIR.js → RssFeed-dsx9S793.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CspVIxDc.js → Search-DnnC8kz-.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Bm66o_DH.js → Security-VuZknRMK.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DvanNqXl.js → Services-B3XNX8Vx.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CHjEe9_O.js → Skeleton-Dl1nVtCj.js} +1 -1
- package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +1 -0
- package/src/assets/web-panel/assets/{Sla-DxZm1491.js → Sla-HzU56n1z.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DWxEEclC.js → SpeechSettings-CRle7Jl2.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-FIvOFw5p.js → SyncSettings-DWadz7Sl.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +1 -0
- package/src/assets/web-panel/assets/{Templates-CaNjzV_t.js → Templates-BBAgvrlM.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DjlP6TmH.js → Tenant-BsbbvGN8.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-uscpZI4q.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-DoZ8y7y8.js → TimelineRenderer-mTyjNE6r.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-390cg2TC.js → Tokens-Bz97oBgs.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CLrtCj5_.js → Trigger-BTRKC140.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DpESfSPn.js → Trust-B4A_KuyX.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CN6279O0.js → UkeySign-DES9TmLU.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DPtAUlSe.js → VideoEditing-ClXs7Qxp.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-nKOMV_1n.js → Wallet-IzZqFz-7.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-HuXlSOc4.js → WebAuthn-BtOP04g_.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CUfFjARi.js → WorkflowEditor-_0cqnopY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-6Eq0PcZJ.js → chat-ax7193uU.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BP_Sf6Iy.js → colors-Bu4WGJah.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BufMKIh5.js → compact-item-BX1dlTW0.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BBMXxWSW.js → createContext-CktA_Dj7.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-x877iKKS.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-fUK_tRO2.js → hasIn-D5wd817l.js} +1 -1
- package/src/assets/web-panel/assets/{index-t8N1tSeX.js → index-5PYYkr8S.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLpIs_P_.js → index-A4uBPQCz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVsqvI4M.js → index-BAtBNfkr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bkg4ZePE.js → index-BLLsUNDc.js} +1 -1
- package/src/assets/web-panel/assets/{index-cpEli44X.js → index-BQtoRyTO.js} +1 -1
- package/src/assets/web-panel/assets/{index-wKTLbKyc.js → index-BRfXoRuh.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7Etk0Te.js → index-Bad2CYeH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwSf5pnf.js → index-BcGdL3sG.js} +1 -1
- package/src/assets/web-panel/assets/{index-3FuwBewR.js → index-C3HmzCLt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHy6yNiF.js → index-C8DkWyNr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgiECHu2.js → index-C8tLR2TG.js} +1 -1
- package/src/assets/web-panel/assets/{index-C44atv4k.js → index-CP8ELJBc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW_G-MXw.js → index-CUForzsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-pum7HYBA.js → index-CYRE0NJA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWKoBU8R.js → index-CeVZaJSC.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4_MuGkd.js → index-CfzxR61U.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dyngbd29.js → index-Ci3y1ZAv.js} +19 -19
- package/src/assets/web-panel/assets/{index-fzaMtSJ8.js → index-CjEJjl5C.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cp8vhyZy.js → index-Cv7bVWJ5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPdUNk1i.js → index-D-o4Rwjk.js} +1 -1
- package/src/assets/web-panel/assets/index-D7pfPouB.js +1 -0
- package/src/assets/web-panel/assets/{index-CeASi4fr.js → index-D9op8poE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DASluSqY.js → index-DF1TysIF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocdRcbd.js → index-DYo9S5OT.js} +1 -1
- package/src/assets/web-panel/assets/{index-2A5ph8b2.js → index-D_cycOET.js} +1 -1
- package/src/assets/web-panel/assets/{index-wV6iGdpY.js → index-Db3CpWTr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMLHv389.js → index-DkD1K-fF.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9WYSn4m.js → index-DmPJ6T6F.js} +1 -1
- package/src/assets/web-panel/assets/{index-uCbEN7cY.js → index-DmVUeWUk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmkMXwnR.js → index-Dzdr0zDf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZ6Yt17L.js → index-PBLzwYnu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE2DdTP2.js → index-bFCaM7Eq.js} +1 -1
- package/src/assets/web-panel/assets/index-e4i-VLBd.js +1 -0
- package/src/assets/web-panel/assets/{index-GwdRUZ-x.js → index-h3KJZr95.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2vS66eI.js → index-iHcPtQ2e.js} +1 -1
- package/src/assets/web-panel/assets/{index-6GmBa4Kg.js → index-jzlzm4VK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9vU25ij.js → index-o14NNnxw.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGjmILDm.js → index-tYPSfGbd.js} +1 -1
- package/src/assets/web-panel/assets/{index-LvCBfTLo.js → index-ytp2H5Zx.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-V5Fo95FM.js → initDefaultProps-D2Iut83B.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BYWU0hUT.js → motion-BuaJVdK9.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cr1M-J2h.js → move-CaEuM92y.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BysQKi2N.js → mtc-parser-wuKNBwys.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D1zHU8GC.js → omit-7a3eklaq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CUArxtfi.js → pickAttrs-Dn4z7KvJ.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-ChoYEys0.js → placementArrow-DVn_qT4-.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BQRs3Mo7.js → responsiveObserve-Dodz1OMQ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B8zbsCkg.js → slide-CxoGskbp.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C2U8Gs6h.js → statusUtils-DBh_epQH.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dd8MgL_V.js → styleChecker-RXQ4gG5P.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-hknqY5NH.js → useFlexGapSupport-1Wxm0HEG.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CLTAmcfw.js → useFs-CI_ySNxJ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-ByegHbiB.js → usePersonalDataHub-C6973rZP.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DqlAy5DP.js → vnode-CalynwKr.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DiXiztQn.js → zoom-CM1cd-Q0.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +74 -1
- package/src/commands/agent.js +9 -0
- package/src/commands/background-session.js +60 -1
- package/src/commands/batch.js +160 -5
- package/src/commands/plugin.js +318 -43
- package/src/commands/session.js +44 -1
- package/src/commands/team.js +212 -6
- package/src/data/changelog.json +26 -0
- package/src/harness/jsonl-session-store.js +59 -6
- package/src/harness/mcp-client.js +65 -43
- package/src/lib/agent-batch.js +4 -1
- package/src/lib/agent-team/team-runner.js +19 -1
- package/src/lib/agent-team/team-worktree.js +3 -0
- package/src/lib/async-hook-supervisor.cjs +82 -10
- package/src/lib/async-hook-supervisor.js +50 -4
- package/src/lib/background-agent-supervisor.js +49 -0
- package/src/lib/collaboration-run-store.js +362 -0
- package/src/lib/hook-runner.cjs +98 -17
- package/src/lib/hook-runner.js +77 -6
- package/src/lib/hook-shell-command.cjs +148 -0
- package/src/lib/hooks-v2-runtime.js +188 -77
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +18 -0
- package/src/lib/lsp/lsp-client.js +12 -1
- package/src/lib/plugin-monitor-supervisor.js +53 -16
- package/src/lib/plugin-runtime/bin.js +1 -1
- package/src/lib/plugin-runtime/install.js +465 -54
- package/src/lib/plugin-runtime/mcp.js +21 -2
- package/src/lib/plugin-runtime/monitors.js +19 -3
- package/src/lib/plugin-runtime/sandbox-policy.js +99 -0
- package/src/lib/plugin-runtime/scopes.js +13 -2
- package/src/lib/plugin-usage-attribution.js +44 -0
- package/src/lib/process-execution-broker/index.js +705 -13
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1347 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +1260 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +1831 -157
- package/src/lib/session-usage.js +278 -1
- package/src/lib/stream-retry.js +29 -0
- package/src/repl/agent-repl.js +38 -9
- package/src/runtime/agent-core.js +32 -4
- package/src/runtime/headless-runner.js +46 -1
- package/src/runtime/headless-stream.js +54 -3
- package/src/runtime/mcp-config.js +15 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-PR074hUN.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-GlJtqLjT.js +0 -1
- package/src/assets/web-panel/assets/Projects-Qx_MdX9k.js +0 -1
- package/src/assets/web-panel/assets/Skills-Cb8jWjMX.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CyuLnDFR.js +0 -1
- package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +0 -3
- package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +0 -1
- package/src/assets/web-panel/assets/index-DOZTSoS7.js +0 -1
- package/src/assets/web-panel/assets/index-DhpvjpqI.js +0 -1
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* Platform enforcement currently available:
|
|
5
5
|
* - macOS: Seatbelt sandbox-exec profiles
|
|
6
6
|
* - Linux: prlimit resource-limit wrapper, plus a narrow bubblewrap backend
|
|
7
|
-
* for an attested direct strict Plugin Node bin or
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* for an attested direct strict Plugin Node bin or a narrow static/dynamic
|
|
8
|
+
* ELF native bin. Dynamic acceptance binds PT_INTERP plus the entry's direct
|
|
9
|
+
* DT_NEEDED names to an attested system-file set; broader loader surfaces,
|
|
10
|
+
* shells, broader working directories, host-writable paths, inherited
|
|
11
|
+
* descriptors, and network egress remain fail-closed.
|
|
11
12
|
* - Windows: Win32 Job Object + restricted-token wrapper, with an attested
|
|
12
13
|
* zero-capability AppContainer for explicit filesystem/network boundaries
|
|
13
14
|
*
|
|
@@ -25,6 +26,10 @@ import * as fs from "node:fs";
|
|
|
25
26
|
import crypto from "node:crypto";
|
|
26
27
|
import { spawnSync as nativeSpawnSync } from "node:child_process";
|
|
27
28
|
import { fileURLToPath } from "node:url";
|
|
29
|
+
import {
|
|
30
|
+
applyLinuxGenericWorkspaceSandbox,
|
|
31
|
+
isLinuxGenericWorkspaceContract,
|
|
32
|
+
} from "./linux-generic-bwrap-runtime.js";
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* Stable boundary identifiers shared by broker policies, platform plans, and
|
|
@@ -146,14 +151,38 @@ const LINUX_LDD_PATH = "/usr/bin/ldd";
|
|
|
146
151
|
const LINUX_BWRAP_BACKEND = "linux-bwrap";
|
|
147
152
|
const LINUX_BWRAP_NODE_PROBE_SENTINEL = "chainless-linux-bwrap-plugin-node-v1";
|
|
148
153
|
const LINUX_BWRAP_MAX_PLUGIN_ENTRIES = 512;
|
|
149
|
-
const
|
|
154
|
+
const LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES = 256;
|
|
155
|
+
const LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES = 256 * 1024 * 1024;
|
|
156
|
+
const LINUX_BWRAP_SUPERVISOR_CHILD_FD = 3;
|
|
157
|
+
const LINUX_BWRAP_FIRST_MOUNT_FD = 4;
|
|
158
|
+
const LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH = "/run/.chainless-bwrap-supervisor";
|
|
159
|
+
const LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM =
|
|
160
|
+
"pinned-child-fd3-file-consume-run-overmount-v1";
|
|
150
161
|
const LINUX_ATTESTED_FILE_MAX_BYTES = 256 * 1024 * 1024;
|
|
151
162
|
const LINUX_ATTESTATION_HASH_CHUNK_BYTES = 1024 * 1024;
|
|
163
|
+
const LINUX_ENTRY_SNAPSHOT_MECHANISM =
|
|
164
|
+
"verified-o_tmpfile-copy-bwrap-ro-bind-data-v1";
|
|
165
|
+
const LINUX_ENTRY_SNAPSHOT_SOURCE_MODE = 0o400;
|
|
166
|
+
const LINUX_NODE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-source";
|
|
167
|
+
const LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-executable";
|
|
168
|
+
const LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE = "all-pinned-plugin-regular-files";
|
|
169
|
+
const LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY = "per-file-pin-to-launch";
|
|
170
|
+
const LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE = "0400";
|
|
171
|
+
const LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE = "0500";
|
|
152
172
|
const LINUX_ELF64_HEADER_BYTES = 64;
|
|
153
173
|
const LINUX_ELF64_PROGRAM_HEADER_BYTES = 56;
|
|
154
174
|
const LINUX_ELF64_DYNAMIC_ENTRY_BYTES = 16;
|
|
155
175
|
const LINUX_ELF_MAX_PROGRAM_HEADERS = 128;
|
|
156
176
|
const LINUX_ELF_MAX_DYNAMIC_ENTRIES = 4096;
|
|
177
|
+
const LINUX_ELF_MAX_NEEDED_ENTRIES = 128;
|
|
178
|
+
const LINUX_ELF_MAX_INTERPRETER_BYTES = 4096;
|
|
179
|
+
const LINUX_ELF_MAX_STRING_TABLE_BYTES = 1024 * 1024;
|
|
180
|
+
const LINUX_AUXV_ENTRY_BYTES = 16;
|
|
181
|
+
const LINUX_AUXV_MAX_BYTES = 64 * 1024;
|
|
182
|
+
const LINUX_AUXV_PAGE_SIZE = 6n;
|
|
183
|
+
const LINUX_MIN_RUNTIME_PAGE_BYTES = 4096;
|
|
184
|
+
const LINUX_MAX_RUNTIME_PAGE_BYTES = 1024 * 1024;
|
|
185
|
+
const LINUX_ELF_MAX_UINT64 = (1n << 64n) - 1n;
|
|
157
186
|
const LINUX_ELF_TYPE_EXEC = 2;
|
|
158
187
|
const LINUX_ELF_TYPE_DYN = 3;
|
|
159
188
|
const LINUX_ELF_PROGRAM_LOAD = 1;
|
|
@@ -162,8 +191,24 @@ const LINUX_ELF_PROGRAM_INTERP = 3;
|
|
|
162
191
|
const LINUX_ELF_PROGRAM_GNU_STACK = 0x6474e551;
|
|
163
192
|
const LINUX_ELF_DYNAMIC_NULL = 0n;
|
|
164
193
|
const LINUX_ELF_DYNAMIC_NEEDED = 1n;
|
|
194
|
+
const LINUX_ELF_DYNAMIC_STRTAB = 5n;
|
|
195
|
+
const LINUX_ELF_DYNAMIC_STRSZ = 10n;
|
|
196
|
+
const LINUX_ELF_DYNAMIC_RPATH = 15n;
|
|
197
|
+
const LINUX_ELF_DYNAMIC_TEXTREL = 22n;
|
|
198
|
+
const LINUX_ELF_DYNAMIC_FLAGS = 30n;
|
|
199
|
+
const LINUX_ELF_DYNAMIC_RUNPATH = 29n;
|
|
200
|
+
const LINUX_ELF_DYNAMIC_CONFIG = 0x6ffffefan;
|
|
201
|
+
const LINUX_ELF_DYNAMIC_DEPAUDIT = 0x6ffffefbn;
|
|
202
|
+
const LINUX_ELF_DYNAMIC_AUDIT = 0x6ffffefcn;
|
|
165
203
|
const LINUX_ELF_DYNAMIC_FLAGS_1 = 0x6ffffffbn;
|
|
204
|
+
const LINUX_ELF_DYNAMIC_AUXILIARY = 0x7ffffffdn;
|
|
205
|
+
const LINUX_ELF_DYNAMIC_FILTER = 0x7fffffffn;
|
|
206
|
+
const LINUX_ELF_DYNAMIC_FLAG_TEXTREL = 0x4n;
|
|
166
207
|
const LINUX_ELF_DYNAMIC_FLAG_PIE = 0x08000000n;
|
|
208
|
+
const LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE =
|
|
209
|
+
"initial-pt_interp-and-direct-dt_needed-attested-system-set";
|
|
210
|
+
const LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM =
|
|
211
|
+
"parsed-elf-direct-system-set-to-attested-node-runtime-fds-v1";
|
|
167
212
|
const LINUX_ELF_MACHINES = Object.freeze({
|
|
168
213
|
x64: 62,
|
|
169
214
|
arm64: 183,
|
|
@@ -2582,18 +2627,131 @@ function linuxBubblewrapProbe(
|
|
|
2582
2627
|
runnable,
|
|
2583
2628
|
reason,
|
|
2584
2629
|
targetRuntime = "node",
|
|
2630
|
+
contentSnapshot = null,
|
|
2631
|
+
supervisorBinding = null,
|
|
2632
|
+
pluginTreeSnapshot = null,
|
|
2633
|
+
nativeDynamicClosure = null,
|
|
2585
2634
|
) {
|
|
2586
|
-
const native =
|
|
2635
|
+
const native =
|
|
2636
|
+
typeof targetRuntime === "string" && targetRuntime.startsWith("native-");
|
|
2637
|
+
const dynamicNative = targetRuntime === "native-dynamic-elf";
|
|
2638
|
+
const expectedSnapshotScope = native
|
|
2639
|
+
? LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE
|
|
2640
|
+
: LINUX_NODE_ENTRY_SNAPSHOT_SCOPE;
|
|
2641
|
+
const snapshot =
|
|
2642
|
+
contentSnapshot?.scope === expectedSnapshotScope &&
|
|
2643
|
+
contentSnapshot?.mechanism === LINUX_ENTRY_SNAPSHOT_MECHANISM;
|
|
2644
|
+
const supervisorDescriptorBound =
|
|
2645
|
+
supervisorBinding?.mechanism === LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM;
|
|
2646
|
+
const pluginTreeContentSnapshot =
|
|
2647
|
+
!native &&
|
|
2648
|
+
attempted &&
|
|
2649
|
+
snapshot &&
|
|
2650
|
+
runnable &&
|
|
2651
|
+
reason === null &&
|
|
2652
|
+
supervisorDescriptorBound &&
|
|
2653
|
+
pluginTreeSnapshot?.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
|
|
2654
|
+
pluginTreeSnapshot?.mechanism === LINUX_ENTRY_SNAPSHOT_MECHANISM &&
|
|
2655
|
+
Number.isSafeInteger(pluginTreeSnapshot?.files) &&
|
|
2656
|
+
pluginTreeSnapshot.files > 0 &&
|
|
2657
|
+
pluginTreeSnapshot.files <= LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES &&
|
|
2658
|
+
Number.isSafeInteger(pluginTreeSnapshot?.bytes) &&
|
|
2659
|
+
pluginTreeSnapshot.bytes >= 0 &&
|
|
2660
|
+
pluginTreeSnapshot.bytes <= LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES &&
|
|
2661
|
+
typeof pluginTreeSnapshot?.digest === "string" &&
|
|
2662
|
+
/^[a-f0-9]{64}$/.test(pluginTreeSnapshot.digest) &&
|
|
2663
|
+
pluginTreeSnapshot?.consistency ===
|
|
2664
|
+
LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
|
|
2665
|
+
pluginTreeSnapshot?.contractBound === false &&
|
|
2666
|
+
pluginTreeSnapshot?.atomic === false;
|
|
2667
|
+
const initialDynamicLoadClosureDescriptorBound =
|
|
2668
|
+
dynamicNative &&
|
|
2669
|
+
attempted &&
|
|
2670
|
+
snapshot &&
|
|
2671
|
+
runnable &&
|
|
2672
|
+
reason === null &&
|
|
2673
|
+
supervisorDescriptorBound &&
|
|
2674
|
+
nativeDynamicClosure?.mechanism ===
|
|
2675
|
+
LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM &&
|
|
2676
|
+
nativeDynamicClosure?.scope ===
|
|
2677
|
+
LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE &&
|
|
2678
|
+
typeof nativeDynamicClosure?.interpreter === "string" &&
|
|
2679
|
+
path.posix.isAbsolute(nativeDynamicClosure.interpreter) &&
|
|
2680
|
+
Number.isSafeInteger(nativeDynamicClosure?.dependencies) &&
|
|
2681
|
+
nativeDynamicClosure.dependencies >= 0 &&
|
|
2682
|
+
nativeDynamicClosure.dependencies <= LINUX_ELF_MAX_NEEDED_ENTRIES &&
|
|
2683
|
+
Number.isSafeInteger(nativeDynamicClosure?.files) &&
|
|
2684
|
+
nativeDynamicClosure.files >= 1 &&
|
|
2685
|
+
typeof nativeDynamicClosure?.digest === "string" &&
|
|
2686
|
+
/^[a-f0-9]{64}$/.test(nativeDynamicClosure.digest);
|
|
2587
2687
|
return {
|
|
2588
|
-
kind:
|
|
2589
|
-
? "linux-bwrap-plugin-native-
|
|
2590
|
-
: "
|
|
2688
|
+
kind: dynamicNative
|
|
2689
|
+
? "linux-bwrap-plugin-native-dynamic-elf-policy-v1"
|
|
2690
|
+
: targetRuntime === "native-static-elf"
|
|
2691
|
+
? "linux-bwrap-plugin-native-static-elf-policy-v1"
|
|
2692
|
+
: native
|
|
2693
|
+
? "linux-bwrap-plugin-native-elf-policy-v1"
|
|
2694
|
+
: "linux-bwrap-plugin-node-policy-v1",
|
|
2591
2695
|
attempted,
|
|
2592
2696
|
runnable,
|
|
2593
2697
|
reason,
|
|
2594
2698
|
probeRuntime: "node",
|
|
2595
2699
|
targetRuntime,
|
|
2596
|
-
contentSnapshot:
|
|
2700
|
+
contentSnapshot: snapshot,
|
|
2701
|
+
...(snapshot
|
|
2702
|
+
? {
|
|
2703
|
+
contentSnapshotScope: contentSnapshot.scope,
|
|
2704
|
+
contentSnapshotMechanism: contentSnapshot.mechanism,
|
|
2705
|
+
}
|
|
2706
|
+
: {}),
|
|
2707
|
+
...(pluginTreeContentSnapshot
|
|
2708
|
+
? {
|
|
2709
|
+
pluginTreeContentSnapshot: true,
|
|
2710
|
+
pluginTreeContentSnapshotScope: pluginTreeSnapshot.scope,
|
|
2711
|
+
pluginTreeContentSnapshotMechanism: pluginTreeSnapshot.mechanism,
|
|
2712
|
+
pluginTreeContentSnapshotFiles: pluginTreeSnapshot.files,
|
|
2713
|
+
pluginTreeContentSnapshotBytes: pluginTreeSnapshot.bytes,
|
|
2714
|
+
pluginTreeContentSnapshotDigest: pluginTreeSnapshot.digest,
|
|
2715
|
+
pluginTreeSnapshotConsistency: pluginTreeSnapshot.consistency,
|
|
2716
|
+
pluginTreeSnapshotContractBound: pluginTreeSnapshot.contractBound,
|
|
2717
|
+
pluginTreeSnapshotAtomic: pluginTreeSnapshot.atomic,
|
|
2718
|
+
}
|
|
2719
|
+
: {}),
|
|
2720
|
+
...(initialDynamicLoadClosureDescriptorBound
|
|
2721
|
+
? {
|
|
2722
|
+
initialDynamicLoadClosureDescriptorBound: true,
|
|
2723
|
+
initialDynamicLoadClosureScope: nativeDynamicClosure.scope,
|
|
2724
|
+
initialDynamicLoadClosureMechanism: nativeDynamicClosure.mechanism,
|
|
2725
|
+
initialDynamicInterpreter: nativeDynamicClosure.interpreter,
|
|
2726
|
+
initialDynamicDependencyCount: nativeDynamicClosure.dependencies,
|
|
2727
|
+
initialDynamicRuntimeFileCount: nativeDynamicClosure.files,
|
|
2728
|
+
initialDynamicLoadClosureDigest: nativeDynamicClosure.digest,
|
|
2729
|
+
}
|
|
2730
|
+
: {}),
|
|
2731
|
+
supervisorDescriptorBound,
|
|
2732
|
+
...(supervisorDescriptorBound
|
|
2733
|
+
? {
|
|
2734
|
+
supervisorExecutablePinned: true,
|
|
2735
|
+
supervisorBindingScope: supervisorBinding.scope,
|
|
2736
|
+
supervisorDescriptorBindingMechanism: supervisorBinding.mechanism,
|
|
2737
|
+
supervisorDescriptorContained: runnable,
|
|
2738
|
+
supervisorDescriptorConsumedBeforeTarget: runnable,
|
|
2739
|
+
supervisorStagingPathHidden: runnable,
|
|
2740
|
+
supervisorTemporaryCopyObscured: runnable,
|
|
2741
|
+
supervisorPid1ExecutableExposure:
|
|
2742
|
+
supervisorBinding.pid1ExecutableExposure,
|
|
2743
|
+
supervisorExecutableIdentity: {
|
|
2744
|
+
path: supervisorBinding.path,
|
|
2745
|
+
fileId: supervisorBinding.fileId,
|
|
2746
|
+
sha256: supervisorBinding.sha256,
|
|
2747
|
+
bytes: supervisorBinding.bytes,
|
|
2748
|
+
mtimeMs: supervisorBinding.mtimeMs,
|
|
2749
|
+
mode: supervisorBinding.mode,
|
|
2750
|
+
uid: supervisorBinding.uid,
|
|
2751
|
+
gid: supervisorBinding.gid,
|
|
2752
|
+
},
|
|
2753
|
+
}
|
|
2754
|
+
: {}),
|
|
2597
2755
|
handleAtomic: false,
|
|
2598
2756
|
};
|
|
2599
2757
|
}
|
|
@@ -2836,7 +2994,7 @@ function assignLinuxMountChildFds(mounts) {
|
|
|
2836
2994
|
function linuxStdioWithPinnedMounts(
|
|
2837
2995
|
stdio,
|
|
2838
2996
|
descriptors,
|
|
2839
|
-
{ probe = false } = {},
|
|
2997
|
+
{ probe = false, supervisorFd = null } = {},
|
|
2840
2998
|
) {
|
|
2841
2999
|
let standard;
|
|
2842
3000
|
if (probe) {
|
|
@@ -2849,8 +3007,10 @@ function linuxStdioWithPinnedMounts(
|
|
|
2849
3007
|
standard = ["pipe", "pipe", "pipe"];
|
|
2850
3008
|
}
|
|
2851
3009
|
while (standard.length < 3) standard.push(undefined);
|
|
3010
|
+
const supervisor = Number.isInteger(supervisorFd) ? [supervisorFd] : [];
|
|
2852
3011
|
return [
|
|
2853
3012
|
...standard.slice(0, 3),
|
|
3013
|
+
...supervisor,
|
|
2854
3014
|
...descriptors.map((descriptor) => descriptor.fd),
|
|
2855
3015
|
];
|
|
2856
3016
|
}
|
|
@@ -2862,10 +3022,16 @@ function validateLinuxPluginContract(
|
|
|
2862
3022
|
contract,
|
|
2863
3023
|
runtime,
|
|
2864
3024
|
sync,
|
|
3025
|
+
{ sealedEntry = false } = {},
|
|
2865
3026
|
) {
|
|
2866
3027
|
const nodeContract = contract?.kind === "strict-plugin-node-bin";
|
|
2867
|
-
const
|
|
3028
|
+
const legacyStaticNativeContract =
|
|
2868
3029
|
contract?.kind === "strict-plugin-native-static-elf-bin";
|
|
3030
|
+
const nativeContract =
|
|
3031
|
+
legacyStaticNativeContract ||
|
|
3032
|
+
contract?.kind === "strict-plugin-native-elf-bin";
|
|
3033
|
+
const entryIsSealed =
|
|
3034
|
+
(nodeContract || nativeContract) && sealedEntry === true;
|
|
2869
3035
|
if (
|
|
2870
3036
|
!contract ||
|
|
2871
3037
|
contract.contractVersion !== 1 ||
|
|
@@ -2973,9 +3139,10 @@ function validateLinuxPluginContract(
|
|
|
2973
3139
|
}
|
|
2974
3140
|
if (
|
|
2975
3141
|
!linuxDirectoryIdentityMatches(runtime, contract.rootIdentity) ||
|
|
2976
|
-
!
|
|
2977
|
-
|
|
2978
|
-
|
|
3142
|
+
(!entryIsSealed &&
|
|
3143
|
+
!linuxIdentityMatches(runtime, contract.entryIdentity, {
|
|
3144
|
+
executable: nativeContract,
|
|
3145
|
+
})) ||
|
|
2979
3146
|
!linuxIdentityMatches(runtime, contract.runtimeIdentity, {
|
|
2980
3147
|
executable: true,
|
|
2981
3148
|
})
|
|
@@ -2983,18 +3150,24 @@ function validateLinuxPluginContract(
|
|
|
2983
3150
|
return { ok: false, reason: "execution_identity_changed" };
|
|
2984
3151
|
}
|
|
2985
3152
|
let entryFormat = null;
|
|
2986
|
-
if (nativeContract) {
|
|
3153
|
+
if (nativeContract && !entryIsSealed) {
|
|
2987
3154
|
try {
|
|
2988
|
-
entryFormat =
|
|
2989
|
-
runtime,
|
|
2990
|
-
contract.entryIdentity,
|
|
2991
|
-
);
|
|
3155
|
+
entryFormat = inspectLinuxNativePath(runtime, contract.entryIdentity);
|
|
2992
3156
|
} catch (error) {
|
|
2993
3157
|
return {
|
|
2994
3158
|
ok: false,
|
|
2995
3159
|
reason: error.message || "native_entry_unattested",
|
|
2996
3160
|
};
|
|
2997
3161
|
}
|
|
3162
|
+
if (
|
|
3163
|
+
legacyStaticNativeContract &&
|
|
3164
|
+
entryFormat.runtime !== "native-static-elf"
|
|
3165
|
+
) {
|
|
3166
|
+
return {
|
|
3167
|
+
ok: false,
|
|
3168
|
+
reason: "native_entry_dynamic_contract_required",
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
2998
3171
|
}
|
|
2999
3172
|
if (
|
|
3000
3173
|
!validateLinuxPluginTree(runtime, contract.pluginRoot) ||
|
|
@@ -3005,7 +3178,12 @@ function validateLinuxPluginContract(
|
|
|
3005
3178
|
return {
|
|
3006
3179
|
ok: true,
|
|
3007
3180
|
contract,
|
|
3008
|
-
entryRuntime: nativeContract
|
|
3181
|
+
entryRuntime: nativeContract
|
|
3182
|
+
? entryFormat?.runtime ||
|
|
3183
|
+
(legacyStaticNativeContract
|
|
3184
|
+
? "native-static-elf"
|
|
3185
|
+
: "native-unclassified")
|
|
3186
|
+
: "node",
|
|
3009
3187
|
entryFormat,
|
|
3010
3188
|
};
|
|
3011
3189
|
}
|
|
@@ -3073,18 +3251,663 @@ function hashLinuxOpenFile(runtime, fd, bytes) {
|
|
|
3073
3251
|
return digest.digest("hex");
|
|
3074
3252
|
}
|
|
3075
3253
|
|
|
3254
|
+
function linuxEntrySnapshotContract(targetRuntime) {
|
|
3255
|
+
if (targetRuntime?.startsWith("native-")) {
|
|
3256
|
+
return {
|
|
3257
|
+
scope: LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE,
|
|
3258
|
+
targetMode: LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE,
|
|
3259
|
+
minimumBytes: LINUX_ELF64_HEADER_BYTES,
|
|
3260
|
+
errorPrefix: "native_entry_snapshot",
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
if (targetRuntime === "node") {
|
|
3264
|
+
return {
|
|
3265
|
+
scope: LINUX_NODE_ENTRY_SNAPSHOT_SCOPE,
|
|
3266
|
+
targetMode: LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE,
|
|
3267
|
+
minimumBytes: 0,
|
|
3268
|
+
errorPrefix: "node_entry_snapshot",
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
throw new Error("entry_snapshot_runtime_unsupported");
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function linuxNodePluginTreeFileSnapshotContract(sourceMode) {
|
|
3275
|
+
if (!Number.isSafeInteger(sourceMode) || sourceMode < 0) {
|
|
3276
|
+
throw new Error("node_plugin_tree_snapshot_source_mode_invalid");
|
|
3277
|
+
}
|
|
3278
|
+
return {
|
|
3279
|
+
scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
|
|
3280
|
+
targetMode: (sourceMode & 0o111) !== 0 ? "0500" : "0400",
|
|
3281
|
+
minimumBytes: 0,
|
|
3282
|
+
errorPrefix: "node_plugin_tree_snapshot",
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
function createLinuxRegularFileSnapshot(
|
|
3287
|
+
runtime,
|
|
3288
|
+
entryMount,
|
|
3289
|
+
entryIdentity,
|
|
3290
|
+
snapshotContract,
|
|
3291
|
+
) {
|
|
3292
|
+
let writerFd;
|
|
3293
|
+
let probeFd;
|
|
3294
|
+
let finalFd;
|
|
3295
|
+
try {
|
|
3296
|
+
if (
|
|
3297
|
+
!snapshotContract ||
|
|
3298
|
+
typeof snapshotContract.scope !== "string" ||
|
|
3299
|
+
typeof snapshotContract.targetMode !== "string" ||
|
|
3300
|
+
!Number.isSafeInteger(snapshotContract.minimumBytes) ||
|
|
3301
|
+
typeof snapshotContract.errorPrefix !== "string"
|
|
3302
|
+
) {
|
|
3303
|
+
throw new Error("file_snapshot_contract_invalid");
|
|
3304
|
+
}
|
|
3305
|
+
const snapshotError = (reason) =>
|
|
3306
|
+
new Error(`${snapshotContract.errorPrefix}_${reason}`);
|
|
3307
|
+
const expectedSha256 = entryIdentity?.sha256 ?? null;
|
|
3308
|
+
for (const method of [
|
|
3309
|
+
"openSync",
|
|
3310
|
+
"readSync",
|
|
3311
|
+
"writeSync",
|
|
3312
|
+
"fchmodSync",
|
|
3313
|
+
"fsyncSync",
|
|
3314
|
+
"fstatSync",
|
|
3315
|
+
"closeSync",
|
|
3316
|
+
]) {
|
|
3317
|
+
if (typeof runtime.fs[method] !== "function") {
|
|
3318
|
+
throw snapshotError(`${method}_unavailable`);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
if (
|
|
3322
|
+
!Number.isInteger(entryMount?.fd) ||
|
|
3323
|
+
!entryIdentity ||
|
|
3324
|
+
(expectedSha256 !== null &&
|
|
3325
|
+
(typeof expectedSha256 !== "string" ||
|
|
3326
|
+
!/^[a-f0-9]{64}$/.test(expectedSha256))) ||
|
|
3327
|
+
!Number.isSafeInteger(entryIdentity.bytes) ||
|
|
3328
|
+
entryIdentity.bytes < snapshotContract.minimumBytes ||
|
|
3329
|
+
entryIdentity.bytes > LINUX_ATTESTED_FILE_MAX_BYTES
|
|
3330
|
+
) {
|
|
3331
|
+
throw snapshotError("identity_invalid");
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
const sourceBefore = runtime.fs.fstatSync(entryMount.fd);
|
|
3335
|
+
const sourceFileMode = Number(sourceBefore.mode) & 0o7777;
|
|
3336
|
+
if (
|
|
3337
|
+
!sourceBefore.isFile() ||
|
|
3338
|
+
Number(sourceBefore.nlink) !== 1 ||
|
|
3339
|
+
String(sourceBefore.dev) !== String(entryIdentity.fileId?.dev) ||
|
|
3340
|
+
String(sourceBefore.ino) !== String(entryIdentity.fileId?.ino) ||
|
|
3341
|
+
Number(sourceBefore.size) !== entryIdentity.bytes ||
|
|
3342
|
+
Number(sourceBefore.mtimeMs) !== Number(entryIdentity.mtimeMs)
|
|
3343
|
+
) {
|
|
3344
|
+
throw snapshotError("source_changed");
|
|
3345
|
+
}
|
|
3346
|
+
if (
|
|
3347
|
+
snapshotContract.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
|
|
3348
|
+
snapshotContract.targetMode !==
|
|
3349
|
+
linuxNodePluginTreeFileSnapshotContract(sourceFileMode).targetMode
|
|
3350
|
+
) {
|
|
3351
|
+
throw snapshotError("source_mode_changed");
|
|
3352
|
+
}
|
|
3353
|
+
if (
|
|
3354
|
+
snapshotContract.scope === LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE &&
|
|
3355
|
+
(sourceFileMode & 0o111) === 0
|
|
3356
|
+
) {
|
|
3357
|
+
throw snapshotError("source_mode_changed");
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
3361
|
+
const writerFlags =
|
|
3362
|
+
Number(constants.O_RDWR) |
|
|
3363
|
+
Number(constants.O_EXCL) |
|
|
3364
|
+
Number(constants.O_TMPFILE ?? LINUX_O_TMPFILE) |
|
|
3365
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
3366
|
+
Number(constants.O_NONBLOCK || 0);
|
|
3367
|
+
writerFd = runtime.fs.openSync(
|
|
3368
|
+
"/tmp",
|
|
3369
|
+
writerFlags,
|
|
3370
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE,
|
|
3371
|
+
);
|
|
3372
|
+
|
|
3373
|
+
const sourceDigest = crypto.createHash("sha256");
|
|
3374
|
+
const chunk = Buffer.allocUnsafe(
|
|
3375
|
+
Math.max(
|
|
3376
|
+
1,
|
|
3377
|
+
Math.min(LINUX_ATTESTATION_HASH_CHUNK_BYTES, entryIdentity.bytes),
|
|
3378
|
+
),
|
|
3379
|
+
);
|
|
3380
|
+
let copied = 0;
|
|
3381
|
+
while (copied < entryIdentity.bytes) {
|
|
3382
|
+
const read = runtime.fs.readSync(
|
|
3383
|
+
entryMount.fd,
|
|
3384
|
+
chunk,
|
|
3385
|
+
0,
|
|
3386
|
+
Math.min(chunk.length, entryIdentity.bytes - copied),
|
|
3387
|
+
copied,
|
|
3388
|
+
);
|
|
3389
|
+
if (read <= 0) {
|
|
3390
|
+
throw snapshotError("source_ended_early");
|
|
3391
|
+
}
|
|
3392
|
+
sourceDigest.update(chunk.subarray(0, read));
|
|
3393
|
+
let written = 0;
|
|
3394
|
+
while (written < read) {
|
|
3395
|
+
const count = runtime.fs.writeSync(
|
|
3396
|
+
writerFd,
|
|
3397
|
+
chunk,
|
|
3398
|
+
written,
|
|
3399
|
+
read - written,
|
|
3400
|
+
copied + written,
|
|
3401
|
+
);
|
|
3402
|
+
if (count <= 0) {
|
|
3403
|
+
throw snapshotError("write_failed");
|
|
3404
|
+
}
|
|
3405
|
+
written += count;
|
|
3406
|
+
}
|
|
3407
|
+
copied += read;
|
|
3408
|
+
}
|
|
3409
|
+
const sourceAfter = runtime.fs.fstatSync(entryMount.fd);
|
|
3410
|
+
const sourceSha256 = sourceDigest.digest("hex");
|
|
3411
|
+
if (
|
|
3412
|
+
!linuxOpenStatMatches(sourceBefore, sourceAfter) ||
|
|
3413
|
+
(expectedSha256 !== null && sourceSha256 !== expectedSha256)
|
|
3414
|
+
) {
|
|
3415
|
+
throw snapshotError("source_changed");
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
runtime.fs.fsyncSync(writerFd);
|
|
3419
|
+
const snapshotBeforeRead = runtime.fs.fstatSync(writerFd);
|
|
3420
|
+
if (
|
|
3421
|
+
!snapshotBeforeRead.isFile() ||
|
|
3422
|
+
Number(snapshotBeforeRead.nlink) !== 0 ||
|
|
3423
|
+
Number(snapshotBeforeRead.size) !== entryIdentity.bytes
|
|
3424
|
+
) {
|
|
3425
|
+
throw snapshotError("identity_changed");
|
|
3426
|
+
}
|
|
3427
|
+
const snapshotSha256 = hashLinuxOpenFile(
|
|
3428
|
+
runtime,
|
|
3429
|
+
writerFd,
|
|
3430
|
+
entryIdentity.bytes,
|
|
3431
|
+
);
|
|
3432
|
+
const snapshotAfterRead = runtime.fs.fstatSync(writerFd);
|
|
3433
|
+
if (
|
|
3434
|
+
!linuxOpenStatMatches(snapshotBeforeRead, snapshotAfterRead) ||
|
|
3435
|
+
snapshotSha256 !== sourceSha256
|
|
3436
|
+
) {
|
|
3437
|
+
throw snapshotError("identity_changed");
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
runtime.fs.fchmodSync(writerFd, LINUX_ENTRY_SNAPSHOT_SOURCE_MODE);
|
|
3441
|
+
runtime.fs.fsyncSync(writerFd);
|
|
3442
|
+
const sealedBeforeRead = runtime.fs.fstatSync(writerFd);
|
|
3443
|
+
if (
|
|
3444
|
+
!sealedBeforeRead.isFile() ||
|
|
3445
|
+
Number(sealedBeforeRead.nlink) !== 0 ||
|
|
3446
|
+
Number(sealedBeforeRead.size) !== entryIdentity.bytes ||
|
|
3447
|
+
(Number(sealedBeforeRead.mode) & 0o777) !==
|
|
3448
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE
|
|
3449
|
+
) {
|
|
3450
|
+
throw snapshotError("readonly_mode_unattested");
|
|
3451
|
+
}
|
|
3452
|
+
const sealedSha256 = hashLinuxOpenFile(
|
|
3453
|
+
runtime,
|
|
3454
|
+
writerFd,
|
|
3455
|
+
entryIdentity.bytes,
|
|
3456
|
+
);
|
|
3457
|
+
const sealed = runtime.fs.fstatSync(writerFd);
|
|
3458
|
+
if (
|
|
3459
|
+
!linuxOpenStatMatches(sealedBeforeRead, sealed) ||
|
|
3460
|
+
sealedSha256 !== sourceSha256
|
|
3461
|
+
) {
|
|
3462
|
+
throw snapshotError("identity_changed");
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
const readerFlags =
|
|
3466
|
+
Number(constants.O_RDONLY) | Number(constants.O_NONBLOCK || 0);
|
|
3467
|
+
probeFd = runtime.fs.openSync(`/proc/self/fd/${writerFd}`, readerFlags);
|
|
3468
|
+
finalFd = runtime.fs.openSync(`/proc/self/fd/${writerFd}`, readerFlags);
|
|
3469
|
+
if (probeFd === finalFd) {
|
|
3470
|
+
throw snapshotError("readers_not_independent");
|
|
3471
|
+
}
|
|
3472
|
+
for (const fd of [probeFd, finalFd]) {
|
|
3473
|
+
const reader = runtime.fs.fstatSync(fd);
|
|
3474
|
+
if (!linuxOpenStatMatches(sealed, reader)) {
|
|
3475
|
+
throw snapshotError("reader_identity_changed");
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
const ownedWriterFd = writerFd;
|
|
3479
|
+
writerFd = undefined;
|
|
3480
|
+
runtime.fs.closeSync(ownedWriterFd);
|
|
3481
|
+
|
|
3482
|
+
const verifyClosedWriterSnapshot = runtime.fs.fstatSync(finalFd);
|
|
3483
|
+
if (!linuxOpenStatMatches(sealed, verifyClosedWriterSnapshot)) {
|
|
3484
|
+
throw snapshotError("reader_identity_changed");
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
const attestation = Object.freeze({
|
|
3488
|
+
scope: snapshotContract.scope,
|
|
3489
|
+
mechanism: LINUX_ENTRY_SNAPSHOT_MECHANISM,
|
|
3490
|
+
sha256: sourceSha256,
|
|
3491
|
+
bytes: entryIdentity.bytes,
|
|
3492
|
+
sourceFileId: Object.freeze({
|
|
3493
|
+
dev: String(entryIdentity.fileId.dev),
|
|
3494
|
+
ino: String(entryIdentity.fileId.ino),
|
|
3495
|
+
}),
|
|
3496
|
+
sourceMtimeMs: Number(entryIdentity.mtimeMs),
|
|
3497
|
+
sourceFileMode: sourceFileMode.toString(8).padStart(4, "0"),
|
|
3498
|
+
sourceMode: LINUX_ENTRY_SNAPSHOT_SOURCE_MODE.toString(8).padStart(4, "0"),
|
|
3499
|
+
targetMode: snapshotContract.targetMode,
|
|
3500
|
+
});
|
|
3501
|
+
const descriptor = (fd) => {
|
|
3502
|
+
const stat = runtime.fs.fstatSync(fd);
|
|
3503
|
+
return {
|
|
3504
|
+
...entryMount,
|
|
3505
|
+
fd,
|
|
3506
|
+
fileId: {
|
|
3507
|
+
dev: String(stat.dev),
|
|
3508
|
+
ino: String(stat.ino),
|
|
3509
|
+
},
|
|
3510
|
+
bytes: Number(stat.size),
|
|
3511
|
+
mtimeMs: Number(stat.mtimeMs),
|
|
3512
|
+
mountMode: "ro-bind-data",
|
|
3513
|
+
mountPermissions: snapshotContract.targetMode,
|
|
3514
|
+
contentSnapshot: attestation,
|
|
3515
|
+
snapshotIdentity: Object.freeze({
|
|
3516
|
+
sha256: sourceSha256,
|
|
3517
|
+
bytes: entryIdentity.bytes,
|
|
3518
|
+
fileId: Object.freeze({
|
|
3519
|
+
dev: String(stat.dev),
|
|
3520
|
+
ino: String(stat.ino),
|
|
3521
|
+
}),
|
|
3522
|
+
mtimeMs: Number(stat.mtimeMs),
|
|
3523
|
+
}),
|
|
3524
|
+
};
|
|
3525
|
+
};
|
|
3526
|
+
const result = {
|
|
3527
|
+
attestation,
|
|
3528
|
+
probeMount: descriptor(probeFd),
|
|
3529
|
+
finalMount: descriptor(finalFd),
|
|
3530
|
+
};
|
|
3531
|
+
probeFd = undefined;
|
|
3532
|
+
finalFd = undefined;
|
|
3533
|
+
return result;
|
|
3534
|
+
} catch (error) {
|
|
3535
|
+
closeLinuxPinnedMounts(
|
|
3536
|
+
runtime,
|
|
3537
|
+
[probeFd, finalFd, writerFd]
|
|
3538
|
+
.filter(Number.isInteger)
|
|
3539
|
+
.map((fd) => ({ fd })),
|
|
3540
|
+
);
|
|
3541
|
+
throw error;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
function createLinuxEntrySnapshot(
|
|
3546
|
+
runtime,
|
|
3547
|
+
entryMount,
|
|
3548
|
+
entryIdentity,
|
|
3549
|
+
targetRuntime,
|
|
3550
|
+
) {
|
|
3551
|
+
return createLinuxRegularFileSnapshot(
|
|
3552
|
+
runtime,
|
|
3553
|
+
entryMount,
|
|
3554
|
+
entryIdentity,
|
|
3555
|
+
linuxEntrySnapshotContract(targetRuntime),
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
function attestLinuxRegularFileSnapshot(
|
|
3560
|
+
runtime,
|
|
3561
|
+
mount,
|
|
3562
|
+
attestation,
|
|
3563
|
+
snapshotContract,
|
|
3564
|
+
) {
|
|
3565
|
+
const errorPrefix = snapshotContract.errorPrefix;
|
|
3566
|
+
const before = runtime.fs.fstatSync(mount?.fd);
|
|
3567
|
+
if (
|
|
3568
|
+
attestation?.scope !== snapshotContract.scope ||
|
|
3569
|
+
attestation?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
|
|
3570
|
+
attestation?.sourceMode !==
|
|
3571
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE.toString(8).padStart(4, "0") ||
|
|
3572
|
+
typeof attestation?.sourceFileMode !== "string" ||
|
|
3573
|
+
!/^[0-7]{4}$/.test(attestation.sourceFileMode) ||
|
|
3574
|
+
attestation?.targetMode !== snapshotContract.targetMode ||
|
|
3575
|
+
mount?.mountMode !== "ro-bind-data" ||
|
|
3576
|
+
mount?.mountPermissions !== snapshotContract.targetMode ||
|
|
3577
|
+
mount?.contentSnapshot !== attestation ||
|
|
3578
|
+
!before.isFile() ||
|
|
3579
|
+
Number(before.nlink) !== 0 ||
|
|
3580
|
+
(Number(before.mode) & 0o777) !== LINUX_ENTRY_SNAPSHOT_SOURCE_MODE ||
|
|
3581
|
+
String(before.dev) !== String(mount?.snapshotIdentity?.fileId?.dev) ||
|
|
3582
|
+
String(before.ino) !== String(mount?.snapshotIdentity?.fileId?.ino) ||
|
|
3583
|
+
Number(before.size) !== Number(attestation?.bytes) ||
|
|
3584
|
+
Number(before.size) !== Number(mount?.snapshotIdentity?.bytes) ||
|
|
3585
|
+
Number(before.mtimeMs) !== Number(mount?.snapshotIdentity?.mtimeMs)
|
|
3586
|
+
) {
|
|
3587
|
+
throw new Error(`${errorPrefix}_identity_changed`);
|
|
3588
|
+
}
|
|
3589
|
+
const sha256 = hashLinuxOpenFile(runtime, mount.fd, Number(before.size));
|
|
3590
|
+
const after = runtime.fs.fstatSync(mount.fd);
|
|
3591
|
+
if (
|
|
3592
|
+
!linuxOpenStatMatches(before, after) ||
|
|
3593
|
+
sha256 !== attestation?.sha256 ||
|
|
3594
|
+
sha256 !== mount.snapshotIdentity.sha256
|
|
3595
|
+
) {
|
|
3596
|
+
throw new Error(`${errorPrefix}_identity_changed`);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
function attestLinuxEntrySnapshot(runtime, mount, attestation, targetRuntime) {
|
|
3601
|
+
attestLinuxRegularFileSnapshot(
|
|
3602
|
+
runtime,
|
|
3603
|
+
mount,
|
|
3604
|
+
attestation,
|
|
3605
|
+
linuxEntrySnapshotContract(targetRuntime),
|
|
3606
|
+
);
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
|
|
3610
|
+
const members = [];
|
|
3611
|
+
const destinations = new Set();
|
|
3612
|
+
for (const mount of mounts || []) {
|
|
3613
|
+
if (!linuxPathWithin("/opt/chainless/plugin", mount?.destination)) continue;
|
|
3614
|
+
if (
|
|
3615
|
+
path.posix.normalize(mount.destination) !== mount.destination ||
|
|
3616
|
+
destinations.has(mount.destination)
|
|
3617
|
+
) {
|
|
3618
|
+
throw new Error("node_plugin_tree_snapshot_destination_invalid");
|
|
3619
|
+
}
|
|
3620
|
+
destinations.add(mount.destination);
|
|
3621
|
+
const snapshot = mount.contentSnapshot;
|
|
3622
|
+
const entry = mount.destination === entryDestination;
|
|
3623
|
+
const expectedScope = entry
|
|
3624
|
+
? LINUX_NODE_ENTRY_SNAPSHOT_SCOPE
|
|
3625
|
+
: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE;
|
|
3626
|
+
const sourceMode = Number.parseInt(snapshot?.sourceFileMode, 8);
|
|
3627
|
+
const expectedTargetMode = entry
|
|
3628
|
+
? LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE
|
|
3629
|
+
: linuxNodePluginTreeFileSnapshotContract(sourceMode).targetMode;
|
|
3630
|
+
if (
|
|
3631
|
+
snapshot?.scope !== expectedScope ||
|
|
3632
|
+
snapshot?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
|
|
3633
|
+
typeof snapshot?.sha256 !== "string" ||
|
|
3634
|
+
!/^[a-f0-9]{64}$/.test(snapshot.sha256) ||
|
|
3635
|
+
typeof snapshot?.sourceFileId !== "object" ||
|
|
3636
|
+
typeof snapshot.sourceFileId?.dev !== "string" ||
|
|
3637
|
+
!/^\d+$/.test(snapshot.sourceFileId.dev) ||
|
|
3638
|
+
typeof snapshot.sourceFileId?.ino !== "string" ||
|
|
3639
|
+
!/^\d+$/.test(snapshot.sourceFileId.ino) ||
|
|
3640
|
+
!Number.isFinite(snapshot?.sourceMtimeMs) ||
|
|
3641
|
+
!Number.isSafeInteger(snapshot?.bytes) ||
|
|
3642
|
+
snapshot.bytes < 0 ||
|
|
3643
|
+
snapshot.bytes > LINUX_ATTESTED_FILE_MAX_BYTES ||
|
|
3644
|
+
typeof snapshot?.sourceFileMode !== "string" ||
|
|
3645
|
+
!/^[0-7]{4}$/.test(snapshot.sourceFileMode) ||
|
|
3646
|
+
snapshot.sourceMode !==
|
|
3647
|
+
LINUX_ENTRY_SNAPSHOT_SOURCE_MODE.toString(8).padStart(4, "0") ||
|
|
3648
|
+
snapshot.targetMode !== expectedTargetMode ||
|
|
3649
|
+
mount.mountMode !== "ro-bind-data" ||
|
|
3650
|
+
mount.mountPermissions !== expectedTargetMode
|
|
3651
|
+
) {
|
|
3652
|
+
throw new Error("node_plugin_tree_snapshot_member_invalid");
|
|
3653
|
+
}
|
|
3654
|
+
members.push({
|
|
3655
|
+
destination: mount.destination,
|
|
3656
|
+
sourceFileId: snapshot.sourceFileId,
|
|
3657
|
+
sourceMtimeMs: snapshot.sourceMtimeMs,
|
|
3658
|
+
sourceFileMode: snapshot.sourceFileMode,
|
|
3659
|
+
sha256: snapshot.sha256,
|
|
3660
|
+
bytes: snapshot.bytes,
|
|
3661
|
+
sourceMode: snapshot.sourceMode,
|
|
3662
|
+
targetMode: snapshot.targetMode,
|
|
3663
|
+
scope: snapshot.scope,
|
|
3664
|
+
mechanism: snapshot.mechanism,
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
3667
|
+
members.sort((left, right) =>
|
|
3668
|
+
left.destination < right.destination
|
|
3669
|
+
? -1
|
|
3670
|
+
: left.destination > right.destination
|
|
3671
|
+
? 1
|
|
3672
|
+
: 0,
|
|
3673
|
+
);
|
|
3674
|
+
if (
|
|
3675
|
+
members.length < 1 ||
|
|
3676
|
+
members.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES ||
|
|
3677
|
+
!members.some((member) => member.destination === entryDestination)
|
|
3678
|
+
) {
|
|
3679
|
+
throw new Error("node_plugin_tree_snapshot_file_count_invalid");
|
|
3680
|
+
}
|
|
3681
|
+
const bytes = members.reduce((total, member) => total + member.bytes, 0);
|
|
3682
|
+
if (
|
|
3683
|
+
!Number.isSafeInteger(bytes) ||
|
|
3684
|
+
bytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
|
|
3685
|
+
) {
|
|
3686
|
+
throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
|
|
3687
|
+
}
|
|
3688
|
+
const digest = crypto
|
|
3689
|
+
.createHash("sha256")
|
|
3690
|
+
.update(
|
|
3691
|
+
JSON.stringify({
|
|
3692
|
+
version: 1,
|
|
3693
|
+
scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
|
|
3694
|
+
consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
|
|
3695
|
+
members,
|
|
3696
|
+
}),
|
|
3697
|
+
)
|
|
3698
|
+
.digest("hex");
|
|
3699
|
+
return Object.freeze({
|
|
3700
|
+
scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
|
|
3701
|
+
mechanism: LINUX_ENTRY_SNAPSHOT_MECHANISM,
|
|
3702
|
+
files: members.length,
|
|
3703
|
+
bytes,
|
|
3704
|
+
digest,
|
|
3705
|
+
consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
|
|
3706
|
+
contractBound: false,
|
|
3707
|
+
atomic: false,
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
function attestLinuxNodePluginTreeSnapshot(
|
|
3712
|
+
runtime,
|
|
3713
|
+
mounts,
|
|
3714
|
+
entryDestination,
|
|
3715
|
+
expected,
|
|
3716
|
+
) {
|
|
3717
|
+
for (const mount of mounts || []) {
|
|
3718
|
+
if (!linuxPathWithin("/opt/chainless/plugin", mount?.destination)) continue;
|
|
3719
|
+
const snapshot = mount.contentSnapshot;
|
|
3720
|
+
const contract =
|
|
3721
|
+
mount.destination === entryDestination
|
|
3722
|
+
? linuxEntrySnapshotContract("node")
|
|
3723
|
+
: linuxNodePluginTreeFileSnapshotContract(
|
|
3724
|
+
Number.parseInt(snapshot?.sourceFileMode, 8),
|
|
3725
|
+
);
|
|
3726
|
+
attestLinuxRegularFileSnapshot(runtime, mount, snapshot, contract);
|
|
3727
|
+
}
|
|
3728
|
+
const actual = buildLinuxNodePluginTreeSnapshotAttestation(
|
|
3729
|
+
mounts,
|
|
3730
|
+
entryDestination,
|
|
3731
|
+
);
|
|
3732
|
+
for (const field of [
|
|
3733
|
+
"scope",
|
|
3734
|
+
"mechanism",
|
|
3735
|
+
"files",
|
|
3736
|
+
"bytes",
|
|
3737
|
+
"digest",
|
|
3738
|
+
"consistency",
|
|
3739
|
+
"contractBound",
|
|
3740
|
+
"atomic",
|
|
3741
|
+
]) {
|
|
3742
|
+
if (actual[field] !== expected?.[field]) {
|
|
3743
|
+
throw new Error("node_plugin_tree_snapshot_identity_changed");
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
function validLinuxRuntimePageSize(value) {
|
|
3749
|
+
if (
|
|
3750
|
+
!Number.isSafeInteger(value) ||
|
|
3751
|
+
value < LINUX_MIN_RUNTIME_PAGE_BYTES ||
|
|
3752
|
+
value > LINUX_MAX_RUNTIME_PAGE_BYTES
|
|
3753
|
+
) {
|
|
3754
|
+
return false;
|
|
3755
|
+
}
|
|
3756
|
+
const size = BigInt(value);
|
|
3757
|
+
return (size & (size - 1n)) === 0n;
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
function readLinuxPageSizeFromAuxv(runtimeFs) {
|
|
3761
|
+
// AT_PAGESZ is emitted by the kernel for this exact process and matches the
|
|
3762
|
+
// page granularity used by the ELF loader. Avoid filesystem block sizes or
|
|
3763
|
+
// an external getconf binary, neither of which is the loader contract.
|
|
3764
|
+
const raw = runtimeFs.readFileSync("/proc/self/auxv");
|
|
3765
|
+
const auxv = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
3766
|
+
if (
|
|
3767
|
+
auxv.length < LINUX_AUXV_ENTRY_BYTES ||
|
|
3768
|
+
auxv.length > LINUX_AUXV_MAX_BYTES ||
|
|
3769
|
+
auxv.length % LINUX_AUXV_ENTRY_BYTES !== 0
|
|
3770
|
+
) {
|
|
3771
|
+
throw new Error("linux_runtime_auxv_invalid");
|
|
3772
|
+
}
|
|
3773
|
+
let pageSize = null;
|
|
3774
|
+
let terminated = false;
|
|
3775
|
+
for (let offset = 0; offset < auxv.length; offset += LINUX_AUXV_ENTRY_BYTES) {
|
|
3776
|
+
const tag = auxv.readBigUInt64LE(offset);
|
|
3777
|
+
const value = auxv.readBigUInt64LE(offset + 8);
|
|
3778
|
+
if (tag === 0n) {
|
|
3779
|
+
terminated = true;
|
|
3780
|
+
break;
|
|
3781
|
+
}
|
|
3782
|
+
if (tag !== LINUX_AUXV_PAGE_SIZE) continue;
|
|
3783
|
+
if (pageSize !== null || value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
3784
|
+
throw new Error("linux_runtime_page_size_ambiguous");
|
|
3785
|
+
}
|
|
3786
|
+
pageSize = Number(value);
|
|
3787
|
+
}
|
|
3788
|
+
if (!terminated || !validLinuxRuntimePageSize(pageSize)) {
|
|
3789
|
+
throw new Error("linux_runtime_page_size_unattested");
|
|
3790
|
+
}
|
|
3791
|
+
return pageSize;
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
function linuxElfRuntimePageSize(runtime) {
|
|
3795
|
+
const pageSize =
|
|
3796
|
+
typeof runtime.linuxPageSize === "function"
|
|
3797
|
+
? runtime.linuxPageSize()
|
|
3798
|
+
: readLinuxPageSizeFromAuxv(runtime.fs);
|
|
3799
|
+
if (!validLinuxRuntimePageSize(pageSize)) {
|
|
3800
|
+
throw new Error("linux_runtime_page_size_unattested");
|
|
3801
|
+
}
|
|
3802
|
+
return BigInt(pageSize);
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
function linuxElfAlignDown(value, alignment) {
|
|
3806
|
+
return value - (value % alignment);
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
function linuxElfAlignUp(value, alignment) {
|
|
3810
|
+
const remainder = value % alignment;
|
|
3811
|
+
return remainder === 0n ? value : value + alignment - remainder;
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
function attestLinuxElfLoadMappings(loadSegments, pageSize) {
|
|
3815
|
+
// Linux maps later PT_LOAD entries with MAP_FIXED. Even disjoint byte ranges
|
|
3816
|
+
// can therefore replace one another after the loader rounds them to pages.
|
|
3817
|
+
// A conservative no-overlap contract keeps our file-offset parser and the
|
|
3818
|
+
// in-memory dynamic-loader view identical.
|
|
3819
|
+
const ranges = [];
|
|
3820
|
+
for (const load of loadSegments) {
|
|
3821
|
+
if (
|
|
3822
|
+
load.fileOffset % pageSize !== load.virtualAddress % pageSize ||
|
|
3823
|
+
(load.alignment > 1n &&
|
|
3824
|
+
((load.alignment & (load.alignment - 1n)) !== 0n ||
|
|
3825
|
+
load.fileOffset % load.alignment !==
|
|
3826
|
+
load.virtualAddress % load.alignment))
|
|
3827
|
+
) {
|
|
3828
|
+
throw new Error("native_entry_load_segment_alignment_invalid");
|
|
3829
|
+
}
|
|
3830
|
+
if (
|
|
3831
|
+
load.virtualAddress > LINUX_ELF_MAX_UINT64 - load.memorySize ||
|
|
3832
|
+
load.virtualAddress + load.memorySize >
|
|
3833
|
+
LINUX_ELF_MAX_UINT64 - (pageSize - 1n)
|
|
3834
|
+
) {
|
|
3835
|
+
throw new Error("native_entry_load_segment_virtual_range_overflow");
|
|
3836
|
+
}
|
|
3837
|
+
if (load.memorySize === 0n) continue;
|
|
3838
|
+
ranges.push({
|
|
3839
|
+
start: linuxElfAlignDown(load.virtualAddress, pageSize),
|
|
3840
|
+
end: linuxElfAlignUp(load.virtualAddress + load.memorySize, pageSize),
|
|
3841
|
+
});
|
|
3842
|
+
}
|
|
3843
|
+
ranges.sort((left, right) =>
|
|
3844
|
+
left.start < right.start ? -1 : left.start > right.start ? 1 : 0,
|
|
3845
|
+
);
|
|
3846
|
+
for (let index = 1; index < ranges.length; index += 1) {
|
|
3847
|
+
if (ranges[index].start < ranges[index - 1].end) {
|
|
3848
|
+
throw new Error("native_entry_load_segment_page_overlap");
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
function linuxElfVirtualRangeFileMappings(loadSegments, address, bytes) {
|
|
3854
|
+
return loadSegments
|
|
3855
|
+
.filter((load) => {
|
|
3856
|
+
if (address < load.virtualAddress) return false;
|
|
3857
|
+
const delta = address - load.virtualAddress;
|
|
3858
|
+
return (
|
|
3859
|
+
delta <= load.fileSize &&
|
|
3860
|
+
bytes <= load.fileSize - delta &&
|
|
3861
|
+
delta <= load.memorySize &&
|
|
3862
|
+
bytes <= load.memorySize - delta
|
|
3863
|
+
);
|
|
3864
|
+
})
|
|
3865
|
+
.map((load) => ({
|
|
3866
|
+
load,
|
|
3867
|
+
fileOffset: load.fileOffset + (address - load.virtualAddress),
|
|
3868
|
+
}));
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
function readLinuxElfString(table, offset, label) {
|
|
3872
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset >= table.length) {
|
|
3873
|
+
throw new Error(`native_entry_${label}_string_out_of_bounds`);
|
|
3874
|
+
}
|
|
3875
|
+
const end = table.indexOf(0, offset);
|
|
3876
|
+
if (end < 0) {
|
|
3877
|
+
throw new Error(`native_entry_${label}_string_unterminated`);
|
|
3878
|
+
}
|
|
3879
|
+
const bytes = table.subarray(offset, end);
|
|
3880
|
+
if (bytes.length < 1 || bytes.some((value) => value < 0x21 || value > 0x7e)) {
|
|
3881
|
+
throw new Error(`native_entry_${label}_string_invalid`);
|
|
3882
|
+
}
|
|
3883
|
+
return bytes.toString("ascii");
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
function linuxSystemDynamicPath(value) {
|
|
3887
|
+
return (
|
|
3888
|
+
typeof value === "string" &&
|
|
3889
|
+
path.posix.isAbsolute(value) &&
|
|
3890
|
+
path.posix.normalize(value) === value &&
|
|
3891
|
+
!value.includes("\0") &&
|
|
3892
|
+
["/lib/", "/lib64/", "/usr/lib/", "/usr/lib64/"].some((prefix) =>
|
|
3893
|
+
value.startsWith(prefix),
|
|
3894
|
+
)
|
|
3895
|
+
);
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3076
3898
|
/**
|
|
3077
|
-
* Classify one already-pinned plugin entry without invoking it or
|
|
3078
|
-
* inspection utility.
|
|
3079
|
-
*
|
|
3080
|
-
*
|
|
3081
|
-
*
|
|
3899
|
+
* Classify one already-pinned plugin entry without invoking it or passing it
|
|
3900
|
+
* to a host inspection utility. Besides conventional fully static ET_EXEC and
|
|
3901
|
+
* static-PIE-shaped ET_DYN images, the parser can describe a narrow dynamic
|
|
3902
|
+
* executable. The caller must separately prove that its PT_INTERP and every
|
|
3903
|
+
* direct DT_NEEDED member are present in a pinned, attested system-file set.
|
|
3082
3904
|
*/
|
|
3083
|
-
function
|
|
3905
|
+
function inspectLinuxNativeElf(runtime, fd, identity) {
|
|
3084
3906
|
const expectedMachine = LINUX_ELF_MACHINES[runtime.arch];
|
|
3085
3907
|
if (!expectedMachine) {
|
|
3086
3908
|
throw new Error(`unsupported_elf_architecture:${String(runtime.arch)}`);
|
|
3087
3909
|
}
|
|
3910
|
+
const runtimePageSize = linuxElfRuntimePageSize(runtime);
|
|
3088
3911
|
const before = runtime.fs.fstatSync(fd);
|
|
3089
3912
|
if (
|
|
3090
3913
|
!before.isFile() ||
|
|
@@ -3151,6 +3974,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3151
3974
|
let nonExecutableStack = false;
|
|
3152
3975
|
const loadSegments = [];
|
|
3153
3976
|
const dynamicSegments = [];
|
|
3977
|
+
const interpreterSegments = [];
|
|
3154
3978
|
for (let index = 0; index < programHeaderCount; index += 1) {
|
|
3155
3979
|
const offset =
|
|
3156
3980
|
Number(programHeaderOffset) + index * LINUX_ELF64_PROGRAM_HEADER_BYTES;
|
|
@@ -3166,6 +3990,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3166
3990
|
const virtualAddress = program.readBigUInt64LE(16);
|
|
3167
3991
|
const fileSize = program.readBigUInt64LE(32);
|
|
3168
3992
|
const memorySize = program.readBigUInt64LE(40);
|
|
3993
|
+
const alignment = program.readBigUInt64LE(48);
|
|
3169
3994
|
if (fileOffset > fileBytes || fileSize > fileBytes - fileOffset) {
|
|
3170
3995
|
throw new Error("native_entry_segment_out_of_bounds");
|
|
3171
3996
|
}
|
|
@@ -3173,18 +3998,21 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3173
3998
|
throw new Error("native_entry_segment_out_of_bounds");
|
|
3174
3999
|
}
|
|
3175
4000
|
if (type === LINUX_ELF_PROGRAM_INTERP) {
|
|
3176
|
-
|
|
4001
|
+
interpreterSegments.push({
|
|
4002
|
+
flags,
|
|
4003
|
+
fileOffset,
|
|
4004
|
+
fileSize,
|
|
4005
|
+
memorySize,
|
|
4006
|
+
});
|
|
3177
4007
|
}
|
|
3178
4008
|
if (type === LINUX_ELF_PROGRAM_DYNAMIC) {
|
|
3179
|
-
if (elfType === LINUX_ELF_TYPE_EXEC) {
|
|
3180
|
-
throw new Error("native_entry_dynamic_elf_unsupported");
|
|
3181
|
-
}
|
|
3182
4009
|
dynamicSegments.push({
|
|
3183
4010
|
flags,
|
|
3184
4011
|
fileOffset,
|
|
3185
4012
|
virtualAddress,
|
|
3186
4013
|
fileSize,
|
|
3187
4014
|
memorySize,
|
|
4015
|
+
alignment,
|
|
3188
4016
|
});
|
|
3189
4017
|
}
|
|
3190
4018
|
if (type === LINUX_ELF_PROGRAM_GNU_STACK) {
|
|
@@ -3200,6 +4028,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3200
4028
|
virtualAddress,
|
|
3201
4029
|
fileSize,
|
|
3202
4030
|
memorySize,
|
|
4031
|
+
alignment,
|
|
3203
4032
|
});
|
|
3204
4033
|
}
|
|
3205
4034
|
if (type === LINUX_ELF_PROGRAM_LOAD && (flags & 0x1) !== 0) {
|
|
@@ -3215,6 +4044,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3215
4044
|
}
|
|
3216
4045
|
}
|
|
3217
4046
|
}
|
|
4047
|
+
attestLinuxElfLoadMappings(loadSegments, runtimePageSize);
|
|
3218
4048
|
if (!executableLoad || !entryInExecutableLoad) {
|
|
3219
4049
|
throw new Error("native_entry_has_no_executable_entry_segment");
|
|
3220
4050
|
}
|
|
@@ -3222,23 +4052,65 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3222
4052
|
throw new Error("native_entry_nonexecutable_stack_unattested");
|
|
3223
4053
|
}
|
|
3224
4054
|
|
|
3225
|
-
if (
|
|
4055
|
+
if (interpreterSegments.length > 1) {
|
|
4056
|
+
throw new Error("native_entry_interpreter_ambiguous");
|
|
4057
|
+
}
|
|
4058
|
+
let interpreter = null;
|
|
4059
|
+
if (interpreterSegments.length === 1) {
|
|
4060
|
+
const segment = interpreterSegments[0];
|
|
4061
|
+
if (
|
|
4062
|
+
(segment.flags & 0x3) !== 0 ||
|
|
4063
|
+
segment.fileSize < 2n ||
|
|
4064
|
+
segment.fileSize > BigInt(LINUX_ELF_MAX_INTERPRETER_BYTES) ||
|
|
4065
|
+
segment.fileSize > segment.memorySize
|
|
4066
|
+
) {
|
|
4067
|
+
throw new Error("native_entry_interpreter_invalid");
|
|
4068
|
+
}
|
|
4069
|
+
const contents = readLinuxFdExactly(
|
|
4070
|
+
runtime,
|
|
4071
|
+
fd,
|
|
4072
|
+
Number(segment.fileSize),
|
|
4073
|
+
Number(segment.fileOffset),
|
|
4074
|
+
);
|
|
4075
|
+
if (
|
|
4076
|
+
contents[contents.length - 1] !== 0 ||
|
|
4077
|
+
contents.indexOf(0) !== contents.length - 1
|
|
4078
|
+
) {
|
|
4079
|
+
throw new Error("native_entry_interpreter_unterminated");
|
|
4080
|
+
}
|
|
4081
|
+
interpreter = readLinuxElfString(contents, 0, "interpreter");
|
|
4082
|
+
if (!linuxSystemDynamicPath(interpreter)) {
|
|
4083
|
+
throw new Error("native_entry_interpreter_outside_system_roots");
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
const staticPie = elfType === LINUX_ELF_TYPE_DYN && interpreter === null;
|
|
4088
|
+
const dynamicExecutable = interpreter !== null;
|
|
4089
|
+
let dynamicMetadata = null;
|
|
4090
|
+
if (elfType === LINUX_ELF_TYPE_EXEC && !dynamicExecutable) {
|
|
4091
|
+
if (dynamicSegments.length > 0) {
|
|
4092
|
+
throw new Error("native_entry_dynamic_elf_unsupported");
|
|
4093
|
+
}
|
|
4094
|
+
} else {
|
|
4095
|
+
const prefix = staticPie
|
|
4096
|
+
? "native_entry_static_pie"
|
|
4097
|
+
: "native_entry_dynamic";
|
|
3226
4098
|
if (dynamicSegments.length === 0) {
|
|
3227
|
-
throw new Error(
|
|
4099
|
+
throw new Error(`${prefix}_dynamic_segment_missing`);
|
|
3228
4100
|
}
|
|
3229
4101
|
if (dynamicSegments.length !== 1) {
|
|
3230
|
-
throw new Error(
|
|
4102
|
+
throw new Error(`${prefix}_dynamic_segment_ambiguous`);
|
|
3231
4103
|
}
|
|
3232
4104
|
const dynamic = dynamicSegments[0];
|
|
3233
4105
|
if ((dynamic.flags & 0x1) !== 0) {
|
|
3234
|
-
throw new Error(
|
|
4106
|
+
throw new Error(`${prefix}_dynamic_segment_executable`);
|
|
3235
4107
|
}
|
|
3236
4108
|
if (
|
|
3237
4109
|
dynamic.fileSize < BigInt(LINUX_ELF64_DYNAMIC_ENTRY_BYTES) ||
|
|
3238
4110
|
dynamic.fileSize % BigInt(LINUX_ELF64_DYNAMIC_ENTRY_BYTES) !== 0n ||
|
|
3239
4111
|
dynamic.fileSize > dynamic.memorySize
|
|
3240
4112
|
) {
|
|
3241
|
-
throw new Error(
|
|
4113
|
+
throw new Error(`${prefix}_dynamic_segment_invalid`);
|
|
3242
4114
|
}
|
|
3243
4115
|
const dynamicEntries =
|
|
3244
4116
|
dynamic.fileSize / BigInt(LINUX_ELF64_DYNAMIC_ENTRY_BYTES);
|
|
@@ -3246,9 +4118,9 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3246
4118
|
dynamicEntries < 1n ||
|
|
3247
4119
|
dynamicEntries > BigInt(LINUX_ELF_MAX_DYNAMIC_ENTRIES)
|
|
3248
4120
|
) {
|
|
3249
|
-
throw new Error(
|
|
4121
|
+
throw new Error(`${prefix}_dynamic_table_too_large`);
|
|
3250
4122
|
}
|
|
3251
|
-
const
|
|
4123
|
+
const containingLoads = loadSegments.filter((load) => {
|
|
3252
4124
|
if (
|
|
3253
4125
|
dynamic.fileOffset < load.fileOffset ||
|
|
3254
4126
|
dynamic.virtualAddress < load.virtualAddress
|
|
@@ -3265,8 +4137,22 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3265
4137
|
dynamic.memorySize <= load.memorySize - memoryDelta
|
|
3266
4138
|
);
|
|
3267
4139
|
});
|
|
3268
|
-
if (
|
|
3269
|
-
throw new Error(
|
|
4140
|
+
if (containingLoads.length === 0) {
|
|
4141
|
+
throw new Error(`${prefix}_dynamic_segment_unmapped`);
|
|
4142
|
+
}
|
|
4143
|
+
if (containingLoads.length !== 1) {
|
|
4144
|
+
throw new Error(`${prefix}_dynamic_segment_mapping_ambiguous`);
|
|
4145
|
+
}
|
|
4146
|
+
const dynamicLoaderMappings = linuxElfVirtualRangeFileMappings(
|
|
4147
|
+
loadSegments,
|
|
4148
|
+
dynamic.virtualAddress,
|
|
4149
|
+
dynamic.fileSize,
|
|
4150
|
+
);
|
|
4151
|
+
if (
|
|
4152
|
+
dynamicLoaderMappings.length !== 1 ||
|
|
4153
|
+
dynamicLoaderMappings[0].fileOffset !== dynamic.fileOffset
|
|
4154
|
+
) {
|
|
4155
|
+
throw new Error(`${prefix}_dynamic_segment_loader_view_mismatch`);
|
|
3270
4156
|
}
|
|
3271
4157
|
|
|
3272
4158
|
const dynamicTable = readLinuxFdExactly(
|
|
@@ -3275,8 +4161,21 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3275
4161
|
Number(dynamic.fileSize),
|
|
3276
4162
|
Number(dynamic.fileOffset),
|
|
3277
4163
|
);
|
|
4164
|
+
const neededOffsets = [];
|
|
4165
|
+
const forbiddenDependencyTags = new Set([
|
|
4166
|
+
LINUX_ELF_DYNAMIC_RPATH,
|
|
4167
|
+
LINUX_ELF_DYNAMIC_RUNPATH,
|
|
4168
|
+
LINUX_ELF_DYNAMIC_CONFIG,
|
|
4169
|
+
LINUX_ELF_DYNAMIC_DEPAUDIT,
|
|
4170
|
+
LINUX_ELF_DYNAMIC_AUDIT,
|
|
4171
|
+
LINUX_ELF_DYNAMIC_AUXILIARY,
|
|
4172
|
+
LINUX_ELF_DYNAMIC_FILTER,
|
|
4173
|
+
]);
|
|
3278
4174
|
let terminated = false;
|
|
4175
|
+
let flags = null;
|
|
3279
4176
|
let flags1 = null;
|
|
4177
|
+
let stringTableAddress = null;
|
|
4178
|
+
let stringTableBytes = null;
|
|
3280
4179
|
for (
|
|
3281
4180
|
let offset = 0;
|
|
3282
4181
|
offset < dynamicTable.length;
|
|
@@ -3288,21 +4187,113 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3288
4187
|
terminated = true;
|
|
3289
4188
|
break;
|
|
3290
4189
|
}
|
|
3291
|
-
if (
|
|
3292
|
-
|
|
4190
|
+
if (
|
|
4191
|
+
tag === LINUX_ELF_DYNAMIC_TEXTREL ||
|
|
4192
|
+
forbiddenDependencyTags.has(tag)
|
|
4193
|
+
) {
|
|
4194
|
+
throw new Error(`${prefix}_loader_directive_unsupported`);
|
|
3293
4195
|
}
|
|
3294
|
-
if (tag ===
|
|
3295
|
-
|
|
3296
|
-
|
|
4196
|
+
if (tag === LINUX_ELF_DYNAMIC_NEEDED) {
|
|
4197
|
+
neededOffsets.push(value);
|
|
4198
|
+
if (neededOffsets.length > LINUX_ELF_MAX_NEEDED_ENTRIES) {
|
|
4199
|
+
throw new Error(`${prefix}_dependency_count_exceeded`);
|
|
4200
|
+
}
|
|
4201
|
+
} else if (tag === LINUX_ELF_DYNAMIC_STRTAB) {
|
|
4202
|
+
if (stringTableAddress !== null) {
|
|
4203
|
+
throw new Error(`${prefix}_string_table_ambiguous`);
|
|
4204
|
+
}
|
|
4205
|
+
stringTableAddress = value;
|
|
4206
|
+
} else if (tag === LINUX_ELF_DYNAMIC_STRSZ) {
|
|
4207
|
+
if (stringTableBytes !== null) {
|
|
4208
|
+
throw new Error(`${prefix}_string_table_ambiguous`);
|
|
4209
|
+
}
|
|
4210
|
+
stringTableBytes = value;
|
|
4211
|
+
} else if (tag === LINUX_ELF_DYNAMIC_FLAGS) {
|
|
4212
|
+
if (flags !== null) {
|
|
4213
|
+
throw new Error(`${prefix}_flags_ambiguous`);
|
|
4214
|
+
}
|
|
4215
|
+
flags = value;
|
|
4216
|
+
} else if (tag === LINUX_ELF_DYNAMIC_FLAGS_1) {
|
|
4217
|
+
if (flags1 !== null) {
|
|
4218
|
+
throw new Error(`${prefix}_flags_ambiguous`);
|
|
3297
4219
|
}
|
|
3298
4220
|
flags1 = value;
|
|
3299
4221
|
}
|
|
3300
4222
|
}
|
|
3301
4223
|
if (!terminated) {
|
|
3302
|
-
throw new Error(
|
|
4224
|
+
throw new Error(`${prefix}_dynamic_table_unterminated`);
|
|
3303
4225
|
}
|
|
3304
|
-
if (
|
|
3305
|
-
throw new Error(
|
|
4226
|
+
if (flags !== null && (flags & LINUX_ELF_DYNAMIC_FLAG_TEXTREL) !== 0n) {
|
|
4227
|
+
throw new Error(`${prefix}_text_relocation_unsupported`);
|
|
4228
|
+
}
|
|
4229
|
+
if (
|
|
4230
|
+
elfType === LINUX_ELF_TYPE_DYN &&
|
|
4231
|
+
(flags1 === null || (flags1 & LINUX_ELF_DYNAMIC_FLAG_PIE) === 0n)
|
|
4232
|
+
) {
|
|
4233
|
+
throw new Error(`${prefix}_flag_missing`);
|
|
4234
|
+
}
|
|
4235
|
+
if (staticPie && neededOffsets.length > 0) {
|
|
4236
|
+
throw new Error("native_entry_static_pie_dependency_unsupported");
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
let needed = [];
|
|
4240
|
+
if (neededOffsets.length > 0) {
|
|
4241
|
+
if (
|
|
4242
|
+
stringTableAddress === null ||
|
|
4243
|
+
stringTableBytes === null ||
|
|
4244
|
+
stringTableBytes < 1n ||
|
|
4245
|
+
stringTableBytes > BigInt(LINUX_ELF_MAX_STRING_TABLE_BYTES)
|
|
4246
|
+
) {
|
|
4247
|
+
throw new Error(`${prefix}_string_table_invalid`);
|
|
4248
|
+
}
|
|
4249
|
+
const stringTableMappings = linuxElfVirtualRangeFileMappings(
|
|
4250
|
+
loadSegments,
|
|
4251
|
+
stringTableAddress,
|
|
4252
|
+
stringTableBytes,
|
|
4253
|
+
);
|
|
4254
|
+
if (stringTableMappings.length === 0) {
|
|
4255
|
+
throw new Error(`${prefix}_string_table_unmapped`);
|
|
4256
|
+
}
|
|
4257
|
+
if (stringTableMappings.length !== 1) {
|
|
4258
|
+
throw new Error(`${prefix}_string_table_mapping_ambiguous`);
|
|
4259
|
+
}
|
|
4260
|
+
const stringTableOffset = stringTableMappings[0].fileOffset;
|
|
4261
|
+
if (stringTableOffset > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
4262
|
+
throw new Error(`${prefix}_string_table_unmapped`);
|
|
4263
|
+
}
|
|
4264
|
+
const stringTable = readLinuxFdExactly(
|
|
4265
|
+
runtime,
|
|
4266
|
+
fd,
|
|
4267
|
+
Number(stringTableBytes),
|
|
4268
|
+
Number(stringTableOffset),
|
|
4269
|
+
);
|
|
4270
|
+
needed = neededOffsets.map((offset) => {
|
|
4271
|
+
if (offset > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
4272
|
+
throw new Error(`${prefix}_dependency_string_out_of_bounds`);
|
|
4273
|
+
}
|
|
4274
|
+
const name = readLinuxElfString(
|
|
4275
|
+
stringTable,
|
|
4276
|
+
Number(offset),
|
|
4277
|
+
"dynamic_dependency",
|
|
4278
|
+
);
|
|
4279
|
+
if (
|
|
4280
|
+
!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(name) ||
|
|
4281
|
+
path.posix.basename(name) !== name
|
|
4282
|
+
) {
|
|
4283
|
+
throw new Error("native_entry_dynamic_dependency_name_invalid");
|
|
4284
|
+
}
|
|
4285
|
+
return name;
|
|
4286
|
+
});
|
|
4287
|
+
if (new Set(needed).size !== needed.length) {
|
|
4288
|
+
throw new Error("native_entry_dynamic_dependency_ambiguous");
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4292
|
+
if (dynamicExecutable) {
|
|
4293
|
+
dynamicMetadata = Object.freeze({
|
|
4294
|
+
interpreter,
|
|
4295
|
+
needed: Object.freeze(needed),
|
|
4296
|
+
});
|
|
3306
4297
|
}
|
|
3307
4298
|
}
|
|
3308
4299
|
|
|
@@ -3312,17 +4303,23 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
|
|
|
3312
4303
|
throw new Error("native_entry_changed_during_elf_attestation");
|
|
3313
4304
|
}
|
|
3314
4305
|
return Object.freeze({
|
|
3315
|
-
|
|
3316
|
-
|
|
4306
|
+
runtime: dynamicExecutable ? "native-dynamic-elf" : "native-static-elf",
|
|
4307
|
+
format: dynamicExecutable
|
|
4308
|
+
? elfType === LINUX_ELF_TYPE_DYN
|
|
4309
|
+
? "elf64-dynamic-pie-et-dyn"
|
|
4310
|
+
: "elf64-dynamic-et-exec"
|
|
4311
|
+
: elfType === LINUX_ELF_TYPE_DYN
|
|
3317
4312
|
? "elf64-static-pie-shaped-et-dyn"
|
|
3318
4313
|
: "elf64-static-et-exec",
|
|
3319
4314
|
architecture: runtime.arch,
|
|
3320
4315
|
machine: expectedMachine,
|
|
4316
|
+
loaderPageBytes: Number(runtimePageSize),
|
|
3321
4317
|
programHeaders: programHeaderCount,
|
|
4318
|
+
...(dynamicMetadata || {}),
|
|
3322
4319
|
});
|
|
3323
4320
|
}
|
|
3324
4321
|
|
|
3325
|
-
function
|
|
4322
|
+
function inspectLinuxNativePath(runtime, identity) {
|
|
3326
4323
|
let fd;
|
|
3327
4324
|
try {
|
|
3328
4325
|
const constants = runtime.fs.constants || fs.constants;
|
|
@@ -3331,7 +4328,7 @@ function inspectLinuxStaticNativePath(runtime, identity) {
|
|
|
3331
4328
|
Number(constants.O_NOFOLLOW || 0) |
|
|
3332
4329
|
Number(constants.O_NONBLOCK || 0);
|
|
3333
4330
|
fd = runtime.fs.openSync(identity.realPath, flags);
|
|
3334
|
-
return
|
|
4331
|
+
return inspectLinuxNativeElf(runtime, fd, identity);
|
|
3335
4332
|
} finally {
|
|
3336
4333
|
if (fd !== undefined) {
|
|
3337
4334
|
try {
|
|
@@ -3355,8 +4352,12 @@ function linuxFdMountId(runtime, fd) {
|
|
|
3355
4352
|
function closeLinuxPinnedMounts(runtime, mounts) {
|
|
3356
4353
|
for (const mount of mounts || []) {
|
|
3357
4354
|
if (!Number.isInteger(mount?.fd)) continue;
|
|
4355
|
+
const fd = mount.fd;
|
|
4356
|
+
// Clear ownership before close so repeated cleanup cannot close an
|
|
4357
|
+
// unrelated descriptor if the OS has already reused this number.
|
|
4358
|
+
mount.fd = null;
|
|
3358
4359
|
try {
|
|
3359
|
-
runtime.fs.closeSync(
|
|
4360
|
+
runtime.fs.closeSync(fd);
|
|
3360
4361
|
} catch {
|
|
3361
4362
|
// Best effort. The process is already fail-closed if a pin cannot be
|
|
3362
4363
|
// created; cleanup must not replace the original boundary error.
|
|
@@ -3706,13 +4707,209 @@ function attestLinuxBubblewrapBinary(runtime) {
|
|
|
3706
4707
|
);
|
|
3707
4708
|
}
|
|
3708
4709
|
|
|
3709
|
-
function
|
|
4710
|
+
function linuxBubblewrapSupervisorStatValid(
|
|
4711
|
+
stat,
|
|
4712
|
+
{ allowUnlinked = false } = {},
|
|
4713
|
+
) {
|
|
4714
|
+
const linkCount = Number(stat?.nlink);
|
|
4715
|
+
return (
|
|
4716
|
+
stat?.isFile?.() === true &&
|
|
4717
|
+
Number(stat.uid) === 0 &&
|
|
4718
|
+
(linkCount === 1 || (allowUnlinked && linkCount === 0)) &&
|
|
4719
|
+
Number(stat.size) > 0 &&
|
|
4720
|
+
Number(stat.size) <= LINUX_ATTESTED_FILE_MAX_BYTES &&
|
|
4721
|
+
(Number(stat.mode) & 0o111) !== 0 &&
|
|
4722
|
+
(Number(stat.mode) & 0o022) === 0
|
|
4723
|
+
);
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
function linuxBubblewrapPinnedStatMatches(expected, current) {
|
|
4727
|
+
return (
|
|
4728
|
+
linuxBubblewrapSupervisorStatValid(current, { allowUnlinked: true }) &&
|
|
4729
|
+
String(expected.dev) === String(current.dev) &&
|
|
4730
|
+
String(expected.ino) === String(current.ino) &&
|
|
4731
|
+
Number(expected.size) === Number(current.size) &&
|
|
4732
|
+
Number(expected.mtimeMs) === Number(current.mtimeMs) &&
|
|
4733
|
+
Number(expected.mode) === Number(current.mode) &&
|
|
4734
|
+
Number(expected.uid) === Number(current.uid) &&
|
|
4735
|
+
Number(expected.gid) === Number(current.gid)
|
|
4736
|
+
);
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
function pinLinuxBubblewrapSupervisor(runtime) {
|
|
4740
|
+
let fd;
|
|
4741
|
+
try {
|
|
4742
|
+
for (const method of [
|
|
4743
|
+
"openSync",
|
|
4744
|
+
"readSync",
|
|
4745
|
+
"fstatSync",
|
|
4746
|
+
"statSync",
|
|
4747
|
+
"lstatSync",
|
|
4748
|
+
"closeSync",
|
|
4749
|
+
]) {
|
|
4750
|
+
if (typeof runtime.fs?.[method] !== "function") {
|
|
4751
|
+
throw new Error(`supervisor_${method}_unavailable`);
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
if (linuxRealpath(runtime, LINUX_BWRAP_PATH) !== LINUX_BWRAP_PATH) {
|
|
4755
|
+
throw new Error("supervisor_realpath_changed");
|
|
4756
|
+
}
|
|
4757
|
+
const pathBefore = runtime.fs.lstatSync(LINUX_BWRAP_PATH, {
|
|
4758
|
+
bigint: true,
|
|
4759
|
+
});
|
|
4760
|
+
if (
|
|
4761
|
+
pathBefore.isSymbolicLink?.() === true ||
|
|
4762
|
+
!linuxBubblewrapSupervisorStatValid(pathBefore)
|
|
4763
|
+
) {
|
|
4764
|
+
throw new Error("supervisor_path_unattested");
|
|
4765
|
+
}
|
|
4766
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
4767
|
+
const flags =
|
|
4768
|
+
Number(constants.O_RDONLY) |
|
|
4769
|
+
Number(constants.O_NOFOLLOW || 0) |
|
|
4770
|
+
Number(constants.O_NONBLOCK || 0);
|
|
4771
|
+
fd = runtime.fs.openSync(LINUX_BWRAP_PATH, flags);
|
|
4772
|
+
const openedBefore = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4773
|
+
const currentPath = runtime.fs.statSync(LINUX_BWRAP_PATH, {
|
|
4774
|
+
bigint: true,
|
|
4775
|
+
});
|
|
4776
|
+
if (
|
|
4777
|
+
!linuxBubblewrapSupervisorStatValid(openedBefore) ||
|
|
4778
|
+
!linuxOpenStatMatches(pathBefore, openedBefore) ||
|
|
4779
|
+
!linuxOpenStatMatches(openedBefore, currentPath)
|
|
4780
|
+
) {
|
|
4781
|
+
throw new Error("supervisor_identity_changed");
|
|
4782
|
+
}
|
|
4783
|
+
const sha256 = hashLinuxOpenFile(runtime, fd, Number(openedBefore.size));
|
|
4784
|
+
const openedAfter = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4785
|
+
const pathAfter = runtime.fs.statSync(LINUX_BWRAP_PATH, {
|
|
4786
|
+
bigint: true,
|
|
4787
|
+
});
|
|
4788
|
+
if (
|
|
4789
|
+
!linuxOpenStatMatches(openedBefore, openedAfter) ||
|
|
4790
|
+
!linuxOpenStatMatches(openedAfter, pathAfter)
|
|
4791
|
+
) {
|
|
4792
|
+
throw new Error("supervisor_identity_changed");
|
|
4793
|
+
}
|
|
4794
|
+
const attestation = Object.freeze({
|
|
4795
|
+
path: LINUX_BWRAP_PATH,
|
|
4796
|
+
fileId: Object.freeze({
|
|
4797
|
+
dev: String(openedAfter.dev),
|
|
4798
|
+
ino: String(openedAfter.ino),
|
|
4799
|
+
}),
|
|
4800
|
+
sha256,
|
|
4801
|
+
bytes: Number(openedAfter.size),
|
|
4802
|
+
mtimeMs: Number(openedAfter.mtimeMs),
|
|
4803
|
+
mode: Number(openedAfter.mode),
|
|
4804
|
+
uid: Number(openedAfter.uid),
|
|
4805
|
+
gid: Number(openedAfter.gid),
|
|
4806
|
+
mechanism: LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM,
|
|
4807
|
+
scope: "host-path-replacement",
|
|
4808
|
+
childFd: LINUX_BWRAP_SUPERVISOR_CHILD_FD,
|
|
4809
|
+
consumeOperation: "file-copy-close",
|
|
4810
|
+
consumePath: LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH,
|
|
4811
|
+
consumeMode: "0000",
|
|
4812
|
+
obscuredBy: "tmpfs:/run",
|
|
4813
|
+
pid1ExecutableExposure: "procfs",
|
|
4814
|
+
});
|
|
4815
|
+
return {
|
|
4816
|
+
fd,
|
|
4817
|
+
openedStat: openedAfter,
|
|
4818
|
+
attestation,
|
|
4819
|
+
};
|
|
4820
|
+
} catch (error) {
|
|
4821
|
+
if (fd !== undefined) {
|
|
4822
|
+
try {
|
|
4823
|
+
runtime.fs.closeSync(fd);
|
|
4824
|
+
} catch {
|
|
4825
|
+
// Preserve the original attestation error.
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
throw error;
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
function openLinuxBubblewrapSupervisorLaunch(runtime, supervisorPin) {
|
|
4833
|
+
let fd;
|
|
4834
|
+
try {
|
|
4835
|
+
if (
|
|
4836
|
+
!Number.isInteger(supervisorPin?.fd) ||
|
|
4837
|
+
!supervisorPin.openedStat ||
|
|
4838
|
+
!supervisorPin.attestation
|
|
4839
|
+
) {
|
|
4840
|
+
throw new Error("supervisor_pin_missing");
|
|
4841
|
+
}
|
|
4842
|
+
const constants = runtime.fs.constants || fs.constants;
|
|
4843
|
+
const flags =
|
|
4844
|
+
Number(constants.O_RDONLY) | Number(constants.O_NONBLOCK || 0);
|
|
4845
|
+
fd = runtime.fs.openSync(`/proc/self/fd/${supervisorPin.fd}`, flags);
|
|
4846
|
+
const openedBefore = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4847
|
+
if (
|
|
4848
|
+
!linuxBubblewrapPinnedStatMatches(supervisorPin.openedStat, openedBefore)
|
|
4849
|
+
) {
|
|
4850
|
+
throw new Error("supervisor_launch_reader_identity_changed");
|
|
4851
|
+
}
|
|
4852
|
+
const sha256 = hashLinuxOpenFile(
|
|
4853
|
+
runtime,
|
|
4854
|
+
fd,
|
|
4855
|
+
supervisorPin.attestation.bytes,
|
|
4856
|
+
);
|
|
4857
|
+
const openedAfter = runtime.fs.fstatSync(fd, { bigint: true });
|
|
4858
|
+
if (
|
|
4859
|
+
!linuxOpenStatMatches(openedBefore, openedAfter) ||
|
|
4860
|
+
sha256 !== supervisorPin.attestation.sha256
|
|
4861
|
+
) {
|
|
4862
|
+
throw new Error("supervisor_launch_reader_identity_changed");
|
|
4863
|
+
}
|
|
4864
|
+
return {
|
|
4865
|
+
fd,
|
|
4866
|
+
command: `/proc/self/fd/${LINUX_BWRAP_SUPERVISOR_CHILD_FD}`,
|
|
4867
|
+
};
|
|
4868
|
+
} catch (error) {
|
|
4869
|
+
if (fd !== undefined) {
|
|
4870
|
+
try {
|
|
4871
|
+
runtime.fs.closeSync(fd);
|
|
4872
|
+
} catch {
|
|
4873
|
+
// Preserve the original launch-reader error.
|
|
4874
|
+
}
|
|
4875
|
+
}
|
|
4876
|
+
throw error;
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4880
|
+
function attestLinuxBubblewrapSupervisorPin(runtime, supervisorPin) {
|
|
4881
|
+
if (
|
|
4882
|
+
!Number.isInteger(supervisorPin?.fd) ||
|
|
4883
|
+
!supervisorPin.openedStat ||
|
|
4884
|
+
!supervisorPin.attestation
|
|
4885
|
+
) {
|
|
4886
|
+
throw new Error("supervisor_pin_missing");
|
|
4887
|
+
}
|
|
4888
|
+
const before = runtime.fs.fstatSync(supervisorPin.fd, { bigint: true });
|
|
4889
|
+
if (!linuxBubblewrapPinnedStatMatches(supervisorPin.openedStat, before)) {
|
|
4890
|
+
throw new Error("supervisor_identity_changed");
|
|
4891
|
+
}
|
|
4892
|
+
const sha256 = hashLinuxOpenFile(
|
|
4893
|
+
runtime,
|
|
4894
|
+
supervisorPin.fd,
|
|
4895
|
+
supervisorPin.attestation.bytes,
|
|
4896
|
+
);
|
|
4897
|
+
const after = runtime.fs.fstatSync(supervisorPin.fd, { bigint: true });
|
|
4898
|
+
if (
|
|
4899
|
+
!linuxOpenStatMatches(before, after) ||
|
|
4900
|
+
sha256 !== supervisorPin.attestation.sha256
|
|
4901
|
+
) {
|
|
4902
|
+
throw new Error("supervisor_identity_changed");
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
|
|
4906
|
+
function attestLinuxBubblewrapCapabilities(runtime, supervisorLaunch) {
|
|
3710
4907
|
let result;
|
|
3711
4908
|
try {
|
|
3712
|
-
result = runtime.spawnSync(
|
|
4909
|
+
result = runtime.spawnSync(supervisorLaunch.command, ["--help"], {
|
|
3713
4910
|
shell: false,
|
|
3714
4911
|
encoding: "utf8",
|
|
3715
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4912
|
+
stdio: ["ignore", "pipe", "pipe", supervisorLaunch.fd],
|
|
3716
4913
|
timeout: 10_000,
|
|
3717
4914
|
env: {
|
|
3718
4915
|
PATH: "/usr/bin:/bin",
|
|
@@ -3727,13 +4924,18 @@ function attestLinuxBubblewrapCapabilities(runtime) {
|
|
|
3727
4924
|
return { ok: false, reason: "capability_probe_failed" };
|
|
3728
4925
|
}
|
|
3729
4926
|
const help = `${String(result.stdout || "")}\n${String(result.stderr || "")}`;
|
|
3730
|
-
|
|
4927
|
+
const supportedOptions = new Set(help.split(/\s+/).filter(Boolean));
|
|
4928
|
+
const requiredOptions = [
|
|
4929
|
+
"--file",
|
|
4930
|
+
"--perms",
|
|
3731
4931
|
"--ro-bind-fd",
|
|
4932
|
+
"--ro-bind-data",
|
|
3732
4933
|
"--disable-userns",
|
|
3733
4934
|
"--assert-userns-disabled",
|
|
3734
4935
|
"--seccomp",
|
|
3735
|
-
]
|
|
3736
|
-
|
|
4936
|
+
];
|
|
4937
|
+
for (const option of requiredOptions) {
|
|
4938
|
+
if (!supportedOptions.has(option)) {
|
|
3737
4939
|
return {
|
|
3738
4940
|
ok: false,
|
|
3739
4941
|
reason: `required_option_missing:${option.slice(2)}`,
|
|
@@ -3861,6 +5063,87 @@ function pinLinuxRuntimeMounts(runtime, runtimeMounts, runtimeIdentity) {
|
|
|
3861
5063
|
}
|
|
3862
5064
|
}
|
|
3863
5065
|
|
|
5066
|
+
// Bind only the kernel-selected interpreter and the entry ELF's direct
|
|
5067
|
+
// DT_NEEDED names. This deliberately does not claim transitive or dlopen-time
|
|
5068
|
+
// closure; handleAtomic remains false for the wider launch chain.
|
|
5069
|
+
function bindLinuxNativeDynamicRuntime(entryFormat, pinnedRuntimeMounts) {
|
|
5070
|
+
if (entryFormat?.runtime !== "native-dynamic-elf") return null;
|
|
5071
|
+
if (
|
|
5072
|
+
!linuxSystemDynamicPath(entryFormat.interpreter) ||
|
|
5073
|
+
!Array.isArray(entryFormat.needed) ||
|
|
5074
|
+
entryFormat.needed.length > LINUX_ELF_MAX_NEEDED_ENTRIES
|
|
5075
|
+
) {
|
|
5076
|
+
throw new Error("native_dynamic_metadata_invalid");
|
|
5077
|
+
}
|
|
5078
|
+
const systemMounts = (pinnedRuntimeMounts || []).filter(
|
|
5079
|
+
(mount) =>
|
|
5080
|
+
linuxSystemDynamicPath(mount?.destination) &&
|
|
5081
|
+
Number.isInteger(mount?.fd) &&
|
|
5082
|
+
typeof mount?.fileId?.dev === "string" &&
|
|
5083
|
+
typeof mount?.fileId?.ino === "string" &&
|
|
5084
|
+
Number.isSafeInteger(mount?.bytes) &&
|
|
5085
|
+
mount.bytes > 0 &&
|
|
5086
|
+
Number.isFinite(mount?.mtimeMs),
|
|
5087
|
+
);
|
|
5088
|
+
const interpreterMount = systemMounts.find(
|
|
5089
|
+
(mount) => mount.destination === entryFormat.interpreter,
|
|
5090
|
+
);
|
|
5091
|
+
if (!interpreterMount) {
|
|
5092
|
+
throw new Error("native_dynamic_interpreter_outside_direct_system_set");
|
|
5093
|
+
}
|
|
5094
|
+
const selected = new Map([[interpreterMount.destination, interpreterMount]]);
|
|
5095
|
+
for (const dependency of entryFormat.needed) {
|
|
5096
|
+
if (
|
|
5097
|
+
typeof dependency !== "string" ||
|
|
5098
|
+
!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(dependency)
|
|
5099
|
+
) {
|
|
5100
|
+
throw new Error("native_dynamic_dependency_name_invalid");
|
|
5101
|
+
}
|
|
5102
|
+
const matches = systemMounts.filter(
|
|
5103
|
+
(mount) => path.posix.basename(mount.destination) === dependency,
|
|
5104
|
+
);
|
|
5105
|
+
if (matches.length === 0) {
|
|
5106
|
+
throw new Error("native_dynamic_dependency_outside_direct_system_set");
|
|
5107
|
+
}
|
|
5108
|
+
if (matches.length !== 1) {
|
|
5109
|
+
throw new Error("native_dynamic_dependency_runtime_ambiguous");
|
|
5110
|
+
}
|
|
5111
|
+
selected.set(matches[0].destination, matches[0]);
|
|
5112
|
+
}
|
|
5113
|
+
const members = [...selected.values()]
|
|
5114
|
+
.map((mount) => ({
|
|
5115
|
+
destination: mount.destination,
|
|
5116
|
+
fileId: {
|
|
5117
|
+
dev: mount.fileId.dev,
|
|
5118
|
+
ino: mount.fileId.ino,
|
|
5119
|
+
},
|
|
5120
|
+
bytes: mount.bytes,
|
|
5121
|
+
mtimeMs: mount.mtimeMs,
|
|
5122
|
+
}))
|
|
5123
|
+
.sort((left, right) => left.destination.localeCompare(right.destination));
|
|
5124
|
+
const digest = crypto
|
|
5125
|
+
.createHash("sha256")
|
|
5126
|
+
.update(
|
|
5127
|
+
JSON.stringify({
|
|
5128
|
+
version: 1,
|
|
5129
|
+
scope: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE,
|
|
5130
|
+
mechanism: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM,
|
|
5131
|
+
interpreter: entryFormat.interpreter,
|
|
5132
|
+
needed: [...entryFormat.needed].sort(),
|
|
5133
|
+
members,
|
|
5134
|
+
}),
|
|
5135
|
+
)
|
|
5136
|
+
.digest("hex");
|
|
5137
|
+
return Object.freeze({
|
|
5138
|
+
scope: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE,
|
|
5139
|
+
mechanism: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM,
|
|
5140
|
+
interpreter: entryFormat.interpreter,
|
|
5141
|
+
dependencies: entryFormat.needed.length,
|
|
5142
|
+
files: members.length,
|
|
5143
|
+
digest,
|
|
5144
|
+
});
|
|
5145
|
+
}
|
|
5146
|
+
|
|
3864
5147
|
function linuxSandboxEnvironment() {
|
|
3865
5148
|
return {
|
|
3866
5149
|
HOME: "/home/sandbox",
|
|
@@ -3928,8 +5211,30 @@ function buildLinuxBubblewrapPolicyArgs(
|
|
|
3928
5211
|
for (const directory of orderedDirectories) {
|
|
3929
5212
|
args.push("--dir", directory);
|
|
3930
5213
|
}
|
|
5214
|
+
// FD 3 is also the descriptor-backed executable used to start bubblewrap.
|
|
5215
|
+
// Consume and close it during setup, then obscure this staging copy with the
|
|
5216
|
+
// later /run tmpfs. The target can still observe bubblewrap itself as the
|
|
5217
|
+
// PID-namespace supervisor through procfs; this only contains the inherited
|
|
5218
|
+
// launch descriptor and its temporary setup path.
|
|
5219
|
+
args.push(
|
|
5220
|
+
"--perms",
|
|
5221
|
+
"0000",
|
|
5222
|
+
"--file",
|
|
5223
|
+
String(LINUX_BWRAP_SUPERVISOR_CHILD_FD),
|
|
5224
|
+
LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH,
|
|
5225
|
+
);
|
|
3931
5226
|
for (const mount of pinnedMounts) {
|
|
3932
|
-
|
|
5227
|
+
if (mount.mountMode === "ro-bind-data") {
|
|
5228
|
+
args.push(
|
|
5229
|
+
"--perms",
|
|
5230
|
+
mount.mountPermissions,
|
|
5231
|
+
"--ro-bind-data",
|
|
5232
|
+
String(mount.childFd),
|
|
5233
|
+
mount.destination,
|
|
5234
|
+
);
|
|
5235
|
+
} else {
|
|
5236
|
+
args.push("--ro-bind-fd", String(mount.childFd), mount.destination);
|
|
5237
|
+
}
|
|
3933
5238
|
}
|
|
3934
5239
|
args.push(
|
|
3935
5240
|
"--seccomp",
|
|
@@ -3974,29 +5279,50 @@ function buildLinuxBubblewrapPolicyArgs(
|
|
|
3974
5279
|
|
|
3975
5280
|
function probeLinuxBubblewrapPolicy(
|
|
3976
5281
|
runtime,
|
|
5282
|
+
supervisorLaunch,
|
|
5283
|
+
supervisorBinding,
|
|
3977
5284
|
policyArgs,
|
|
3978
5285
|
pinnedDescriptors,
|
|
3979
5286
|
targetRuntime,
|
|
5287
|
+
contentSnapshot = null,
|
|
3980
5288
|
) {
|
|
5289
|
+
const supervisorIdentity = JSON.stringify({
|
|
5290
|
+
dev: supervisorBinding.fileId.dev,
|
|
5291
|
+
ino: supervisorBinding.fileId.ino,
|
|
5292
|
+
bytes: supervisorBinding.bytes,
|
|
5293
|
+
});
|
|
5294
|
+
const probeSource = [
|
|
5295
|
+
'const fs = require("node:fs");',
|
|
5296
|
+
`const expected = ${supervisorIdentity};`,
|
|
5297
|
+
'for (const name of fs.readdirSync("/proc/self/fd")) {',
|
|
5298
|
+
" let current;",
|
|
5299
|
+
" try { current = fs.fstatSync(Number(name), { bigint: true }); } catch { continue; }",
|
|
5300
|
+
" if (String(current.dev) === expected.dev &&",
|
|
5301
|
+
" String(current.ino) === expected.ino &&",
|
|
5302
|
+
" Number(current.size) === expected.bytes) {",
|
|
5303
|
+
' throw new Error("bubblewrap supervisor descriptor leaked");',
|
|
5304
|
+
" }",
|
|
5305
|
+
"}",
|
|
5306
|
+
"try {",
|
|
5307
|
+
` fs.lstatSync(${JSON.stringify(LINUX_BWRAP_SUPERVISOR_HIDDEN_PATH)});`,
|
|
5308
|
+
' throw new Error("bubblewrap supervisor staging path visible");',
|
|
5309
|
+
"} catch (error) {",
|
|
5310
|
+
' if (error?.code !== "ENOENT") throw error;',
|
|
5311
|
+
"}",
|
|
5312
|
+
`process.stdout.write(${JSON.stringify(LINUX_BWRAP_NODE_PROBE_SENTINEL)});`,
|
|
5313
|
+
].join("\n");
|
|
3981
5314
|
let result;
|
|
3982
5315
|
try {
|
|
3983
5316
|
result = runtime.spawnSync(
|
|
3984
|
-
|
|
3985
|
-
[
|
|
3986
|
-
...policyArgs,
|
|
3987
|
-
"--",
|
|
3988
|
-
"/opt/chainless/runtime/node",
|
|
3989
|
-
"-e",
|
|
3990
|
-
`process.stdout.write(${JSON.stringify(
|
|
3991
|
-
LINUX_BWRAP_NODE_PROBE_SENTINEL,
|
|
3992
|
-
)})`,
|
|
3993
|
-
],
|
|
5317
|
+
supervisorLaunch.command,
|
|
5318
|
+
[...policyArgs, "--", "/opt/chainless/runtime/node", "-e", probeSource],
|
|
3994
5319
|
{
|
|
3995
5320
|
cwd: "/",
|
|
3996
5321
|
shell: false,
|
|
3997
5322
|
encoding: "utf8",
|
|
3998
5323
|
stdio: linuxStdioWithPinnedMounts(null, pinnedDescriptors, {
|
|
3999
5324
|
probe: true,
|
|
5325
|
+
supervisorFd: supervisorLaunch.fd,
|
|
4000
5326
|
}),
|
|
4001
5327
|
timeout: 15_000,
|
|
4002
5328
|
env: {
|
|
@@ -4012,6 +5338,8 @@ function probeLinuxBubblewrapPolicy(
|
|
|
4012
5338
|
false,
|
|
4013
5339
|
"probe_spawn_failed",
|
|
4014
5340
|
targetRuntime,
|
|
5341
|
+
contentSnapshot,
|
|
5342
|
+
supervisorBinding,
|
|
4015
5343
|
);
|
|
4016
5344
|
}
|
|
4017
5345
|
if (result?.error || result?.status !== 0) {
|
|
@@ -4020,6 +5348,8 @@ function probeLinuxBubblewrapPolicy(
|
|
|
4020
5348
|
false,
|
|
4021
5349
|
result?.error ? "probe_spawn_failed" : "probe_failed",
|
|
4022
5350
|
targetRuntime,
|
|
5351
|
+
contentSnapshot,
|
|
5352
|
+
supervisorBinding,
|
|
4023
5353
|
);
|
|
4024
5354
|
}
|
|
4025
5355
|
if (String(result.stdout) !== LINUX_BWRAP_NODE_PROBE_SENTINEL) {
|
|
@@ -4028,9 +5358,18 @@ function probeLinuxBubblewrapPolicy(
|
|
|
4028
5358
|
false,
|
|
4029
5359
|
"node_runtime_probe_failed",
|
|
4030
5360
|
targetRuntime,
|
|
5361
|
+
contentSnapshot,
|
|
5362
|
+
supervisorBinding,
|
|
4031
5363
|
);
|
|
4032
5364
|
}
|
|
4033
|
-
return linuxBubblewrapProbe(
|
|
5365
|
+
return linuxBubblewrapProbe(
|
|
5366
|
+
true,
|
|
5367
|
+
true,
|
|
5368
|
+
null,
|
|
5369
|
+
targetRuntime,
|
|
5370
|
+
contentSnapshot,
|
|
5371
|
+
supervisorBinding,
|
|
5372
|
+
);
|
|
4034
5373
|
}
|
|
4035
5374
|
|
|
4036
5375
|
/**
|
|
@@ -4081,11 +5420,22 @@ export function applyLinuxSandbox(
|
|
|
4081
5420
|
);
|
|
4082
5421
|
|
|
4083
5422
|
if (requiresStrongLinuxBoundary) {
|
|
5423
|
+
if (isLinuxGenericWorkspaceContract(sandboxOpts.executionContract)) {
|
|
5424
|
+
return applyLinuxGenericWorkspaceSandbox(
|
|
5425
|
+
command,
|
|
5426
|
+
args,
|
|
5427
|
+
spawnOpts,
|
|
5428
|
+
sandboxOpts,
|
|
5429
|
+
runtime,
|
|
5430
|
+
);
|
|
5431
|
+
}
|
|
4084
5432
|
const requestedEntryRuntime =
|
|
4085
5433
|
sandboxOpts.executionContract?.kind ===
|
|
4086
5434
|
"strict-plugin-native-static-elf-bin"
|
|
4087
5435
|
? "native-static-elf"
|
|
4088
|
-
: "
|
|
5436
|
+
: sandboxOpts.executionContract?.kind === "strict-plugin-native-elf-bin"
|
|
5437
|
+
? "native-unclassified"
|
|
5438
|
+
: "node";
|
|
4089
5439
|
const validation = validateLinuxPluginContract(
|
|
4090
5440
|
command,
|
|
4091
5441
|
args,
|
|
@@ -4129,24 +5479,6 @@ export function applyLinuxSandbox(
|
|
|
4129
5479
|
guarantees: [],
|
|
4130
5480
|
});
|
|
4131
5481
|
}
|
|
4132
|
-
const capabilities = attestLinuxBubblewrapCapabilities(runtime);
|
|
4133
|
-
if (!capabilities.ok) {
|
|
4134
|
-
return createSandboxPlan({
|
|
4135
|
-
...base,
|
|
4136
|
-
backend: null,
|
|
4137
|
-
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
4138
|
-
policyAttested: false,
|
|
4139
|
-
runtimeProbe: linuxBubblewrapProbe(
|
|
4140
|
-
true,
|
|
4141
|
-
false,
|
|
4142
|
-
capabilities.reason,
|
|
4143
|
-
validation.entryRuntime,
|
|
4144
|
-
),
|
|
4145
|
-
reason: "linux_bwrap_unavailable",
|
|
4146
|
-
guarantees: [],
|
|
4147
|
-
});
|
|
4148
|
-
}
|
|
4149
|
-
|
|
4150
5482
|
let runtimeMounts;
|
|
4151
5483
|
let pinnedRuntimeMounts = [];
|
|
4152
5484
|
try {
|
|
@@ -4176,11 +5508,42 @@ export function applyLinuxSandbox(
|
|
|
4176
5508
|
guarantees: [],
|
|
4177
5509
|
});
|
|
4178
5510
|
}
|
|
5511
|
+
let nativeDynamicClosure = null;
|
|
5512
|
+
try {
|
|
5513
|
+
nativeDynamicClosure = bindLinuxNativeDynamicRuntime(
|
|
5514
|
+
validation.entryFormat,
|
|
5515
|
+
pinnedRuntimeMounts,
|
|
5516
|
+
);
|
|
5517
|
+
} catch (error) {
|
|
5518
|
+
closeLinuxPinnedMounts(runtime, pinnedRuntimeMounts);
|
|
5519
|
+
return createSandboxPlan({
|
|
5520
|
+
...base,
|
|
5521
|
+
backend: null,
|
|
5522
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5523
|
+
policyAttested: false,
|
|
5524
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5525
|
+
false,
|
|
5526
|
+
false,
|
|
5527
|
+
error.message || "native_dynamic_direct_system_set_unattested",
|
|
5528
|
+
validation.entryRuntime,
|
|
5529
|
+
),
|
|
5530
|
+
reason: "linux_bwrap_native_runtime_unattested",
|
|
5531
|
+
guarantees: [],
|
|
5532
|
+
});
|
|
5533
|
+
}
|
|
4179
5534
|
|
|
4180
5535
|
let pinnedMounts = [];
|
|
4181
5536
|
let pluginTree;
|
|
4182
5537
|
let entryFormat = null;
|
|
4183
5538
|
let entryMount;
|
|
5539
|
+
const entryRelative = path.posix.relative(
|
|
5540
|
+
validation.contract.pluginRoot,
|
|
5541
|
+
validation.contract.entryIdentity.realPath,
|
|
5542
|
+
);
|
|
5543
|
+
const sandboxEntry = path.posix.join(
|
|
5544
|
+
"/opt/chainless/plugin",
|
|
5545
|
+
entryRelative,
|
|
5546
|
+
);
|
|
4184
5547
|
try {
|
|
4185
5548
|
pluginTree = pinLinuxPluginTree(
|
|
4186
5549
|
runtime,
|
|
@@ -4205,8 +5568,8 @@ export function applyLinuxSandbox(
|
|
|
4205
5568
|
) {
|
|
4206
5569
|
throw new Error("plugin_entry_pin_mismatch");
|
|
4207
5570
|
}
|
|
4208
|
-
if (validation.entryRuntime
|
|
4209
|
-
entryFormat =
|
|
5571
|
+
if (validation.entryRuntime.startsWith("native-")) {
|
|
5572
|
+
entryFormat = inspectLinuxNativeElf(
|
|
4210
5573
|
runtime,
|
|
4211
5574
|
entryMount.fd,
|
|
4212
5575
|
validation.contract.entryIdentity,
|
|
@@ -4240,8 +5603,124 @@ export function applyLinuxSandbox(
|
|
|
4240
5603
|
});
|
|
4241
5604
|
}
|
|
4242
5605
|
|
|
5606
|
+
let entrySnapshot = null;
|
|
5607
|
+
let pluginTreeSnapshot = null;
|
|
5608
|
+
const pluginTreeFileSnapshots = [];
|
|
5609
|
+
let probePinnedMounts = pinnedMounts;
|
|
5610
|
+
try {
|
|
5611
|
+
const entryIndex = pinnedMounts.indexOf(entryMount);
|
|
5612
|
+
if (entryIndex < 0) {
|
|
5613
|
+
throw new Error("plugin_entry_pin_missing");
|
|
5614
|
+
}
|
|
5615
|
+
const pluginMountIndexes = pinnedMounts
|
|
5616
|
+
.map((mount, index) =>
|
|
5617
|
+
linuxPathWithin("/opt/chainless/plugin", mount.destination)
|
|
5618
|
+
? index
|
|
5619
|
+
: -1,
|
|
5620
|
+
)
|
|
5621
|
+
.filter((index) => index >= 0);
|
|
5622
|
+
const rawPluginMounts = pluginMountIndexes.map(
|
|
5623
|
+
(index) => pinnedMounts[index],
|
|
5624
|
+
);
|
|
5625
|
+
if (validation.entryRuntime === "node") {
|
|
5626
|
+
if (
|
|
5627
|
+
pluginMountIndexes.length < 1 ||
|
|
5628
|
+
pluginMountIndexes.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES
|
|
5629
|
+
) {
|
|
5630
|
+
throw new Error("node_plugin_tree_snapshot_file_count_invalid");
|
|
5631
|
+
}
|
|
5632
|
+
const pluginBytes = pluginMountIndexes.reduce((total, index) => {
|
|
5633
|
+
const bytes = pinnedMounts[index]?.bytes;
|
|
5634
|
+
if (
|
|
5635
|
+
!Number.isSafeInteger(bytes) ||
|
|
5636
|
+
bytes < 0 ||
|
|
5637
|
+
bytes > LINUX_ATTESTED_FILE_MAX_BYTES
|
|
5638
|
+
) {
|
|
5639
|
+
throw new Error("node_plugin_tree_snapshot_member_size_invalid");
|
|
5640
|
+
}
|
|
5641
|
+
return total + bytes;
|
|
5642
|
+
}, 0);
|
|
5643
|
+
if (
|
|
5644
|
+
!Number.isSafeInteger(pluginBytes) ||
|
|
5645
|
+
pluginBytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
|
|
5646
|
+
) {
|
|
5647
|
+
throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
const rawEntryMount = entryMount;
|
|
5651
|
+
const snapshot = createLinuxEntrySnapshot(
|
|
5652
|
+
runtime,
|
|
5653
|
+
rawEntryMount,
|
|
5654
|
+
validation.contract.entryIdentity,
|
|
5655
|
+
validation.entryRuntime,
|
|
5656
|
+
);
|
|
5657
|
+
const finalMounts = [...pinnedMounts];
|
|
5658
|
+
const probeMounts = [...pinnedMounts];
|
|
5659
|
+
finalMounts[entryIndex] = snapshot.finalMount;
|
|
5660
|
+
probeMounts[entryIndex] = snapshot.probeMount;
|
|
5661
|
+
entrySnapshot = snapshot;
|
|
5662
|
+
if (validation.entryRuntime === "node") {
|
|
5663
|
+
for (const index of pluginMountIndexes) {
|
|
5664
|
+
if (index === entryIndex) continue;
|
|
5665
|
+
const rawMount = pinnedMounts[index];
|
|
5666
|
+
const source = runtime.fs.fstatSync(rawMount.fd);
|
|
5667
|
+
const fileSnapshot = createLinuxRegularFileSnapshot(
|
|
5668
|
+
runtime,
|
|
5669
|
+
rawMount,
|
|
5670
|
+
{
|
|
5671
|
+
fileId: rawMount.fileId,
|
|
5672
|
+
bytes: rawMount.bytes,
|
|
5673
|
+
mtimeMs: rawMount.mtimeMs,
|
|
5674
|
+
},
|
|
5675
|
+
linuxNodePluginTreeFileSnapshotContract(Number(source.mode)),
|
|
5676
|
+
);
|
|
5677
|
+
pluginTreeFileSnapshots.push(fileSnapshot);
|
|
5678
|
+
finalMounts[index] = fileSnapshot.finalMount;
|
|
5679
|
+
probeMounts[index] = fileSnapshot.probeMount;
|
|
5680
|
+
}
|
|
5681
|
+
pluginTreeSnapshot = buildLinuxNodePluginTreeSnapshotAttestation(
|
|
5682
|
+
finalMounts,
|
|
5683
|
+
sandboxEntry,
|
|
5684
|
+
);
|
|
5685
|
+
}
|
|
5686
|
+
pinnedMounts = finalMounts;
|
|
5687
|
+
probePinnedMounts = probeMounts;
|
|
5688
|
+
entryMount = entrySnapshot.finalMount;
|
|
5689
|
+
closeLinuxPinnedMounts(
|
|
5690
|
+
runtime,
|
|
5691
|
+
validation.entryRuntime === "node" ? rawPluginMounts : [rawEntryMount],
|
|
5692
|
+
);
|
|
5693
|
+
} catch (error) {
|
|
5694
|
+
closeLinuxPinnedMounts(runtime, pinnedMounts);
|
|
5695
|
+
closeLinuxPinnedMounts(runtime, [
|
|
5696
|
+
entrySnapshot?.probeMount,
|
|
5697
|
+
entrySnapshot?.finalMount,
|
|
5698
|
+
...pluginTreeFileSnapshots.flatMap((snapshot) => [
|
|
5699
|
+
snapshot.probeMount,
|
|
5700
|
+
snapshot.finalMount,
|
|
5701
|
+
]),
|
|
5702
|
+
]);
|
|
5703
|
+
return createSandboxPlan({
|
|
5704
|
+
...base,
|
|
5705
|
+
backend: null,
|
|
5706
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5707
|
+
policyAttested: false,
|
|
5708
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5709
|
+
false,
|
|
5710
|
+
false,
|
|
5711
|
+
error.message || "entry_snapshot_unattested",
|
|
5712
|
+
validation.entryRuntime,
|
|
5713
|
+
),
|
|
5714
|
+
reason: "linux_bwrap_plugin_snapshot_unattested",
|
|
5715
|
+
guarantees: [],
|
|
5716
|
+
});
|
|
5717
|
+
}
|
|
5718
|
+
|
|
4243
5719
|
let probeSeccompFilter;
|
|
4244
5720
|
let seccompFilter;
|
|
5721
|
+
const pluginTreeProbeMounts = pluginTreeFileSnapshots.map(
|
|
5722
|
+
(snapshot) => snapshot.probeMount,
|
|
5723
|
+
);
|
|
4245
5724
|
try {
|
|
4246
5725
|
probeSeccompFilter = pinLinuxNetworkSeccompFilter(runtime);
|
|
4247
5726
|
seccompFilter = pinLinuxNetworkSeccompFilter(runtime);
|
|
@@ -4255,7 +5734,12 @@ export function applyLinuxSandbox(
|
|
|
4255
5734
|
closeLinuxPinnedMounts(runtime, pinnedMounts);
|
|
4256
5735
|
closeLinuxPinnedMounts(
|
|
4257
5736
|
runtime,
|
|
4258
|
-
[
|
|
5737
|
+
[
|
|
5738
|
+
entrySnapshot?.probeMount,
|
|
5739
|
+
...pluginTreeProbeMounts,
|
|
5740
|
+
probeSeccompFilter,
|
|
5741
|
+
seccompFilter,
|
|
5742
|
+
].filter(Boolean),
|
|
4259
5743
|
);
|
|
4260
5744
|
return createSandboxPlan({
|
|
4261
5745
|
...base,
|
|
@@ -4273,7 +5757,89 @@ export function applyLinuxSandbox(
|
|
|
4273
5757
|
});
|
|
4274
5758
|
}
|
|
4275
5759
|
const pinnedDescriptors = [...pinnedMounts, seccompFilter];
|
|
4276
|
-
const probeDescriptors = [...
|
|
5760
|
+
const probeDescriptors = [...probePinnedMounts, probeSeccompFilter];
|
|
5761
|
+
const closeStrongLinuxResources = (...extra) => {
|
|
5762
|
+
closeLinuxPinnedMounts(runtime, [
|
|
5763
|
+
...pinnedDescriptors,
|
|
5764
|
+
probeSeccompFilter,
|
|
5765
|
+
entrySnapshot?.probeMount,
|
|
5766
|
+
...pluginTreeProbeMounts,
|
|
5767
|
+
...extra,
|
|
5768
|
+
]);
|
|
5769
|
+
};
|
|
5770
|
+
let supervisorPin;
|
|
5771
|
+
let capabilityLaunch;
|
|
5772
|
+
let probeLaunch;
|
|
5773
|
+
let finalLaunch;
|
|
5774
|
+
try {
|
|
5775
|
+
supervisorPin = pinLinuxBubblewrapSupervisor(runtime);
|
|
5776
|
+
capabilityLaunch = openLinuxBubblewrapSupervisorLaunch(
|
|
5777
|
+
runtime,
|
|
5778
|
+
supervisorPin,
|
|
5779
|
+
);
|
|
5780
|
+
} catch (error) {
|
|
5781
|
+
closeStrongLinuxResources(supervisorPin);
|
|
5782
|
+
return createSandboxPlan({
|
|
5783
|
+
...base,
|
|
5784
|
+
backend: null,
|
|
5785
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5786
|
+
policyAttested: false,
|
|
5787
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5788
|
+
false,
|
|
5789
|
+
false,
|
|
5790
|
+
error.message || "supervisor_descriptor_unattested",
|
|
5791
|
+
validation.entryRuntime,
|
|
5792
|
+
),
|
|
5793
|
+
reason: "linux_bwrap_unavailable",
|
|
5794
|
+
guarantees: [],
|
|
5795
|
+
});
|
|
5796
|
+
}
|
|
5797
|
+
const capabilities = attestLinuxBubblewrapCapabilities(
|
|
5798
|
+
runtime,
|
|
5799
|
+
capabilityLaunch,
|
|
5800
|
+
);
|
|
5801
|
+
closeLinuxPinnedMounts(runtime, [capabilityLaunch]);
|
|
5802
|
+
capabilityLaunch = null;
|
|
5803
|
+
if (!capabilities.ok) {
|
|
5804
|
+
closeStrongLinuxResources(supervisorPin);
|
|
5805
|
+
return createSandboxPlan({
|
|
5806
|
+
...base,
|
|
5807
|
+
backend: null,
|
|
5808
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5809
|
+
policyAttested: false,
|
|
5810
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5811
|
+
true,
|
|
5812
|
+
false,
|
|
5813
|
+
capabilities.reason,
|
|
5814
|
+
validation.entryRuntime,
|
|
5815
|
+
null,
|
|
5816
|
+
supervisorPin.attestation,
|
|
5817
|
+
),
|
|
5818
|
+
reason: "linux_bwrap_unavailable",
|
|
5819
|
+
guarantees: [],
|
|
5820
|
+
});
|
|
5821
|
+
}
|
|
5822
|
+
try {
|
|
5823
|
+
probeLaunch = openLinuxBubblewrapSupervisorLaunch(runtime, supervisorPin);
|
|
5824
|
+
} catch (error) {
|
|
5825
|
+
closeStrongLinuxResources(supervisorPin, probeLaunch);
|
|
5826
|
+
return createSandboxPlan({
|
|
5827
|
+
...base,
|
|
5828
|
+
backend: null,
|
|
5829
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
5830
|
+
policyAttested: false,
|
|
5831
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
5832
|
+
true,
|
|
5833
|
+
false,
|
|
5834
|
+
error.message || "supervisor_launch_reader_unattested",
|
|
5835
|
+
validation.entryRuntime,
|
|
5836
|
+
null,
|
|
5837
|
+
supervisorPin.attestation,
|
|
5838
|
+
),
|
|
5839
|
+
reason: "linux_bwrap_unavailable",
|
|
5840
|
+
guarantees: [],
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
4277
5843
|
const environment = linuxSandboxEnvironment();
|
|
4278
5844
|
const policyArgs = buildLinuxBubblewrapPolicyArgs(
|
|
4279
5845
|
pluginTree.directories,
|
|
@@ -4281,24 +5847,31 @@ export function applyLinuxSandbox(
|
|
|
4281
5847
|
environment,
|
|
4282
5848
|
seccompFilter,
|
|
4283
5849
|
);
|
|
4284
|
-
const
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
5850
|
+
const targetArgs = validation.entryRuntime.startsWith("native-")
|
|
5851
|
+
? [sandboxEntry, ...args]
|
|
5852
|
+
: ["/opt/chainless/runtime/node", sandboxEntry, ...args.slice(1)];
|
|
5853
|
+
const snapshotPolicyBinding = entrySnapshot
|
|
5854
|
+
? {
|
|
5855
|
+
scope: entrySnapshot.attestation.scope,
|
|
5856
|
+
mechanism: entrySnapshot.attestation.mechanism,
|
|
5857
|
+
sourceIdentity: validation.contract.entryIdentity,
|
|
5858
|
+
sha256: entrySnapshot.attestation.sha256,
|
|
5859
|
+
bytes: entrySnapshot.attestation.bytes,
|
|
5860
|
+
destination: sandboxEntry,
|
|
5861
|
+
...(validation.entryRuntime === "node"
|
|
5862
|
+
? { sourceFileMode: entrySnapshot.attestation.sourceFileMode }
|
|
5863
|
+
: {}),
|
|
5864
|
+
sourceMode: entrySnapshot.attestation.sourceMode,
|
|
5865
|
+
targetMode: entrySnapshot.attestation.targetMode,
|
|
5866
|
+
}
|
|
5867
|
+
: null;
|
|
4296
5868
|
const policyDigest = crypto
|
|
4297
5869
|
.createHash("sha256")
|
|
4298
5870
|
.update(
|
|
4299
5871
|
JSON.stringify({
|
|
4300
|
-
version:
|
|
5872
|
+
version: validation.entryRuntime === "node" ? 4 : 3,
|
|
4301
5873
|
backend: LINUX_BWRAP_BACKEND,
|
|
5874
|
+
supervisor: supervisorPin.attestation,
|
|
4302
5875
|
contract: {
|
|
4303
5876
|
kind: validation.contract.kind,
|
|
4304
5877
|
pluginRoot: validation.contract.pluginRoot,
|
|
@@ -4308,43 +5881,79 @@ export function applyLinuxSandbox(
|
|
|
4308
5881
|
entryRuntime: validation.entryRuntime,
|
|
4309
5882
|
entryFormat,
|
|
4310
5883
|
},
|
|
4311
|
-
mounts: pinnedMounts.map((mount) =>
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
5884
|
+
mounts: pinnedMounts.map((mount) =>
|
|
5885
|
+
mount.contentSnapshot
|
|
5886
|
+
? {
|
|
5887
|
+
destination: mount.destination,
|
|
5888
|
+
fileId: mount.contentSnapshot.sourceFileId,
|
|
5889
|
+
bytes: mount.contentSnapshot.bytes,
|
|
5890
|
+
mtimeMs: mount.contentSnapshot.sourceMtimeMs,
|
|
5891
|
+
contentSnapshot: {
|
|
5892
|
+
scope: mount.contentSnapshot.scope,
|
|
5893
|
+
mechanism: mount.contentSnapshot.mechanism,
|
|
5894
|
+
sha256: mount.contentSnapshot.sha256,
|
|
5895
|
+
bytes: mount.contentSnapshot.bytes,
|
|
5896
|
+
...(validation.entryRuntime === "node"
|
|
5897
|
+
? {
|
|
5898
|
+
sourceFileMode: mount.contentSnapshot.sourceFileMode,
|
|
5899
|
+
}
|
|
5900
|
+
: {}),
|
|
5901
|
+
sourceMode: mount.contentSnapshot.sourceMode,
|
|
5902
|
+
targetMode: mount.contentSnapshot.targetMode,
|
|
5903
|
+
},
|
|
5904
|
+
}
|
|
5905
|
+
: {
|
|
5906
|
+
destination: mount.destination,
|
|
5907
|
+
fileId: mount.fileId,
|
|
5908
|
+
bytes: mount.bytes,
|
|
5909
|
+
mtimeMs: mount.mtimeMs,
|
|
5910
|
+
},
|
|
5911
|
+
),
|
|
5912
|
+
contentSnapshot: snapshotPolicyBinding,
|
|
5913
|
+
...(nativeDynamicClosure
|
|
5914
|
+
? { initialDynamicLoadClosure: nativeDynamicClosure }
|
|
5915
|
+
: {}),
|
|
5916
|
+
...(validation.entryRuntime === "node"
|
|
5917
|
+
? { pluginTreeContentSnapshot: pluginTreeSnapshot }
|
|
5918
|
+
: {}),
|
|
4317
5919
|
seccomp: {
|
|
4318
5920
|
arch: seccompFilter.arch,
|
|
4319
5921
|
policy: seccompFilter.policy,
|
|
4320
5922
|
sha256: seccompFilter.sha256,
|
|
4321
5923
|
},
|
|
4322
5924
|
policyArgs,
|
|
4323
|
-
target:
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
: targetArgs.slice(0, 2),
|
|
5925
|
+
target: validation.entryRuntime.startsWith("native-")
|
|
5926
|
+
? targetArgs.slice(0, 1)
|
|
5927
|
+
: targetArgs.slice(0, 2),
|
|
4327
5928
|
}),
|
|
4328
5929
|
)
|
|
4329
5930
|
.digest("hex");
|
|
4330
|
-
const
|
|
5931
|
+
const policyProbe = Object.freeze(
|
|
4331
5932
|
probeLinuxBubblewrapPolicy(
|
|
4332
5933
|
runtime,
|
|
5934
|
+
probeLaunch,
|
|
5935
|
+
supervisorPin.attestation,
|
|
4333
5936
|
policyArgs,
|
|
4334
5937
|
probeDescriptors,
|
|
4335
5938
|
validation.entryRuntime,
|
|
4336
5939
|
),
|
|
4337
5940
|
);
|
|
4338
|
-
closeLinuxPinnedMounts(runtime, [
|
|
4339
|
-
|
|
4340
|
-
|
|
5941
|
+
closeLinuxPinnedMounts(runtime, [
|
|
5942
|
+
probeLaunch,
|
|
5943
|
+
probeSeccompFilter,
|
|
5944
|
+
entrySnapshot?.probeMount,
|
|
5945
|
+
...pluginTreeProbeMounts,
|
|
5946
|
+
]);
|
|
5947
|
+
probeLaunch = null;
|
|
5948
|
+
if (!policyProbe.runnable) {
|
|
5949
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
4341
5950
|
return createSandboxPlan({
|
|
4342
5951
|
...base,
|
|
4343
5952
|
backend: null,
|
|
4344
5953
|
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
4345
5954
|
policyAttested: false,
|
|
4346
5955
|
policyDigest,
|
|
4347
|
-
runtimeProbe,
|
|
5956
|
+
runtimeProbe: policyProbe,
|
|
4348
5957
|
reason: "linux_bwrap_policy_probe_failed",
|
|
4349
5958
|
guarantees: [],
|
|
4350
5959
|
});
|
|
@@ -4356,9 +5965,10 @@ export function applyLinuxSandbox(
|
|
|
4356
5965
|
validation.contract,
|
|
4357
5966
|
runtime,
|
|
4358
5967
|
sandboxOpts.sync,
|
|
5968
|
+
{ sealedEntry: entrySnapshot !== null },
|
|
4359
5969
|
);
|
|
4360
5970
|
if (!finalValidation.ok) {
|
|
4361
|
-
|
|
5971
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
4362
5972
|
return createSandboxPlan({
|
|
4363
5973
|
...base,
|
|
4364
5974
|
backend: null,
|
|
@@ -4370,46 +5980,108 @@ export function applyLinuxSandbox(
|
|
|
4370
5980
|
false,
|
|
4371
5981
|
`post_probe_${finalValidation.reason}`,
|
|
4372
5982
|
validation.entryRuntime,
|
|
5983
|
+
null,
|
|
5984
|
+
supervisorPin.attestation,
|
|
4373
5985
|
),
|
|
4374
5986
|
reason: "linux_bwrap_execution_contract_changed",
|
|
4375
5987
|
guarantees: [],
|
|
4376
5988
|
});
|
|
4377
5989
|
}
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
5990
|
+
try {
|
|
5991
|
+
if (validation.entryRuntime === "node") {
|
|
5992
|
+
attestLinuxNodePluginTreeSnapshot(
|
|
4381
5993
|
runtime,
|
|
4382
|
-
|
|
4383
|
-
|
|
5994
|
+
pinnedMounts,
|
|
5995
|
+
sandboxEntry,
|
|
5996
|
+
pluginTreeSnapshot,
|
|
5997
|
+
);
|
|
5998
|
+
} else {
|
|
5999
|
+
attestLinuxEntrySnapshot(
|
|
6000
|
+
runtime,
|
|
6001
|
+
entryMount,
|
|
6002
|
+
entrySnapshot.attestation,
|
|
6003
|
+
validation.entryRuntime,
|
|
4384
6004
|
);
|
|
4385
|
-
if (JSON.stringify(finalEntryFormat) !== JSON.stringify(entryFormat)) {
|
|
4386
|
-
throw new Error("native_entry_format_changed");
|
|
4387
|
-
}
|
|
4388
|
-
if (
|
|
4389
|
-
JSON.stringify(finalValidation.entryFormat) !==
|
|
4390
|
-
JSON.stringify(entryFormat)
|
|
4391
|
-
) {
|
|
4392
|
-
throw new Error("native_entry_path_format_changed");
|
|
4393
|
-
}
|
|
4394
|
-
} catch (error) {
|
|
4395
|
-
closeLinuxPinnedMounts(runtime, pinnedDescriptors);
|
|
4396
|
-
return createSandboxPlan({
|
|
4397
|
-
...base,
|
|
4398
|
-
backend: null,
|
|
4399
|
-
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
4400
|
-
policyAttested: false,
|
|
4401
|
-
policyDigest,
|
|
4402
|
-
runtimeProbe: linuxBubblewrapProbe(
|
|
4403
|
-
true,
|
|
4404
|
-
false,
|
|
4405
|
-
`post_probe_${error.message || "native_entry_changed"}`,
|
|
4406
|
-
validation.entryRuntime,
|
|
4407
|
-
),
|
|
4408
|
-
reason: "linux_bwrap_execution_contract_changed",
|
|
4409
|
-
guarantees: [],
|
|
4410
|
-
});
|
|
4411
6005
|
}
|
|
6006
|
+
} catch (error) {
|
|
6007
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
6008
|
+
return createSandboxPlan({
|
|
6009
|
+
...base,
|
|
6010
|
+
backend: null,
|
|
6011
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
6012
|
+
policyAttested: false,
|
|
6013
|
+
policyDigest,
|
|
6014
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
6015
|
+
true,
|
|
6016
|
+
false,
|
|
6017
|
+
`post_probe_${error.message || "entry_snapshot_changed"}`,
|
|
6018
|
+
validation.entryRuntime,
|
|
6019
|
+
null,
|
|
6020
|
+
supervisorPin.attestation,
|
|
6021
|
+
),
|
|
6022
|
+
reason: "linux_bwrap_execution_contract_changed",
|
|
6023
|
+
guarantees: [],
|
|
6024
|
+
});
|
|
6025
|
+
}
|
|
6026
|
+
try {
|
|
6027
|
+
attestLinuxBubblewrapSupervisorPin(runtime, supervisorPin);
|
|
6028
|
+
} catch (error) {
|
|
6029
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
6030
|
+
return createSandboxPlan({
|
|
6031
|
+
...base,
|
|
6032
|
+
backend: null,
|
|
6033
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
6034
|
+
policyAttested: false,
|
|
6035
|
+
policyDigest,
|
|
6036
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
6037
|
+
true,
|
|
6038
|
+
false,
|
|
6039
|
+
`post_probe_${error.message || "supervisor_identity_changed"}`,
|
|
6040
|
+
validation.entryRuntime,
|
|
6041
|
+
null,
|
|
6042
|
+
supervisorPin.attestation,
|
|
6043
|
+
),
|
|
6044
|
+
reason: "linux_bwrap_execution_contract_changed",
|
|
6045
|
+
guarantees: [],
|
|
6046
|
+
});
|
|
4412
6047
|
}
|
|
6048
|
+
try {
|
|
6049
|
+
finalLaunch = openLinuxBubblewrapSupervisorLaunch(runtime, supervisorPin);
|
|
6050
|
+
} catch (error) {
|
|
6051
|
+
closeStrongLinuxResources(supervisorPin, finalLaunch);
|
|
6052
|
+
return createSandboxPlan({
|
|
6053
|
+
...base,
|
|
6054
|
+
backend: null,
|
|
6055
|
+
candidateBackend: LINUX_BWRAP_BACKEND,
|
|
6056
|
+
policyAttested: false,
|
|
6057
|
+
policyDigest,
|
|
6058
|
+
runtimeProbe: linuxBubblewrapProbe(
|
|
6059
|
+
true,
|
|
6060
|
+
false,
|
|
6061
|
+
`post_probe_${error.message || "supervisor_launch_reader_unattested"}`,
|
|
6062
|
+
validation.entryRuntime,
|
|
6063
|
+
null,
|
|
6064
|
+
supervisorPin.attestation,
|
|
6065
|
+
),
|
|
6066
|
+
reason: "linux_bwrap_execution_contract_changed",
|
|
6067
|
+
guarantees: [],
|
|
6068
|
+
});
|
|
6069
|
+
}
|
|
6070
|
+
const supervisorBinding = supervisorPin.attestation;
|
|
6071
|
+
closeLinuxPinnedMounts(runtime, [supervisorPin]);
|
|
6072
|
+
supervisorPin = null;
|
|
6073
|
+
const runtimeProbe = Object.freeze(
|
|
6074
|
+
linuxBubblewrapProbe(
|
|
6075
|
+
true,
|
|
6076
|
+
true,
|
|
6077
|
+
null,
|
|
6078
|
+
validation.entryRuntime,
|
|
6079
|
+
entrySnapshot?.attestation,
|
|
6080
|
+
supervisorBinding,
|
|
6081
|
+
pluginTreeSnapshot,
|
|
6082
|
+
nativeDynamicClosure,
|
|
6083
|
+
),
|
|
6084
|
+
);
|
|
4413
6085
|
const options = {
|
|
4414
6086
|
...spawnOpts,
|
|
4415
6087
|
cwd: "/",
|
|
@@ -4421,14 +6093,16 @@ export function applyLinuxSandbox(
|
|
|
4421
6093
|
LC_ALL: "C",
|
|
4422
6094
|
},
|
|
4423
6095
|
stdio: Object.freeze(
|
|
4424
|
-
linuxStdioWithPinnedMounts(spawnOpts?.stdio, pinnedDescriptors
|
|
6096
|
+
linuxStdioWithPinnedMounts(spawnOpts?.stdio, pinnedDescriptors, {
|
|
6097
|
+
supervisorFd: finalLaunch.fd,
|
|
6098
|
+
}),
|
|
4425
6099
|
),
|
|
4426
6100
|
};
|
|
4427
6101
|
let pinsClosed = false;
|
|
4428
6102
|
const cleanup = () => {
|
|
4429
6103
|
if (pinsClosed) return;
|
|
4430
6104
|
pinsClosed = true;
|
|
4431
|
-
closeLinuxPinnedMounts(runtime, pinnedDescriptors);
|
|
6105
|
+
closeLinuxPinnedMounts(runtime, [...pinnedDescriptors, finalLaunch]);
|
|
4432
6106
|
};
|
|
4433
6107
|
return createSandboxPlan({
|
|
4434
6108
|
...base,
|
|
@@ -4441,7 +6115,7 @@ export function applyLinuxSandbox(
|
|
|
4441
6115
|
runtimeProbe,
|
|
4442
6116
|
reason: null,
|
|
4443
6117
|
guarantees: [SANDBOX_BOUNDARIES.FILESYSTEM, SANDBOX_BOUNDARIES.NETWORK],
|
|
4444
|
-
command:
|
|
6118
|
+
command: finalLaunch.command,
|
|
4445
6119
|
args: [...policyArgs, "--", ...targetArgs],
|
|
4446
6120
|
options,
|
|
4447
6121
|
cleanup,
|