[GH-ISSUE #4502] [Bug] Ghidra import snapshot loses descriptor cleanup ownership and primary outcomes #1788

Closed
opened 2026-10-16 21:10:15 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @morluto on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4502

Before submitting

  • Searched Ghidra snapshot/cleanup reports and open PRs; related #3977 is DOS attestation readback, and closed #1190 is runtime-tree removal, both distinct boundaries.
  • No credentials or sensitive target data included.

Area

Native analysis or decompilation

Observed behavior

Source-confirmed ownership/precedence defect; runtime fault reproduction not yet performed. POSIX Ghidra import snapshot creation holds source and destination FileHandles in locals, gives auto-close streams the same handles, and finally awaits Promise.all of both closes. A failed final close replaces a completed snapshot or copy/digest failure; the rejected handle has no retained retry owner after return. Promise.all can reject while the other close is still pending. GhidraClient also discards a snapshot failure when the startup deadline has subsequently aborted.

Reproduction

NOT_ENOUGH_INFO for historical/native provider reproduction. Inspected createGhidraTargetSnapshot and GhidraClient's snapshot-admission catch at the source version below. Proposed controlled production-boundary checks: exact descriptor close failure after success/digest failure; two closes with mixed outcomes and a delayed second close; IO rejection coincident with startup cancellation; retry of only unresolved owners. No test or engine run is claimed at filing.

Expected behavior

AGENTS.md and docs/tool-design.md require one clear owner per resource, preservation of primary physical outcomes and caller cancellation reasons, awaiting all admitted finalization, and retaining exact failed cleanup ownership. Runtime-root removal does not prove an open descriptor was closed.

Impact and scope

Source-derived POSIX startup path. Provider startup may lose the primary failure, report before both descriptor closes settle, or lose retry authority for unconfirmed cleanup. Natural occurrence and actual leak counts are unknown. Native Windows takes a separate WindowsPrivateRuntime snapshot branch and is not diagnosed by this report.

REA version

6.5.0; source base fe2a398dd9 with ongoing local audit changes.

Runtime and platform

Source analysis on macOS arm64, Node24.18.0. Native Ghidra fault reproduction not performed.

Target and artifact context

Digest-committed target copy into the ephemeral private Ghidra import root; source bytes are read-only.

Interface

Shared Ghidra startup used by CLI/MCP.

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

src/ghidra/GhidraTargetSnapshot.ts: source.createReadStream and destination.createWriteStream use implicit auto-close; catch suppresses snapshot rm failure; finally awaits Promise.all([source?.close(), destination?.close()]) without retaining FileHandle owners or operation outcome. src/ghidra/GhidraClient.ts snapshot catch returns startupInterrupted whenever deadline.signal.aborted. src/process/PrivateRuntimeRoot.ts POSIX close only removes the tree and has no descriptor ownership. These are source findings, not executed leak observations.

Typed outcome or recovery expected

Keep completed copy/digest facts and original failures separate from cleanup uncertainty. Stop startup until all active finalization settles, retain the exact failed descriptor capability, and retry only unresolved cleanup. Cancellation must be produced where observed, preserving an already observed failure.

Analysis or suspected cause

Split ownership between streams and raw FileHandles, finally-based failure replacement, and downstream interpretation of a later signal. Reuse filesystem supervision/identity primitives and explicit outcome ownership across the snapshot-to-client boundary. No target execution or mutation capability is proposed.

Additional context

Adjacent to descriptor ownership audit #4458/#4475/#4495. #3977 concerns a different snapshot reader used for DOS attestation and has a contributor preparing a narrow post-close cancellation fix; this report does not duplicate that repair.

Originally created by @morluto on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4502 ### Before submitting - [x] Searched Ghidra snapshot/cleanup reports and open PRs; related #3977 is DOS attestation readback, and closed #1190 is runtime-tree removal, both distinct boundaries. - [x] No credentials or sensitive target data included. ### Area Native analysis or decompilation ### Observed behavior **Source-confirmed ownership/precedence defect; runtime fault reproduction not yet performed.** POSIX Ghidra import snapshot creation holds source and destination FileHandles in locals, gives auto-close streams the same handles, and finally awaits Promise.all of both closes. A failed final close replaces a completed snapshot or copy/digest failure; the rejected handle has no retained retry owner after return. Promise.all can reject while the other close is still pending. GhidraClient also discards a snapshot failure when the startup deadline has subsequently aborted. ### Reproduction NOT_ENOUGH_INFO for historical/native provider reproduction. Inspected `createGhidraTargetSnapshot` and GhidraClient's snapshot-admission catch at the source version below. Proposed controlled production-boundary checks: exact descriptor close failure after success/digest failure; two closes with mixed outcomes and a delayed second close; IO rejection coincident with startup cancellation; retry of only unresolved owners. No test or engine run is claimed at filing. ### Expected behavior AGENTS.md and docs/tool-design.md require one clear owner per resource, preservation of primary physical outcomes and caller cancellation reasons, awaiting all admitted finalization, and retaining exact failed cleanup ownership. Runtime-root removal does not prove an open descriptor was closed. ### Impact and scope Source-derived POSIX startup path. Provider startup may lose the primary failure, report before both descriptor closes settle, or lose retry authority for unconfirmed cleanup. Natural occurrence and actual leak counts are unknown. Native Windows takes a separate WindowsPrivateRuntime snapshot branch and is not diagnosed by this report. ### REA version 6.5.0; source base fe2a398dd9ab290ba6cb3411403d319cec8d07a2 with ongoing local audit changes. ### Runtime and platform Source analysis on macOS arm64, Node24.18.0. Native Ghidra fault reproduction not performed. ### Target and artifact context Digest-committed target copy into the ephemeral private Ghidra import root; source bytes are read-only. ### Interface Shared Ghidra startup used by CLI/MCP. ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence `src/ghidra/GhidraTargetSnapshot.ts`: source.createReadStream and destination.createWriteStream use implicit auto-close; catch suppresses snapshot rm failure; finally awaits Promise.all([source?.close(), destination?.close()]) without retaining FileHandle owners or operation outcome. `src/ghidra/GhidraClient.ts` snapshot catch returns startupInterrupted whenever deadline.signal.aborted. `src/process/PrivateRuntimeRoot.ts` POSIX close only removes the tree and has no descriptor ownership. These are source findings, not executed leak observations. ### Typed outcome or recovery expected Keep completed copy/digest facts and original failures separate from cleanup uncertainty. Stop startup until all active finalization settles, retain the exact failed descriptor capability, and retry only unresolved cleanup. Cancellation must be produced where observed, preserving an already observed failure. ### Analysis or suspected cause Split ownership between streams and raw FileHandles, finally-based failure replacement, and downstream interpretation of a later signal. Reuse filesystem supervision/identity primitives and explicit outcome ownership across the snapshot-to-client boundary. No target execution or mutation capability is proposed. ### Additional context Adjacent to descriptor ownership audit #4458/#4475/#4495. #3977 concerns a different snapshot reader used for DOS attestation and has a contributor preparing a narrow post-close cancellation fix; this report does not duplicate that repair.
Author
Owner

@kaoru0822-kitauji commented on GitHub (Oct 17, 2026):

This appears fixed on current main (2092f90b7) by 9d940c2f2 (the commit message references this issue).

Executed on main 2092f90b7 (macOS arm64, Node 24): npm run test:focused -- … including tests/process-global/ghidraImportSnapshotLifetime.test.ts (14 tests) — all passed (205/205 across the 13 files checked in this batch).

I did not rerun the original fault injection from the report separately; the tests above are the regressions added with the fix. Leaving closure to a maintainer.

<!-- gh-comment-id:6232499996 --> @kaoru0822-kitauji commented on GitHub (Oct 17, 2026): This appears fixed on current main (2092f90b7) by 9d940c2f2 (the commit message references this issue). Executed on main 2092f90b7 (macOS arm64, Node 24): `npm run test:focused -- …` including `tests/process-global/ghidraImportSnapshotLifetime.test.ts` (14 tests) — all passed (205/205 across the 13 files checked in this batch). I did not rerun the original fault injection from the report separately; the tests above are the regressions added with the fix. Leaving closure to a maintainer.
Author
Owner

@morluto commented on GitHub (Oct 18, 2026):

Closing as fixed on main after reconciling this report with the landed owner/consumer changes.

  • 9d940c2f2: fix(ghidra): preserve lifecycle ownership and failure provenance. Retain snapshot, endpoint, launch rollback and compiler cleanup owners. Join request finalization and cleanup cancellation. Preserve bounded compiler output and original byte counts through capture and Ghidra failures. Normalize execFile cancellation once at its producer.
  • 5f9ac2c98: fix(windows): retain primary snapshot and cancellation failures. Keep the original snapshot failure alongside disposal uncertainty and retain exact cancellation reasons at the TypeScript runtime boundary.

Evidence scope: this closure uses the landed source, regression coverage and validation already recorded in the linked commits/thread. No new local tests or native-provider reproduction were run during this backlog pass; previously documented platform and real-provider gaps still apply.

<!-- gh-comment-id:6256013288 --> @morluto commented on GitHub (Oct 18, 2026): <!-- rea-backlog-reconciliation-2026-10-19-4502 --> Closing as fixed on `main` after reconciling this report with the landed owner/consumer changes. - [9d940c2f2](https://github.com/morluto/rea/commit/9d940c2f284cc6f75b27900336e52ceeba4aa2af): fix(ghidra): preserve lifecycle ownership and failure provenance. Retain snapshot, endpoint, launch rollback and compiler cleanup owners. Join request finalization and cleanup cancellation. Preserve bounded compiler output and original byte counts through capture and Ghidra failures. Normalize execFile cancellation once at its producer. - [5f9ac2c98](https://github.com/morluto/rea/commit/5f9ac2c98a6fe11988c41afb737df9b6d5c3a1b3): fix(windows): retain primary snapshot and cancellation failures. Keep the original snapshot failure alongside disposal uncertainty and retain exact cancellation reasons at the TypeScript runtime boundary. Evidence scope: this closure uses the landed source, regression coverage and validation already recorded in the linked commits/thread. No new local tests or native-provider reproduction were run during this backlog pass; previously documented platform and real-provider gaps still apply.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1788
No description provided.