Repository navigation
Description
Linux: ancestor walk fails unconditionally in Docker userns-remap containers (single-uid mapping, / owned by nobody/65534)
Version
Would affect any version from v0.10.0 onward (the daemon coordination layer). Not currently installed — cannot run due to this issue.
Platform
Linux (x64): Docker Desktop 29.x on Windows 11 WSL2, container running Debian 13 (trixie), kernel 6.18.35.2-microsoft-standard-WSL2
Install channel
Binary (attempted previously with v0.10.6; same issue expected on v0.10.8)
What happened, and what did you expect?
Every cbm process exits immediately with secure CLI coordination could not be created (endpoint). The ancestor walk rejects every possible directory path because no ancestor directory from /home/kirocrew to / is owned by uid 0 or uid 1000 — they are all owned by uid 65534 (nobody).
Expected: cbm should be able to run in a standard Docker container that uses user namespace remapping (a single-uid mapping), which is a common production Docker security pattern.
Root cause
Docker's userns-remap feature (or equivalently, Podman's rootless mode) creates a user namespace where only a single uid is mapped:
# /proc/self/uid_map
1000 1000 1
This means:
- The process runs as uid 1000 (kirocrew)
- uid 0 does not exist inside the container namespace
- All filesystem objects owned by uids outside the mapped range appear as
nobody(65534) per kernel overflow behavior
The directory ownership chain:
mode=755 owner=65534:65534 / ← nobody (unmapped host root)
mode=755 owner=65534:65534 /home ← nobody
mode=700 owner=1000:1000 /home/kirocrew ← ✅ current user
mode=1777 owner=65534:65534 /tmp ← nobody (not uid 0!)
The ancestor walk (posix_directory_parent_secure) requires each ancestor to be owned by either:
- The current process euid (1000), OR
- uid 0 (root)
Neither condition is satisfiable for /, /home, or /tmp because:
- uid 0 doesn't exist in the namespace (no mapping)
- These directories show owner uid 65534 (the kernel's overflow uid for unmapped uids)
The /tmp sticky-bit special case also fails: it requires root-owned (uid 0) + sticky, but /tmp is uid 65534 + sticky.
CBM_RUNTIME_DIR (added in v0.10.5 for #1672) cannot help because any path ultimately traverses / or /home, both of which are uid 65534.
Reproduction
- Run any Docker container with a single-uid user namespace mapping:
# docker-compose.yml or docker run equivalent # The container uses userns-remap or is started with --userns=host # with a USER directive that maps only one uid
- Verify the uid_map:
cat /proc/self/uid_map→ shows1000 1000 1 - Verify
/ownership:stat -c '%u' /→ shows65534 - Attempt to run any cbm command → fails at ancestor walk
Suggested fix
When the process detects it is running inside a user namespace with a restricted uid_map (single-uid or no uid-0 mapping), the ancestor walk should relax its ownership requirement for ancestors that show the overflow uid (65534/nobody). Specifically:
- Detect userns: read
/proc/self/uid_map— if uid 0 is not mapped (or maps to an unprivileged host uid), the container has no real root. - Relax ancestor check: in this case, accept ancestors owned by the overflow uid (65534) as equivalent to "owned by the host root that we cannot see". The security guarantee (no local user can swap a path component) is maintained because the container is already isolated — no other uid can exist to attack the path.
- Keep strict checks for the private directory itself (
CBM_RUNTIME_DIR,CBM_CACHE_DIR) — these must still be owned by the process euid (1000).
Alternative: provide a CBM_SKIP_ANCESTOR_WALK=1 escape hatch for container environments where the operator accepts the risk, similar to how git provides safe.directory for ownership mismatches.
Related issues
- Linux: no product-build way to relocate the daemon runtime dir off /tmp; Kubernetes emptyDir (0777, no sticky bit) fails the ancestor walk (v0.10.4) #1672 — Kubernetes emptyDir ancestor walk (fixed in v0.10.5 via
CBM_RUNTIME_DIR) - No product-build way to relocate the daemon runtime directory when %LOCALAPPDATA% has a capability-SID ACE #1574 — Windows: no product-build relocation hook (fixed)
- CBM_CACHE_DIR ignored; daemon still requires /private/tmp/cbm-daemon-<uid> on macOS #1621 — macOS: CBM_CACHE_DIR ignored for relocation (fixed)
- Windows: ancestor walk refuses app container / capability SIDs, so 0.10.0 cannot start inside a containerized host #1533 — Windows containers: AppContainer SID refusal (open)
- No such xattr: com.apple.quarantine #1537 — group-writable ancestor refusal (fixed in v0.10.3)
Environment details
OS: Debian GNU/Linux 13 (trixie)
Kernel: 6.18.35.2-microsoft-standard-WSL2
Container runtime: Docker Desktop (overlay2)
uid_map: 1000 1000 1
gid_map: 1000 1000 1
Process: uid=1000(kirocrew) gid=1000(kirocrew)
Activity
Merge pull request #2138 from DeusData/fix/decision-bd
DeusData commentedon Sep 9, 2026
Fixed on main by #2138 (merge 055fbb7). Inside a single-uid user namespace (unshare -U --map-current-user, podman --userns=keep-id) the root-owned ancestors (/, /tmp) appear owned by the kernel overflow uid; the daemon now tolerates an overflow-owned ANCESTOR only when /proc/self/uid_map is a single-uid map (one line, count==1, inside==euid) and /proc/sys/kernel/overflowuid is readable, with no env hatch. The directories that hold data stay strict == geteuid(), so a host-squatted overflow-owned private dir is still refused. Thanks for the report.
fix(daemon): accept single-uid userns overflow-owned ancestors (DeusD…
fix(test,daemon): make the userns probe revert-sensitive and stop a f…
fix(daemon,ci): stop caching the overflow uid, and run the userns gua…
ci(test): report whether unprivileged userns is actually available
Merge pull request #2195 from DeusData/fix/userns-smoke-spawn
DeusData commentedon Sep 13, 2026
Follow-up landed in
339b3f40(PR #2195) — this issue stays closed, but the guard behind it was not doing its job and now is.The smoke test could never have passed. It forked a child, entered a user namespace, and called
cbm_daemon_ipc_private_directory_secure()directly in that forked child. But the overflow uid was memoised per process viapthread_once, andunshare(CLONE_NEWUSER)is exactly what rewrites/proc/self/uid_map— whilepthread_oncestate survivesfork()already marked done. Seven earlier call sites primed the cache, so the child inherited the host answer and refused. The test reported that as a product refusal while the product was correct.Three things changed:
/procstate was "immutable". A new test counts derivations and fails by name if a cache ever returns.ubuntu-22.04legs, which are broad-matrix only — so it executed in no pull request, on images retiring 2027-04-17, after which it would have become a permanent silent skip. Asysctlon the ubuntu-24.04 legs lifts the AppArmor restriction; CI now printsCBM_USERNS_AVAILABLE=yesas evidence rather than leaving it to inference.The tolerance itself is unchanged and still correct. Its justification was overstated, though: the overflow uid is what every unmapped host uid maps to, not only root, so "overflow-owned" does not prove "root-created". The comment now states the real, narrower guarantee — no principal reachable from inside the namespace can create or mutate such an ancestor — and notes the bound: a hostile host-side ancestor owner reaches denial of service and socket-path control, never the leaf, which stays
0700and euid-rechecked. Narrowing the tolerance was considered and rejected because legitimate root-owned01777 /tmpis accepted through the same branch, so it would have reverted this fix in practice.Verified in a real namespace as a non-root uid: with the tolerance stubbed off the test goes red, with it restored it passes — so the test genuinely binds this decision, not merely the test change.
fix(test): exec the DeusData#1830 userns probe instead of forking a p…
fix(daemon): accept single-uid userns overflow-owned ancestors (DeusD…
fix(test): exec the DeusData#1830 userns probe instead of forking a p…
fix(test,daemon): make the userns probe revert-sensitive and stop a f…
fix(daemon,ci): stop caching the overflow uid, and run the userns gua…
fix(test): exec the DeusData#1830 userns probe instead of forking a p…
fix(test,daemon): make the userns probe revert-sensitive and stop a f…
fix(daemon,ci): stop caching the overflow uid, and run the userns gua…
fix(daemon): explain foreign-owned cache ancestors and warn on networ…