Files
Charles Yao 231e423431 Integrate validated Builder backend testing and CI repairs
Track backend integration, Ruff cleanup and migration reconciliation in ENG-1271. Retain required copy contracts without implementing frontend interactions.
2026-10-20 15:40:06 -07:00

179 lines
6.2 KiB
Go

//go:build linux
package testscope
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"golang.org/x/sys/unix"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"unsafe"
)
func ValidateDelegation(root string) error {
var st unix.Statfs_t
if err := unix.Statfs(root, &st); err != nil {
return err
}
if st.Type != unix.CGROUP2_SUPER_MAGIC {
return errors.New("delegation is not cgroup v2")
}
controllers, err := os.ReadFile(filepath.Join(root, "cgroup.subtree_control"))
if err != nil {
return err
}
for _, c := range []string{"cpu", "memory", "pids"} {
if !strings.Contains(" "+strings.TrimSpace(string(controllers))+" ", " "+c+" ") {
return fmt.Errorf("missing delegated %s controller", c)
}
}
// A dedicated delegated parent must have finite resource ceilings itself.
for _, name := range []string{"memory.max", "pids.max", "cpu.max"} {
b, err := os.ReadFile(filepath.Join(root, name))
if err != nil {
return err
}
if strings.HasPrefix(strings.TrimSpace(string(b)), "max") {
return fmt.Errorf("unbounded delegated parent %s", name)
}
}
return nil
}
func createCgroup(root, id string, l Limits) (string, error) {
if err := ValidateDelegation(root); err != nil {
return "", err
}
if !safeID.MatchString(id) {
return "", errors.New("invalid cgroup identity")
}
path := filepath.Join(root, id)
if err := os.Mkdir(path, 0700); err != nil {
return "", err
}
good := false
defer func() {
if !good {
_ = os.Remove(path)
}
}()
for name, value := range map[string]string{"cpu.max": fmt.Sprintf("%d 100000", l.CPUQuota), "memory.max": strconv.FormatInt(l.MemoryBytes, 10), "memory.swap.max": "0", "memory.oom.group": "1", "pids.max": strconv.FormatInt(l.PIDs, 10)} {
if err := os.WriteFile(filepath.Join(path, name), []byte(value), 0600); err != nil {
return "", err
}
}
f, err := os.OpenFile(filepath.Join(path, "cgroup.kill"), os.O_WRONLY, 0)
if err != nil {
return "", err
}
_ = f.Close()
good = true
return path, nil
}
// KillCgroup returns success only after the kernel reports every descendant gone.
func KillCgroup(path string, timeout time.Duration) error {
if err := os.WriteFile(filepath.Join(path, "cgroup.kill"), []byte("1"), 0600); err != nil {
return err
}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
b, err := os.ReadFile(filepath.Join(path, "cgroup.events"))
if err != nil {
return err
}
if strings.Contains(string(b), "populated 0") {
return nil
}
time.Sleep(10 * time.Millisecond)
}
return errors.New("scope cleanup timeout: quarantined")
}
// CheckOutputSpool counts public stdout and protected stderr together. The
// tmpfs is the hard disk ceiling; this lower aggregate ceiling rejects overflow
// before a completed job can be reported as success.
func CheckOutputSpool(root string, maximum int64) error {
entries, err := os.ReadDir(root)
if err != nil {
return err
}
var total int64
for _, entry := range entries {
if !entry.IsDir() {
continue
}
for _, name := range []string{"output.log", "stderr.log"} {
info, err := os.Stat(filepath.Join(root, entry.Name(), name))
if os.IsNotExist(err) {
continue
}
if err != nil {
return err
}
total += info.Size()
if total > maximum {
return errors.New("native_scope_output_limit_exceeded")
}
}
}
return nil
}
// OpenOutputBudget uses the existing worker SQLite catalog, not an independent
// execution owner. Both PTY and stdio writers share the same immutable budget.
func OpenOutputBudget(configPath string, destination io.Writer) (io.Writer, func(), error) {
data, err := os.ReadFile(configPath)
if err != nil {
return nil, nil, err
}
var cfg WorkerConfig
if err = json.Unmarshal(data, &cfg); err != nil {
return nil, nil, err
}
db, err := sql.Open("sqlite", fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=synchronous(FULL)", filepath.Join(cfg.Directory, "state", "shellctl.db")))
if err != nil {
return nil, nil, err
}
db.SetMaxOpenConns(1)
writer := NewBudgetWriter(destination, NewSQLStore(db), cfg.Spec.ScopeID, "output", func() {
_ = os.WriteFile(filepath.Join(cfg.Directory, "violation"), []byte("native_scope_output_limit_exceeded"), 0600)
})
return writer, func() { _ = db.Close() }, nil
}
// RestrictJobSyscalls layers a job-only filter over the trusted bootstrap
// allowlist. In particular, unprivileged nested user namespaces must not regain
// mount capabilities and create unmetered tmpfs mounts after setup.
func RestrictJobSyscalls() error {
filters := []unix.SockFilter{{Code: unix.BPF_LD | unix.BPF_W | unix.BPF_ABS, K: 0}}
denied := []uint32{unix.SYS_UNSHARE, unix.SYS_SETNS, unix.SYS_MOUNT, unix.SYS_UMOUNT2, unix.SYS_PIVOT_ROOT, unix.SYS_CHROOT, unix.SYS_CAPSET, unix.SYS_OPEN_TREE, unix.SYS_MOVE_MOUNT, unix.SYS_FSOPEN, unix.SYS_FSCONFIG, unix.SYS_FSMOUNT, unix.SYS_FSPICK, unix.SYS_MOUNT_SETATTR}
for _, nr := range denied {
filters = append(filters, unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: nr, Jf: 1}, unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ERRNO | uint32(unix.EPERM)})
}
// ENOSYS lets libc fall back to clone for ordinary threads without permitting
// clone3's pointer-based namespace flags to bypass the inspectable clone filter.
filters = append(filters,
unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: unix.SYS_CLONE3, Jf: 1},
unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ERRNO | uint32(unix.ENOSYS)},
)
filters = append(filters,
unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, K: unix.SYS_CLONE, Jf: 3},
unix.SockFilter{Code: unix.BPF_LD | unix.BPF_W | unix.BPF_ABS, K: 16},
unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JSET | unix.BPF_K, K: unix.CLONE_NEWUSER | unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP, Jf: 1},
unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ERRNO | uint32(unix.EPERM)},
unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ALLOW},
)
program := unix.SockFprog{Len: uint16(len(filters)), Filter: &filters[0]}
err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, uintptr(unsafe.Pointer(&program)), 0, 0)
runtime.KeepAlive(filters)
return err
}