fix(spawner): resolve privileged wrappers to absolute paths + log spawn context #131

Merged
clawdie merged 1 commit from absolute-spawn-wrappers into main 2026-06-21 17:12:04 +02:00
Owner

Targets the FreeBSD colibri-daemon spawn Permission denied (os error 13) (works from shell, fails inside the daemon).

Root-cause hypothesis this addresses: the jail spawn path launches its wrapper by bare name (sudo/jexec/mdo, priv_wrap()) and relies on execvp + the daemon's inherited PATH. Under daemon(8)/rc that PATH is often empty or reordered, so execvp either misses the binary (ENOENT) or hits a non-executable same-named entry first and returns EACCES — exactly the observed failure, and exactly why the same command works from an interactive shell.

Changes

  • resolve_program() absolutizes a bare program name against a fixed search list (/usr/local/sbin,/usr/local/bin,/usr/sbin,/usr/bin,/sbin,/bin); first regular executable wins. Slash-bearing paths pass through untouched; nothing-found falls back to the bare name so the OS still reports a real error.
  • spawn_prepared_child now logs resolved program, requested name, full argv, and PATH before spawning — the old "attempting spawn" log had no spawn-context detail, which is why the failure was opaque.

Why it helps even if a MAC policy is also involved: it removes PATH-search EACCES as a variable, so a truss/ktrace run can attribute any remaining denial to an actual kernel/MAC policy on execve rather than to PATH resolution. Note: capsicum is already ruled out (not used anywhere in the crate, and capability-mode denial returns ECAPMODE, not EACCES).

Tests: resolve_program pass-through, absolutization (sh), and missing-name fallback. cargo clippy -p colibri-daemon clean; spawner tests green.

Next step for the FreeBSD host: rebuild, rerun under truss -f, and read the execve(...) line — the new log prints the exact resolved path it attempts.

🤖 Generated with Claude Code

Targets the FreeBSD `colibri-daemon` spawn `Permission denied (os error 13)` (works from shell, fails inside the daemon). **Root-cause hypothesis this addresses:** the jail spawn path launches its wrapper by **bare name** (`sudo`/`jexec`/`mdo`, `priv_wrap()`) and relies on `execvp` + the daemon's inherited `PATH`. Under `daemon(8)`/rc that `PATH` is often empty or reordered, so `execvp` either misses the binary (`ENOENT`) or hits a non-executable same-named entry first and returns **`EACCES`** — exactly the observed failure, and exactly why the same command works from an interactive shell. **Changes** - `resolve_program()` absolutizes a bare program name against a fixed search list (`/usr/local/sbin`,`/usr/local/bin`,`/usr/sbin`,`/usr/bin`,`/sbin`,`/bin`); first regular executable wins. Slash-bearing paths pass through untouched; nothing-found falls back to the bare name so the OS still reports a real error. - `spawn_prepared_child` now logs **resolved program, requested name, full argv, and PATH** before spawning — the old `"attempting spawn"` log had no spawn-context detail, which is why the failure was opaque. **Why it helps even if a MAC policy is also involved:** it removes PATH-search `EACCES` as a variable, so a `truss`/`ktrace` run can attribute any *remaining* denial to an actual kernel/MAC policy on `execve` rather than to PATH resolution. Note: capsicum is already ruled out (not used anywhere in the crate, and capability-mode denial returns `ECAPMODE`, not `EACCES`). **Tests:** `resolve_program` pass-through, absolutization (`sh`), and missing-name fallback. `cargo clippy -p colibri-daemon` clean; spawner tests green. Next step for the FreeBSD host: rebuild, rerun under `truss -f`, and read the `execve(...)` line — the new log prints the exact resolved path it attempts. 🤖 Generated with Claude Code
clawdie added 1 commit 2026-06-21 17:11:10 +02:00
fix(spawner): resolve privileged wrappers to absolute paths + log spawn context
Some checks failed
CI / rust (pull_request) Has been cancelled
CI / markdown (pull_request) Has been cancelled
CI / port (pull_request) Has been cancelled
CI / agent-jail-pkgs (pull_request) Has been cancelled
78be056b62
The jail spawn path launches its wrapper by bare name (sudo / jexec / mdo)
and relies on execvp + the daemon's inherited PATH. Under daemon(8)/rc the
PATH is often empty or reordered, so execvp either misses the binary (ENOENT)
or hits a non-executable same-named entry first and returns EACCES — the spawn
"Permission denied" seen on FreeBSD even though the identical command runs from
a shell.

- resolve_program() absolutizes a bare program name against a fixed search
  list (first regular executable wins), leaving slash-bearing paths untouched
  and falling back to the bare name so the OS still reports a real error.
- spawn_prepared_child now logs the resolved program, requested name, full
  argv, and PATH before spawning. The previous "attempting spawn" log carried
  no spawn-context detail, which is why the failure was opaque.

This removes the PATH-search EACCES as a variable so a truss/ktrace run can
attribute any remaining denial to an actual kernel/MAC policy instead.

Tests: resolve_program pass-through, absolutization, and missing-name fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clawdie merged commit a1e6d64a27 into main 2026-06-21 17:12:04 +02:00
clawdie deleted branch absolute-spawn-wrappers 2026-06-21 17:12:05 +02:00
Sign in to join this conversation.
No reviewers
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: clawdie/colibri#131
No description provided.