fix: robustness and injection hardening in action_kit_commons#459
Conversation
Reading cmd.ProcessState.ExitCode() right after cmd.Run() panics if the runtime binary never started (ProcessState is nil); guard it with a -1 sentinel. unmarshalGuarded indexed output[0] without a length check, panicking on empty runtime output; guard with len(output) > 0.
len(cmds)/len(o.Interfaces) panics when Interfaces is empty. Guard the per-interface-average check with len(o.Interfaces) > 0, matching tcp_reset.
Commands are written one-per-line to tc/iptables -batch stdin and hostnames one-per-line to dig -f-. A newline in an unsanitized interface name, rate, or hostname forwarded from user input would inject an extra privileged directive/query. Reject any batch command containing a newline (at the runner choke points) and any hostname containing whitespace. Also skip un-parseable IPs in DNS resolution instead of appending nil.
Exited()/Stop() on the stress/memfill/diskfill wrappers dereferenced the background state (and the exec cmd's Process) which are only set on a successful Start — calling them after a failed/absent Start panicked. Guard those (Exited reports 'exited' when never started; Stop is a no-op). Also capture the pid before the SIGTERM timer goroutine so it no longer reads cmd.Process concurrently with the exec waiter.
|
Claude finished @joshiste's task in 2m 38s —— View job Review
No unresolved prior review threads exist on this PR, so there was nothing to re-check. Summary: I read through all 16 changed files against
I didn't find any bugs, security issues, or race conditions in the new code. The batch/dig validation is placed correctly (before stdin is ever written to) at every sink, and the nil-guards are consistent across all four process-wrapper implementations. One note on my process: I was not able to run |
|



Summary
The final action-kit kit-audit item: robustness fixes across
action_kit_commons(crash paths + privileged-command injection hardening). Grouped into cohesive commits; all in one PR per request.Fixes
Crash / panic guards
ociruntime:cmd.ProcessState.ExitCode()is read right aftercmd.Run()— nil-deref if the runtime binary never started; guard with a-1sentinel.unmarshalGuardedindexedoutput[0]with no length check — index panic on empty runtime output.netfault(bandwidth/delay/packageLoss/packageCorruption):len(cmds)/len(o.Interfaces)divide-by-zero when an attack has no interfaces; guarded withlen(o.Interfaces) > 0(matchingtcp_reset).stress/memfill/diskfill):Exited()/Stop()dereferenced the background state / execProcessthat are only set on a successfulStart, so calling them after a failed/absentStartpanicked. Guarded —Exited()reports "exited" when never started,Stop()is a no-op.Injection hardening (defense-in-depth)
tc/iptables -batchstdin and hostnames one-per-line todig -f-. A newline in an unsanitized interface name / rate / hostname forwarded from user input would inject an extra privileged directive/query. Reject any batch command containing a newline (singlevalidateBatchCommandsat the 3 runner choke points) and any hostname containing whitespace (at the dig-stdin sink). Allexecalready uses argv, so there's no shell-metachar injection; this closes the batch-stdin vector.nil.Deliberately not changed / follow-ups
ociruntime.ConfigFromEnvironmentkeepslog.Fatal— it's startup env-config parsing (unrecoverable misconfig, fail-fast is the codebase convention); changing it would ripple anerrorreturn through every caller. This PR is scoped to runtime crash paths./simplify, not done here: extract the repeated stress/memfill/diskfillStop/Exitedblock into a sharedutils.StopBackgroundProcesshelper; and express the netfault "≥1 interface" invariant once instead of four inline guards.Verification
go build ./...,go vet ./...,go test ./...pass (modulego/action_kit_commons). New tests:validateBatchCommands(newline/CR rejection) and dnsresolve whitespace-hostname rejection.