Skip to content

fix(security): cron/user injection, list JSON escaping, ctx erase UB, vmwrap jailPath (1.1.22)#225

Merged
click0 merged 1 commit into
mainfrom
claude/analyze-test-coverage-nCOJW
Jul 7, 2026
Merged

fix(security): cron/user injection, list JSON escaping, ctx erase UB, vmwrap jailPath (1.1.22)#225
click0 merged 1 commit into
mainfrom
claude/analyze-test-coverage-nCOJW

Conversation

@click0

@click0 click0 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Four clear, testable findings from a second-pass project-wide audit (shell-out surfaces, the root daemon, the allocation/registry layer, and the remaining pure modules). Each fix lives in / is validated by the pure unit layer.

1. cron/user injection — lib/run.cpp (HIGH in the setuid build)

The crontab user from a .crate spec was emitted unvalidated into a root-run file path (/var/cron/tabs/<user>) and a sh -c "chmod … <user>" string. In the legacy setuid build (crate.x, still shipped via install-local) that gives an unprivileged spec author path traversal (../../etc/cron.d/pwn → host root) and shell injection. New RunPure::validateCronUser constrains it to a POSIX-ish username ([A-Za-z0-9_-], ≤32, no leading -) before use; empty is unchanged.

2. Unescaped JSON in crate list --jsonlib/list_pure.cpp (MED)

renderJson interpolated name/hostname/ip/path/ports/mounts without escaping, while the sibling DoctorPure::renderJson routes every string through jsonEscape. Those fields come from the kernel's jail state, so a jail created by another tool with a " in its hostname produced malformed/injectable JSON. Now escaped with the same helper shape.

3. Undefined behavior in FwUsers::dellib/ctx.cpp (MED)

pids.erase(pids.find(pid)) is UB when the pid is absent (erase(end())) — reachable on a stale/truncated context file or a double teardown. Switched to erase(key), a safe no-op for a missing key (matching FwSlots::release).

4. Unvalidated jailPath in vm-wrap — lib/vmwrap_pure.cpp (MED)

validateSpec validated every field except jailPath, which is emitted verbatim into path = "…"; in the jail.conf fragment that jail -c -f runs as root. New validateJailPath requires an absolute path and rejects a double-quote (closes the value) or control byte (injects a directive).

Tests

New cases in run_pure_test, list_pure_test, and vmwrap_pure_test cover each rejection and a clean-value accept. All three suites pass locally.

Deferred (recorded in TODO)

Three higher-touch items from the same audit are not in this PR — they touch the auth and concurrency paths and want on-hardware validation before shipping:

  • daemon/auth.cpp — connection locality (and full trust) decided from a client-supplyable REMOTE_ADDR header lookup instead of req.remote_addr / an explicit UDS-listener flag; potential remote auth bypass if the TCP listener is enabled.
  • lib/network_lease*.cppflock taken on the lease-file inode that writeAllAtomic renames over → duplicate IP + dropped lease row on concurrent same-user crate run.
  • lib/run_services.cppsocket_proxy/proxy paths not confined like the share entries (the correct guard needs care because safePath("/") is itself weak).

Version

Bumps to 1.1.22; CHANGELOG.md + docs/trust-model.{md,uk.md} updated.

🤖 Generated with Claude Code


Generated by Claude Code

… vmwrap jailPath (1.1.22)

Four clear, testable findings from a second-pass project-wide audit:

- cron/user injection (lib/run.cpp): the crontab user from a .crate
  spec was emitted unvalidated into a root-run file path
  (/var/cron/tabs/<user>) and a `sh -c "chmod ... <user>"` string. In
  the legacy setuid build that is path traversal (../../etc/cron.d/pwn
  -> host root) + shell injection from an unprivileged spec author. New
  RunPure::validateCronUser constrains it to [A-Za-z0-9_-], <=32, no
  leading '-'.

- unescaped JSON in `crate list --json` (lib/list_pure.cpp): renderJson
  interpolated name/hostname/ip/path/ports/mounts without escaping while
  the sibling DoctorPure::renderJson uses jsonEscape. Those fields come
  from the kernel's jail state, so a `"` in a hostname produced
  malformed/injectable JSON. Now escaped.

- undefined behavior in FwUsers::del (lib/ctx.cpp):
  pids.erase(pids.find(pid)) is UB when pid is absent (erase(end())),
  reachable on a stale context file or double teardown. Switched to
  erase(key), matching FwSlots::release.

- unvalidated jailPath in vm-wrap (lib/vmwrap_pure.cpp): validateSpec
  checked every field except jailPath, emitted verbatim into
  `path = "...";` in the jail.conf fragment `jail -c -f` runs as root.
  New validateJailPath requires an absolute path, rejects `"` and
  control bytes.

Adds run_pure/list_pure/vmwrap_pure test cases for each. Bumps to
1.1.22; CHANGELOG + trust-model (en/uk) updated. Three higher-touch
audit items (REMOTE_ADDR auth locality, lease flock-on-renamed-inode
race, socket_proxy path confinement) recorded in TODO for a follow-up
that wants on-hardware validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
@click0
click0 force-pushed the claude/analyze-test-coverage-nCOJW branch from f7e9800 to bdcd14a Compare July 7, 2026 19:45
@click0
click0 merged commit 8a493ba into main Jul 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants