[GH-ISSUE #288] Own Windows Ghidra process trees with kill-on-close semantics #160

Open
opened 2026-10-15 18:21:52 +00:00 by bsakaguchi · 3 comments
Owner

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

Originally assigned to: @N0zoM1z0 on GitHub.

Problem Statement

Windows Ghidra P0 currently stops the launcher tree with bounded taskkill /T /F. This is useful cleanup but is not an operating-system ownership proof: it is PID-based, cannot establish that every descendant remained in the tree, and cannot provide kill-on-owner-close behavior.

Node/libuv's implicit Windows job is also insufficient for the stronger claim. It enables silent breakaway for descendants and tolerates some assignment failures so ordinary child_process.spawn can continue without containment.

Purpose and Benefit

Give each Windows Ghidra session an OS-owned process lifetime. Closing or losing the owner handle should terminate the associated launcher, Java process, and descendants without enumerating PIDs.

This would:

  • remove PID-reuse ambiguity from provider cleanup;
  • make cancellation, timeout, protocol failure, and parent crash converge on one cleanup primitive;
  • prevent untracked Ghidra descendants from surviving a failed session; and
  • replace the current Windows cleanup limitation with verifiable evidence.

Proposed Direction

Add a Windows process-owner adapter beneath ProviderProcess that:

  1. creates a dedicated unnamed Job Object;
  2. applies JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE without silent breakaway;
  3. creates the launcher already assigned to the job, preferably with PROC_THREAD_ATTRIBUTE_JOB_LIST to avoid a create-then-assign race;
  4. retains the job handle for the full provider lifetime; and
  5. reports typed launch, assignment, termination, and settlement outcomes.

The Ghidra launcher should consume the provider-neutral ownership interface. Raw Win32 handles and Job Object policy must not enter the domain or application layers.

Desired Outcome

  • Ghidra cannot execute before successful Job Object assignment.
  • Child and grandchild fixtures remain in the owned job unless an explicitly unsupported breakaway attempt causes launch or verification to fail closed.
  • Closing the final owner handle terminates all associated live processes.
  • Cleanup reports distinguish already empty, terminated, assignment unavailable, nested-job conflict, and incomplete settlement.
  • Doctor/capability output reports whether this ownership backend is available.
  • The real Windows verifier proves normal exit, cancellation, timeout, parent-handle close, descendant cleanup, and no unrelated-process termination.

Constraints and Non-goals

  • Depends on the packaging and adapter decision in #287.
  • Preserve the existing taskkill implementation only as an explicitly limited fallback; it must not satisfy the Job Object capability claim.
  • Do not broaden this into Windows PTY capture, replay, or resource-limit policy.
  • Do not rely on a create-then-assign sequence that lets child code run before ownership is established.

Evidence and References

Originally created by @morluto on GitHub (Jul 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/288 Originally assigned to: @N0zoM1z0 on GitHub. ## Problem Statement Windows Ghidra P0 currently stops the launcher tree with bounded taskkill /T /F. This is useful cleanup but is not an operating-system ownership proof: it is PID-based, cannot establish that every descendant remained in the tree, and cannot provide kill-on-owner-close behavior. Node/libuv's implicit Windows job is also insufficient for the stronger claim. It enables silent breakaway for descendants and tolerates some assignment failures so ordinary child_process.spawn can continue without containment. ## Purpose and Benefit Give each Windows Ghidra session an OS-owned process lifetime. Closing or losing the owner handle should terminate the associated launcher, Java process, and descendants without enumerating PIDs. This would: - remove PID-reuse ambiguity from provider cleanup; - make cancellation, timeout, protocol failure, and parent crash converge on one cleanup primitive; - prevent untracked Ghidra descendants from surviving a failed session; and - replace the current Windows cleanup limitation with verifiable evidence. ## Proposed Direction Add a Windows process-owner adapter beneath ProviderProcess that: 1. creates a dedicated unnamed Job Object; 2. applies JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE without silent breakaway; 3. creates the launcher already assigned to the job, preferably with PROC_THREAD_ATTRIBUTE_JOB_LIST to avoid a create-then-assign race; 4. retains the job handle for the full provider lifetime; and 5. reports typed launch, assignment, termination, and settlement outcomes. The Ghidra launcher should consume the provider-neutral ownership interface. Raw Win32 handles and Job Object policy must not enter the domain or application layers. ## Desired Outcome - Ghidra cannot execute before successful Job Object assignment. - Child and grandchild fixtures remain in the owned job unless an explicitly unsupported breakaway attempt causes launch or verification to fail closed. - Closing the final owner handle terminates all associated live processes. - Cleanup reports distinguish already empty, terminated, assignment unavailable, nested-job conflict, and incomplete settlement. - Doctor/capability output reports whether this ownership backend is available. - The real Windows verifier proves normal exit, cancellation, timeout, parent-handle close, descendant cleanup, and no unrelated-process termination. ## Constraints and Non-goals - Depends on the packaging and adapter decision in #287. - Preserve the existing taskkill implementation only as an explicitly limited fallback; it must not satisfy the Job Object capability claim. - Do not broaden this into Windows PTY capture, replay, or resource-limit policy. - Do not rely on a create-then-assign sequence that lets child code run before ownership is established. ## Evidence and References - Parent roadmap: #38 - Current implementation: src/process/ProcessOwnership.ts, src/process/ProviderProcess.ts, and src/ghidra/GhidraLauncher.ts - Job Object process-tree and kill-on-close semantics: https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects - Atomic process-creation job list: https://learn.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute - Assignment and nested-job constraints: https://learn.microsoft.com/en-us/windows/win32/api/jobapi2/nf-jobapi2-assignprocesstojobobject - libuv's breakaway and assignment behavior: https://github.com/libuv/libuv/blob/v1.x/src/win/process.c
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Status after #287 packaging research: this remains an active implementation gap. It can proceed once the Windows native platform package/loader/typed port from #287 is accepted, and it does not need to wait for reparse-safe file handles or DACL work. The implementation must use Job Objects for owned-tree cleanup and retain a real non-admin Windows verifier; taskkill is not equivalent ownership proof.

<!-- gh-comment-id:5001546940 --> @morluto commented on GitHub (Jul 17, 2026): Status after #287 packaging research: this remains an active implementation gap. It can proceed once the Windows native platform package/loader/typed port from #287 is accepted, and it does not need to wait for reparse-safe file handles or DACL work. The implementation must use Job Objects for owned-tree cleanup and retain a real non-admin Windows verifier; taskkill is not equivalent ownership proof.
Author
Owner

@morluto commented on GitHub (Jul 17, 2026):

Current-main contract update (2026-07-17):

The provider-neutral process boundary now refuses to publish a WindowsOwnedProcess unless the native result reports the exact initial Job Object policy:

  • dedicated unnamed Job;
  • PROC_THREAD_ATTRIBUTE_JOB_LIST assignment;
  • process execution assigned at creation rather than create-then-assign;
  • kill-on-close enabled;
  • breakaway disabled;
  • no admitted nested Job;
  • retained non-inheritable owner handle;
  • exactly one addon-owned Job handle;
  • stdio-only inherited handles; and
  • verified process membership.

The application derives a versioned SHA-256 commitment from every normalized field; it no longer trusts an opaque addon digest. Any broader, missing, malformed, or unknown ownership report returns native_contract_mismatch and closes the process/Job token before publication. Focused tests reject named/shared Jobs, post-create assignment, pre-assignment execution, disabled kill-on-close, breakaway, nested Jobs, inheritable or duplicate owner handles, ambient inherited handles, unverified membership, and extra fields, with exactly-once cleanup.

Independent review also clarified the last-handle boundary: an unnamed/non-inheritable Job does not prevent another same-user process with PROCESS_DUP_HANDLE access from duplicating REA's retained handle. ADR-0004 now limits last-handle kill-on-close claims to processes outside that handle-theft boundary. Stronger same-user proof requires process-handle protection plus real duplication attempts.

The exact final tree passes npm run check: 160 test files, 1,088 tests, plus generated docs, build, typecheck, lint, and formatting. Independent follow-up review is clean.

This issue remains open for the native Node-API Job implementation, queried-limit/membership proof, real descendant/breakaway/normal-exit/cancellation/timeout/parent-close tests, doctor/capability wiring, and the explicit same-user handle-theft decision. taskkill remains a limited fallback and does not satisfy this contract.

<!-- gh-comment-id:5008079175 --> @morluto commented on GitHub (Jul 17, 2026): Current-main contract update (2026-07-17): The provider-neutral process boundary now refuses to publish a WindowsOwnedProcess unless the native result reports the exact initial Job Object policy: - dedicated unnamed Job; - PROC_THREAD_ATTRIBUTE_JOB_LIST assignment; - process execution assigned at creation rather than create-then-assign; - kill-on-close enabled; - breakaway disabled; - no admitted nested Job; - retained non-inheritable owner handle; - exactly one addon-owned Job handle; - stdio-only inherited handles; and - verified process membership. The application derives a versioned SHA-256 commitment from every normalized field; it no longer trusts an opaque addon digest. Any broader, missing, malformed, or unknown ownership report returns native_contract_mismatch and closes the process/Job token before publication. Focused tests reject named/shared Jobs, post-create assignment, pre-assignment execution, disabled kill-on-close, breakaway, nested Jobs, inheritable or duplicate owner handles, ambient inherited handles, unverified membership, and extra fields, with exactly-once cleanup. Independent review also clarified the last-handle boundary: an unnamed/non-inheritable Job does not prevent another same-user process with PROCESS_DUP_HANDLE access from duplicating REA's retained handle. ADR-0004 now limits last-handle kill-on-close claims to processes outside that handle-theft boundary. Stronger same-user proof requires process-handle protection plus real duplication attempts. The exact final tree passes npm run check: 160 test files, 1,088 tests, plus generated docs, build, typecheck, lint, and formatting. Independent follow-up review is clean. This issue remains open for the native Node-API Job implementation, queried-limit/membership proof, real descendant/breakaway/normal-exit/cancellation/timeout/parent-close tests, doctor/capability wiring, and the explicit same-user handle-theft decision. taskkill remains a limited fallback and does not satisfy this contract.
Author
Owner

@morluto commented on GitHub (Jul 20, 2026):

Split into focused child issues:

These replace the broad remaining scope in this issue. Closing this parent can happen once all children are closed.

<!-- gh-comment-id:5023380462 --> @morluto commented on GitHub (Jul 20, 2026): Split into focused child issues: - [ ] #320 - [ ] #321 - [ ] #322 - [ ] #323 These replace the broad remaining scope in this issue. Closing this parent can happen once all children are closed.
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#160
No description provided.