fix(security): cron/user injection, list JSON escaping, ctx erase UB, vmwrap jailPath (1.1.22)#225
Merged
Merged
Conversation
… 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
force-pushed
the
claude/analyze-test-coverage-nCOJW
branch
from
July 7, 2026 19:45
f7e9800 to
bdcd14a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/userinjection —lib/run.cpp(HIGH in the setuid build)The crontab user from a
.cratespec was emitted unvalidated into a root-run file path (/var/cron/tabs/<user>) and ash -c "chmod … <user>"string. In the legacy setuid build (crate.x, still shipped viainstall-local) that gives an unprivileged spec author path traversal (../../etc/cron.d/pwn→ host root) and shell injection. NewRunPure::validateCronUserconstrains it to a POSIX-ish username ([A-Za-z0-9_-], ≤32, no leading-) before use; empty is unchanged.2. Unescaped JSON in
crate list --json—lib/list_pure.cpp(MED)renderJsoninterpolatedname/hostname/ip/path/ports/mountswithout escaping, while the siblingDoctorPure::renderJsonroutes every string throughjsonEscape. 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::del—lib/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 toerase(key), a safe no-op for a missing key (matchingFwSlots::release).4. Unvalidated
jailPathin vm-wrap —lib/vmwrap_pure.cpp(MED)validateSpecvalidated every field exceptjailPath, which is emitted verbatim intopath = "…";in the jail.conf fragment thatjail -c -fruns as root. NewvalidateJailPathrequires 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, andvmwrap_pure_testcover 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-supplyableREMOTE_ADDRheader lookup instead ofreq.remote_addr/ an explicit UDS-listener flag; potential remote auth bypass if the TCP listener is enabled.lib/network_lease*.cpp—flocktaken on the lease-file inode thatwriteAllAtomicrenames over → duplicate IP + dropped lease row on concurrent same-usercrate run.lib/run_services.cpp—socket_proxy/proxypaths not confined like theshareentries (the correct guard needs care becausesafePath("/")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