[GH-ISSUE #7143] Linux process-ownership cleanup fails when /proc/<pid>/environ is unreadable across user namespaces (EACCES) #4286

Open
opened 2026-10-31 00:48:32 +00:00 by bsakaguchi · 3 comments
Owner

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

Environment

  • rea-agents 6.7.0, Linux (Fedora 44 kernel 7.2.9, inside a rootless podman/distrobox container)
  • Ghidra 12.1.4 + Temurin JRE 21
  • MCP mode (rea mcp)

Summary

open_binary (Ghidra provider) fails with cleanup_incomplete because REA's POSIX process-ownership verification reads /proc/<pid>/environ of every live process and treats EACCES as a hard failure. In environments with bwrap-sandboxed processes in separate user namespaces (browsers, image loaders, desktop portals), those processes' environ is mode 0400 owned by the same uid yet returns EACCES across the namespace boundary. REA classifies them as strict failures instead of withheld/unverified, so Ghidra cleanup never succeeds even though the analysis itself completed.

Reproduction

  1. Have any bwrap (or other user-namespace) process running as the same uid.
  2. rea mcp → open_binary on any ELF.
  3. Result: cleanup_incomplete, reason: process ownership token could not be read for N live process(es): environment_errno_EACCES=N, with candidate PIDs that are unrelated bwrap processes.

Evidence

  • /proc/<pid>/environ mode 0400, owner == current uid, but open() returns EACCES (errno 13) for bwrap processes in other user namespaces.
  • Running REA under unshare --user --pid --fork --mount-proc (private PID namespace) makes open_binary succeed end-to-end, confirming the cause is the process-table scan, not Ghidra/Java.

Suggested fix

In ProcessOwnership / scanTokenOwnedProcesses, treat an EACCES (and EPERM) failure reading /proc/<pid>/environ as "environment withheld from inspection" (i.e. unverified) when the process is not a descendant of the REA launcher and not in a sampled/owned process group — mirroring the existing Darwin environmentWithheldFromInspection handling. Alternatively, scope the cleanup scan to the REA process's own PID namespace / descendants rather than the global process table.

Workaround used

Launch rea mcp under unshare --user --pid --fork --mount-proc so its process table contains only its own processes.

Originally created by @otherdrums on GitHub (Oct 25, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7143 ## Environment - rea-agents 6.7.0, Linux (Fedora 44 kernel 7.2.9, inside a rootless podman/distrobox container) - Ghidra 12.1.4 + Temurin JRE 21 - MCP mode (`rea mcp`) ## Summary `open_binary` (Ghidra provider) fails with `cleanup_incomplete` because REA's POSIX process-ownership verification reads `/proc/<pid>/environ` of *every* live process and treats `EACCES` as a hard failure. In environments with bwrap-sandboxed processes in separate user namespaces (browsers, image loaders, desktop portals), those processes' `environ` is mode `0400` owned by the same uid yet returns `EACCES` across the namespace boundary. REA classifies them as strict failures instead of withheld/unverified, so Ghidra cleanup never succeeds even though the analysis itself completed. ## Reproduction 1. Have any bwrap (or other user-namespace) process running as the same uid. 2. `rea mcp` → `open_binary` on any ELF. 3. Result: `cleanup_incomplete`, `reason: process ownership token could not be read for N live process(es): environment_errno_EACCES=N`, with candidate PIDs that are unrelated bwrap processes. ## Evidence - `/proc/<pid>/environ` mode `0400`, owner == current uid, but `open()` returns `EACCES` (errno 13) for bwrap processes in other user namespaces. - Running REA under `unshare --user --pid --fork --mount-proc` (private PID namespace) makes `open_binary` succeed end-to-end, confirming the cause is the process-table scan, not Ghidra/Java. ## Suggested fix In `ProcessOwnership` / `scanTokenOwnedProcesses`, treat an `EACCES` (and `EPERM`) failure reading `/proc/<pid>/environ` as "environment withheld from inspection" (i.e. unverified) when the process is not a descendant of the REA launcher and not in a sampled/owned process group — mirroring the existing Darwin `environmentWithheldFromInspection` handling. Alternatively, scope the cleanup scan to the REA process's own PID namespace / descendants rather than the global process table. ## Workaround used Launch `rea mcp` under `unshare --user --pid --fork --mount-proc` so its process table contains only its own processes.
Author
Owner

@XinwangTao commented on GitHub (Oct 25, 2026):

Good catch — treating EACCES on /proc//environ as a hard failure is too aggressive for namespace-separated processes. Since the file is mode 0400 and owned by the same uid, an EACCES here usually means the process lives in another user namespace (bwrap/rootless containers), not that REA lacks permission. Suggestion: when read fails with EACCES, fall back to checking ownership via stat(/proc/) or skip that process with a warning instead of aborting the whole cleanup with cleanup_incomplete; only treat genuinely missing processes (/proc/ gone) as a real failure.

<!-- gh-comment-id:6399388173 --> @XinwangTao commented on GitHub (Oct 25, 2026): Good catch — treating EACCES on /proc/<pid>/environ as a hard failure is too aggressive for namespace-separated processes. Since the file is mode 0400 and owned by the same uid, an EACCES here usually means the process lives in another user namespace (bwrap/rootless containers), not that REA lacks permission. Suggestion: when read fails with EACCES, fall back to checking ownership via stat(/proc/<pid>) or skip that process with a warning instead of aborting the whole cleanup with cleanup_incomplete; only treat genuinely missing processes (/proc/<pid> gone) as a real failure.
Author
Owner

@WenyuChiou commented on GitHub (Oct 25, 2026):

I reproduced the underlying Linux permission condition in an isolated VM using disposable synthetic children—not the full REA/Ghidra failure.

Procedure: use a normal child as a same-namespace control, then create two children in separate sibling user namespaces with unshare --user --map-root-user. Have one inspect itself and the other synthetic child; also inspect that target from the parent/ancestor namespace. The children had a minimal clean environment. The probe compared UID/GID and environ file ownership, attempted to open environ without reading any bytes, and inspected namespace/ID-map metadata. Every child was reaped. The probe had a 9-second outer deadline and a 256 MiB per-process address-space limit.

Observation Result
Same-namespace control environ opens successfully
Parent/ancestor reading the child Opens successfully despite a different user namespace
Sibling namespace reading the target EACCES, despite matching visible UID/GID and file owner
Sibling reading the target's ns/user symlink Also EACCES

ID maps remained readable, with no unmapped-ID sentinel. No environment contents, unrelated processes, or real analysis targets were inspected.

This supports the reported permission condition, but file ownership or differing ID maps alone do not establish namespace ancestry or cleanup authority. The Linux ptrace rules distinguish same-namespace capability checks from authority in the target namespace.

At 6381aa0b, REA keeps otherwise unexplained EACCES fail-closed. #6220 handles credential/non-dumpable restrictions; #6983 addresses a separate missing-procfs exit race. I have not changed production code or proposed ignoring permission errors.

Maintainer question: what additional evidence or contract should be required before this same-ID, cross-namespace class can be reported as unverified rather than blocking cleanup? Any change should retain generation-bound identity and the existing related/retained-process safeguards.

<!-- gh-comment-id:6399680726 --> @WenyuChiou commented on GitHub (Oct 25, 2026): I reproduced the underlying Linux permission condition in an isolated VM using disposable synthetic children—not the full REA/Ghidra failure. **Procedure:** use a normal child as a same-namespace control, then create two children in separate sibling user namespaces with `unshare --user --map-root-user`. Have one inspect itself and the other synthetic child; also inspect that target from the parent/ancestor namespace. The children had a minimal clean environment. The probe compared UID/GID and `environ` file ownership, attempted to open `environ` **without reading any bytes**, and inspected namespace/ID-map metadata. Every child was reaped. The probe had a 9-second outer deadline and a 256 MiB per-process address-space limit. | Observation | Result | |---|---| | Same-namespace control | `environ` opens successfully | | Parent/ancestor reading the child | Opens successfully despite a different user namespace | | Sibling namespace reading the target | `EACCES`, despite matching visible UID/GID and file owner | | Sibling reading the target's `ns/user` symlink | Also `EACCES` | ID maps remained readable, with no unmapped-ID sentinel. No environment contents, unrelated processes, or real analysis targets were inspected. This supports the reported permission condition, but file ownership or differing ID maps alone do not establish namespace ancestry or cleanup authority. The [Linux ptrace rules](https://man7.org/linux/man-pages/man2/ptrace.2.html) distinguish same-namespace capability checks from authority in the target namespace. At `6381aa0b`, REA keeps otherwise unexplained `EACCES` fail-closed. #6220 handles credential/non-dumpable restrictions; #6983 addresses a separate missing-procfs exit race. I have not changed production code or proposed ignoring permission errors. **Maintainer question:** what additional evidence or contract should be required before this same-ID, cross-namespace class can be reported as `unverified` rather than blocking cleanup? Any change should retain generation-bound identity and the existing related/retained-process safeguards.
Author
Owner

@hsaki11 commented on GitHub (Oct 25, 2026):

I opened a PR for this: #7145

It treats same-UID processes in another user namespace (e.g. bwrap sandboxes) as unverified during the cleanup scan, detected by comparing /proc/<pid>/uid_map with REA's own. Related/descendant processes still fail closed.

I could not reproduce the EACCES myself, so could you test the branch and tell me whether open_binary succeeds? If it still fails, the output of ls -Z /proc/<pid>/environ and cat /proc/self/attr/current would help check whether SELinux is involved.

<!-- gh-comment-id:6399826375 --> @hsaki11 commented on GitHub (Oct 25, 2026): I opened a PR for this: #7145 It treats same-UID processes in another user namespace (e.g. bwrap sandboxes) as unverified during the cleanup scan, detected by comparing `/proc/<pid>/uid_map` with REA's own. Related/descendant processes still fail closed. I could not reproduce the EACCES myself, so could you test the branch and tell me whether `open_binary` succeeds? If it still fails, the output of `ls -Z /proc/<pid>/environ` and `cat /proc/self/attr/current` would help check whether SELinux is involved.
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#4286
No description provided.