mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-17 23:22:27 +02:00
184 lines
5.3 KiB
Go
184 lines
5.3 KiB
Go
//go:build windows
|
|
|
|
package processguard
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func configurePlatform(o *Options) error {
|
|
if o.CgroupRoot != "" {
|
|
return fmt.Errorf("cgroups are Linux-only")
|
|
}
|
|
if o.Mode == "process_group" {
|
|
return fmt.Errorf("Windows tasks require Job Object containment")
|
|
}
|
|
return nil
|
|
}
|
|
func configureGuardian(cmd *exec.Cmd) {
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP}
|
|
}
|
|
func gatedChildMain(args []string) error {
|
|
return fmt.Errorf("Unix launch gates are unavailable on Windows")
|
|
}
|
|
|
|
type jobGroup struct {
|
|
mu sync.Mutex
|
|
job windows.Handle
|
|
parent windows.Handle
|
|
watcher *watchdog
|
|
closed bool
|
|
broken bool
|
|
}
|
|
|
|
func newPlatformGroup(id string, o Options) (Group, error) {
|
|
if err := configurePlatform(&o); err != nil {
|
|
return nil, err
|
|
}
|
|
name := "Local\\CyberStrikeAI-" + id
|
|
g := &jobGroup{}
|
|
w, err := startWatchdog(watchRequest{Name: name, Options: o}, func() {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
g.broken = true
|
|
if !g.closed && g.job != 0 {
|
|
_ = windows.TerminateJobObject(g.job, 1)
|
|
}
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fail := func(err error) (Group, error) { w.close(); return nil, err }
|
|
namePtr, err := windows.UTF16PtrFromString(name)
|
|
if err != nil {
|
|
return fail(err)
|
|
}
|
|
proc := windows.NewLazySystemDLL("kernel32.dll").NewProc("OpenJobObjectW")
|
|
h, _, callErr := proc.Call(0x0004|0x0008, 0, uintptr(unsafe.Pointer(namePtr)))
|
|
if h == 0 {
|
|
return fail(callErr)
|
|
}
|
|
parent, err := windows.OpenProcess(windows.PROCESS_CREATE_PROCESS|windows.PROCESS_DUP_HANDLE, false, uint32(w.cmd.Process.Pid))
|
|
if err != nil {
|
|
windows.CloseHandle(windows.Handle(h))
|
|
return fail(err)
|
|
}
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
if g.broken {
|
|
windows.CloseHandle(parent)
|
|
windows.CloseHandle(windows.Handle(h))
|
|
return fail(fmt.Errorf("job guardian exited during setup"))
|
|
}
|
|
g.job = windows.Handle(h)
|
|
g.parent = parent
|
|
g.watcher = w
|
|
return g, nil
|
|
}
|
|
func (g *jobGroup) Name() string { return "windows_job" }
|
|
func (g *jobGroup) Prepare(cmd *exec.Cmd) (*Launch, error) {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
if g.closed || g.broken {
|
|
return nil, fmt.Errorf("job is closed")
|
|
}
|
|
if _, err := g.watcher.send(watchRequest{Op: "ping"}); err != nil {
|
|
return nil, err
|
|
}
|
|
if cmd.SysProcAttr == nil {
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
|
}
|
|
// Windows inherits the job at CreateProcess time from this parent. The
|
|
// guardian joined the job BEFORE acknowledging readiness, closing the
|
|
// Start-then-Assign race and its suspended-process crash window.
|
|
cmd.SysProcAttr.ParentProcess = syscall.Handle(g.parent)
|
|
return &Launch{Commit: func() error { return nil }, Dispose: func() {}}, nil
|
|
}
|
|
func (g *jobGroup) Release(pid int) error { return nil }
|
|
func (g *jobGroup) Close(ctx context.Context) error {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
if g.closed {
|
|
return nil
|
|
}
|
|
if err := windows.TerminateJobObject(g.job, 1); err != nil {
|
|
return err
|
|
}
|
|
type accounting struct {
|
|
TotalUser, TotalKernel, PeriodUser, PeriodKernel int64
|
|
PageFaults, TotalProcesses, ActiveProcesses, Terminated uint32
|
|
}
|
|
for {
|
|
var info accounting
|
|
if err := windows.QueryInformationJobObject(g.job, windows.JobObjectBasicAccountingInformation, uintptr(unsafe.Pointer(&info)), uint32(unsafe.Sizeof(info)), nil); err != nil {
|
|
return err
|
|
}
|
|
if info.ActiveProcesses == 0 {
|
|
break
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(10 * time.Millisecond):
|
|
}
|
|
}
|
|
g.closed = true
|
|
return errors.Join(g.watcher.close(), windows.CloseHandle(g.parent), windows.CloseHandle(g.job))
|
|
}
|
|
func guardianMain(dec *json.Decoder, enc *json.Encoder) error {
|
|
var req watchRequest
|
|
if err := dec.Decode(&req); err != nil {
|
|
return err
|
|
}
|
|
name, err := windows.UTF16PtrFromString(req.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
job, err := windows.CreateJobObject(nil, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer windows.CloseHandle(job)
|
|
limits := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{}
|
|
limits.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | windows.JOB_OBJECT_LIMIT_ACTIVE_PROCESS | windows.JOB_OBJECT_LIMIT_JOB_MEMORY
|
|
limits.BasicLimitInformation.ActiveProcessLimit = uint32(req.Options.MaxProcesses + 1)
|
|
limits.JobMemoryLimit = uintptr(req.Options.MemoryMaxBytes)
|
|
if _, err = windows.SetInformationJobObject(job, windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&limits)), uint32(unsafe.Sizeof(limits))); err != nil {
|
|
return err
|
|
}
|
|
if req.Options.CPUQuotaMicros > 0 {
|
|
rate := req.Options.CPUQuotaMicros / 10 / int64(runtime.NumCPU())
|
|
if rate < 1 {
|
|
rate = 1
|
|
}
|
|
if rate > 10000 {
|
|
rate = 10000
|
|
}
|
|
cpu := struct{ Flags, Rate uint32 }{Flags: 1 | 4, Rate: uint32(rate)}
|
|
if _, err = windows.SetInformationJobObject(job, windows.JobObjectCpuRateControlInformation, uintptr(unsafe.Pointer(&cpu)), uint32(unsafe.Sizeof(cpu))); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err = windows.AssignProcessToJobObject(job, windows.CurrentProcess()); err != nil {
|
|
return err
|
|
}
|
|
return serveGuardian(dec, enc, func(req watchRequest) error {
|
|
if req.Op != "ping" {
|
|
return fmt.Errorf("unknown guardian command")
|
|
}
|
|
return nil
|
|
}, func() error { return windows.TerminateJobObject(job, uint32(os.Getpid())) })
|
|
}
|