Scope sandbox orphan probe to its own script (U5)
Why: The host-wide s.sh grep counted unrelated host scripts as sandbox escapes and failed the fixed combination even when its sandbox script had exited.
Evidence: SELF unrelated sentinel produced the exact original RED, while the scoped probe stayed GREEN under the same sentinel and detected its own positive-control script; full Backend Go tests passed. Receipts: /tmp/dependency-sandbox-host-collision-{red,green}-20261014-01a1345f.json and /tmp/dependency-backend-test-repair-all-go-green-20261014-01a1345f.json.
Impact: Test process identity check only; the sandbox process isolation implementation and failure condition for its own script are unchanged.
Refs: Backend PR #196; Dev PR #537; R8/R14
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -87,6 +88,11 @@ func requireBwrap(t *testing.T) {
|
||||
|
||||
// sandboxRun 经 sandbox 后端执行一段 bash 脚本,返回合并输出与退出描述。
|
||||
func sandboxRun(t *testing.T, script string, timeout time.Duration) (string, string) {
|
||||
out, state, _ := sandboxRunWithPath(t, script, timeout)
|
||||
return out, state
|
||||
}
|
||||
|
||||
func sandboxRunWithPath(t *testing.T, script string, timeout time.Duration) (string, string, string) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
p := filepath.Join(dir, "s.sh")
|
||||
@@ -117,7 +123,7 @@ func sandboxRun(t *testing.T, script string, timeout time.Duration) (string, str
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
state = "exit=timeout"
|
||||
}
|
||||
return out, state
|
||||
return out, state, p
|
||||
}
|
||||
|
||||
func TestSandboxHomeHidden(t *testing.T) {
|
||||
@@ -207,17 +213,84 @@ func TestSandboxForkBombContained(t *testing.T) {
|
||||
|
||||
func TestSandboxOrphansDieWithNamespace(t *testing.T) {
|
||||
requireBwrap(t)
|
||||
out, state := sandboxRun(t, ":(){ :|:& };:; echo gone", 30*time.Second)
|
||||
out, state, scriptPath := sandboxRunWithPath(t, ":(){ :|:& };:; echo gone", 30*time.Second)
|
||||
if state != "exit=0" || !strings.Contains(out, "gone") {
|
||||
t.Fatalf("state=%s out=%q", state, out)
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond) // 给孤儿进程留存活窗口
|
||||
hb, err := exec.Command("bash", "-c", "ps -eo args | grep -c '[s]\\.sh' || true").Output()
|
||||
count, err := sandboxScriptProcessCount(scriptPath)
|
||||
if err != nil {
|
||||
t.Skipf("host ps probe failed: %v", err)
|
||||
}
|
||||
if strings.TrimSpace(string(hb)) != "0" {
|
||||
t.Fatalf("orphaned bomb survived outside the namespace: %s", hb)
|
||||
if count != 0 {
|
||||
t.Fatalf("sandbox script still has %d host processes: %s", count, scriptPath)
|
||||
}
|
||||
}
|
||||
|
||||
func sandboxScriptProcessCount(scriptPath string) (int, error) {
|
||||
out, err := exec.Command("ps", "-eo", "args").Output()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count := 0
|
||||
for _, args := range strings.Split(string(out), "\n") {
|
||||
if strings.Contains(args, scriptPath) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func TestSandboxOrphanProbeScopesScript(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
actual := filepath.Join(dir, "actual", "s.sh")
|
||||
unrelated := filepath.Join(dir, "unrelated", "s.sh")
|
||||
for _, path := range []string{actual, unrelated} {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte("sleep 10 & wait\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
start := func(path string) {
|
||||
proc := exec.Command("bash", path)
|
||||
proc.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
if err := proc.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = syscall.Kill(-proc.Process.Pid, syscall.SIGKILL); _ = proc.Wait() })
|
||||
}
|
||||
start(unrelated)
|
||||
for deadline := time.Now().Add(time.Second); ; {
|
||||
count, err := sandboxScriptProcessCount(unrelated)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count > 0 {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("unrelated control script did not start")
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
if count, err := sandboxScriptProcessCount(actual); err != nil || count != 0 {
|
||||
t.Fatalf("unrelated process counted as this sandbox script: count=%d err=%v", count, err)
|
||||
}
|
||||
start(actual)
|
||||
for deadline := time.Now().Add(time.Second); ; {
|
||||
count, err := sandboxScriptProcessCount(actual)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count > 0 {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("own sandbox script was not detected")
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user