Fix -e/--exec namespace resolution on kernels without CONFIG_CHECKPOINT_RESTORE

/proc/<pid>/task/<pid>/children doesn't exist on every kernel (confirmed
missing on a real Android target), so resolve_namespace_pid() fell back to
the outer bwrap pid itself and nsenter ended up with no namespace flags at
all. Add a portable fallback that scans /proc/<n>/stat for the child whose
ppid matches, the same information pstree uses to build its tree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-29 07:42:02 +00:00
parent fa0bf13732
commit 1c186b0365
2 changed files with 82 additions and 7 deletions
+15 -3
View File
@@ -422,9 +422,21 @@ Source layout (all under `src/`):
`/proc/<outer_pid>/ns/{pid,uts,ipc,cgroup}` against this process's own showed
them identical, while only `mnt`/`user` differed. `resolve_namespace_pid()`
reads `/proc/<pid>/task/<pid>/children` (the direct-children list `procfs`
exposes) to find that real inner process and joins *its* namespaces instead
falls back to `pid` itself (best-effort, not fatal) if that file can't be
read. For each of `{mnt→--mount, uts→--uts, ipc→--ipc, pid→--pid,
exposes) to find that real inner process and joins *its* namespaces instead.
**Real bug found via testing on a real target device, not assumed**: that
file requires `CONFIG_CHECKPOINT_RESTORE`, which not every kernel enables —
confirmed absent (not just unreadable — the file doesn't exist at all) on a
real Android device, where `-e/--exec` then fell back to the outer `bwrap`
pid itself and failed outright (`nsenter: no namespace specified`, since
every namespace type either matched the outer process's own or couldn't be
read at all). Fixed by adding `find_child_by_scanning_proc()`, a portable
fallback used only when the children file is missing/empty: scans
`/proc/<n>/stat` for any process whose ppid field equals `pid` — the same
information `pstree` itself reads to build its tree, which is how the actual
sandboxed child was located and confirmed correct on the same device via a
manual `nsenter -t <child_pid> -a -- /bin/sh` before the fix was written.
Picks the lowest matching pid if more than one child exists, for a
deterministic result. For each of `{mnt→--mount, uts→--uts, ipc→--ipc, pid→--pid,
cgroup→--cgroup, user→--user}` (`net` deliberately excluded — this project
never isolates networking, see `bwrap.cpp` below), `readlink()`s both
`/proc/<ns_pid>/ns/<type>` and `/proc/self/ns/<type>` and only passes