mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-17 23:22:27 +02:00
161 lines
4.1 KiB
Go
161 lines
4.1 KiB
Go
//go:build !windows
|
|
|
|
package processguard
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func testID() string {
|
|
return fmt.Sprintf("%08x-1111-4111-8111-%012x", os.Getpid(), uint64(time.Now().UnixNano())&0xffffffffffff)
|
|
}
|
|
func testOptions() Options {
|
|
return Options{CgroupRoot: os.Getenv("CSAI_TEST_CGROUP_ROOT"), MaxProcesses: 64, MemoryMaxBytes: 256 << 20}
|
|
}
|
|
func closeTestGroup(t *testing.T, g Group) {
|
|
t.Helper()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := g.Close(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
func startTestCommand(g Group, command string) (*exec.Cmd, error) {
|
|
cmd := exec.Command("sh", "-c", command)
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
launch, err := g.Prepare(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer launch.Dispose()
|
|
if err = cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = launch.Commit(); err != nil {
|
|
_ = cmd.Process.Kill()
|
|
_ = cmd.Wait()
|
|
return nil, err
|
|
}
|
|
return cmd, nil
|
|
}
|
|
func readPID(t *testing.T, path string) int {
|
|
t.Helper()
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
b, err := os.ReadFile(path)
|
|
if err == nil {
|
|
pid, err := strconv.Atoi(strings.TrimSpace(string(b)))
|
|
if err == nil && pid > 0 {
|
|
return pid
|
|
}
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
t.Fatalf("no PID written to %s", path)
|
|
return 0
|
|
}
|
|
func waitGone(t *testing.T, pid int) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if syscall.Kill(pid, 0) == syscall.ESRCH {
|
|
return
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
t.Fatalf("PID %d survived cleanup", pid)
|
|
}
|
|
func TestGuardianReapsAfterOwnerSIGKILL(t *testing.T) {
|
|
pidPath := filepath.Join(t.TempDir(), "pid")
|
|
owner := exec.Command(os.Args[0], "-test.run=^TestGuardianOwnerHelper$")
|
|
owner.Env = append(os.Environ(), "CSAI_GUARD_TEST_OWNER=1", "CSAI_GUARD_TEST_PID="+pidPath)
|
|
if err := owner.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = owner.Process.Kill(); _ = owner.Wait() }()
|
|
pid := readPID(t, pidPath)
|
|
if err := owner.Process.Kill(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = owner.Wait()
|
|
waitGone(t, pid)
|
|
}
|
|
func TestGuardianOwnerHelper(t *testing.T) {
|
|
if os.Getenv("CSAI_GUARD_TEST_OWNER") != "1" {
|
|
t.Skip("subprocess helper")
|
|
}
|
|
g, err := NewWithOptions(testID(), testOptions())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
command := fmt.Sprintf("echo $$ > %q; exec sleep 300", os.Getenv("CSAI_GUARD_TEST_PID"))
|
|
cmd, err := startTestCommand(g, command)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
go cmd.Wait()
|
|
select {}
|
|
}
|
|
func TestGroupCloseAndAdmission(t *testing.T) {
|
|
g, err := NewWithOptions(testID(), testOptions())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer closeTestGroup(t, g)
|
|
cmd, err := startTestCommand(g, "exec sleep 300")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reaped := make(chan struct{})
|
|
go func() { _ = cmd.Wait(); close(reaped) }()
|
|
closeTestGroup(t, g)
|
|
<-reaped
|
|
waitGone(t, cmd.Process.Pid)
|
|
if _, err = g.Prepare(exec.Command("sh", "-c", "true")); err == nil {
|
|
t.Fatal("closed containment accepted a command")
|
|
}
|
|
}
|
|
func TestLaunchGateOwnerDisappearsBeforeCommit(t *testing.T) {
|
|
if runtime.GOOS == "linux" && testOptions().CgroupRoot != "" {
|
|
t.Skip("cgroup assignment is atomic without a gate")
|
|
}
|
|
g, err := NewWithOptions(testID(), Options{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer closeTestGroup(t, g)
|
|
file := filepath.Join(t.TempDir(), "should-not-exist")
|
|
cmd := exec.Command("sh", "-c", fmt.Sprintf("echo escaped > %q", file))
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
launch, err := g.Prepare(cmd)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = cmd.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
launch.Dispose() // simulate owner crashing before watchdog registration
|
|
_ = cmd.Wait()
|
|
if _, err = os.Stat(file); !os.IsNotExist(err) {
|
|
t.Fatal("unregistered child executed user code")
|
|
}
|
|
}
|
|
func TestRequiredIsolationFailsClosed(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("Windows has Job Objects")
|
|
}
|
|
if _, err := NewWithOptions(testID(), Options{Mode: "required"}); err == nil {
|
|
t.Fatal("required isolation silently downgraded")
|
|
}
|
|
}
|