//go:build linux package sandbox import ( "encoding/base64" "encoding/json" "fmt" "os" "syscall" "unsafe" "golang.org/x/sys/unix" ) type helperPolicy struct { Config Config `json:"config"` CWD string `json:"cwd"` Env map[string]string `json:"env"` Command []string `json:"command"` } func ExecHelper(args []string) error { if len(args) != 1 { return fmt.Errorf("sandbox-exec: expected encoded policy") } data, err := base64.RawStdEncoding.DecodeString(args[0]) if err != nil { return err } var p helperPolicy if err := json.Unmarshal(data, &p); err != nil { return err } if err := restrict(&p.Config, p.CWD, p.Env); err != nil { return err } if len(p.Command) == 0 { return fmt.Errorf("sandbox-exec: empty command") } if err := syscall.Chdir(p.CWD); err != nil { return err } return syscall.Exec(p.Command[0], p.Command, envList(p.Env)) } func envList(env map[string]string) []string { out := make([]string, 0, len(env)) for k, v := range env { out = append(out, k+"="+v) } return out } func restrict(cfg *Config, cwd string, env map[string]string) error { if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { return fmt.Errorf("set no_new_privs: %w", err) } fsAccess := uint64(unix.LANDLOCK_ACCESS_FS_EXECUTE | unix.LANDLOCK_ACCESS_FS_WRITE_FILE | unix.LANDLOCK_ACCESS_FS_READ_FILE | unix.LANDLOCK_ACCESS_FS_READ_DIR | unix.LANDLOCK_ACCESS_FS_REMOVE_DIR | unix.LANDLOCK_ACCESS_FS_REMOVE_FILE | unix.LANDLOCK_ACCESS_FS_MAKE_CHAR | unix.LANDLOCK_ACCESS_FS_MAKE_DIR | unix.LANDLOCK_ACCESS_FS_MAKE_REG | unix.LANDLOCK_ACCESS_FS_MAKE_SOCK | unix.LANDLOCK_ACCESS_FS_MAKE_FIFO | unix.LANDLOCK_ACCESS_FS_MAKE_BLOCK | unix.LANDLOCK_ACCESS_FS_MAKE_SYM) fd, _, err := unix.Syscall(unix.SYS_LANDLOCK_CREATE_RULESET, uintptr(unsafe.Pointer(&fsAccess)), unsafe.Sizeof(fsAccess), 0) if err != 0 { return fmt.Errorf("landlock unavailable: %w", err) } defer unix.Close(int(fd)) add := func(paths []string, access uint64) error { for _, pattern := range paths { p := expandPath(pattern, cwd, func(k string) string { return env[k] }) if !pathExists(p) { continue } f, e := unix.Open(p, unix.O_PATH|unix.O_CLOEXEC, 0) if e != nil { return e } rule := struct { Allowed uint64 ParentFD int }{access, int(f)} _, _, e = unix.Syscall6(unix.SYS_LANDLOCK_ADD_RULE, fd, uintptr(1), uintptr(unsafe.Pointer(&rule)), 0, 0, 0) unix.Close(f) if e != nil { return fmt.Errorf("add landlock path %s: %w", p, e) } } return nil } read := uint64(unix.LANDLOCK_ACCESS_FS_READ_FILE | unix.LANDLOCK_ACCESS_FS_READ_DIR) write := uint64(unix.LANDLOCK_ACCESS_FS_WRITE_FILE | unix.LANDLOCK_ACCESS_FS_REMOVE_DIR | unix.LANDLOCK_ACCESS_FS_REMOVE_FILE | unix.LANDLOCK_ACCESS_FS_MAKE_DIR | unix.LANDLOCK_ACCESS_FS_MAKE_REG | unix.LANDLOCK_ACCESS_FS_MAKE_SOCK | unix.LANDLOCK_ACCESS_FS_MAKE_FIFO | unix.LANDLOCK_ACCESS_FS_MAKE_CHAR | unix.LANDLOCK_ACCESS_FS_MAKE_BLOCK | unix.LANDLOCK_ACCESS_FS_MAKE_SYM) if err := add(cfg.Filesystem.RO, read); err != nil { return err } if err := add(cfg.Filesystem.ROX, read|unix.LANDLOCK_ACCESS_FS_EXECUTE); err != nil { return err } if err := add(cfg.Filesystem.RW, read|write); err != nil { return err } if err := add(cfg.Filesystem.RWX, read|write|unix.LANDLOCK_ACCESS_FS_EXECUTE); err != nil { return err } _, _, err = unix.Syscall(unix.SYS_LANDLOCK_RESTRICT_SELF, fd, 0, 0) if err != 0 { return fmt.Errorf("restrict landlock: %w", err) } return nil } var _ = os.Args