//go:build linux package hostfloor import ( "context" "fmt" "golang.org/x/sys/unix" "os" "os/exec" "path/filepath" "strconv" "strings" "syscall" ) func OpenProtected(path string) (*os.File, error) { if !filepath.IsAbs(path) || filepath.Clean(path) != path { return nil, ErrFloor } // Every parent is root-owned and cannot be changed by a platform worker. for p := filepath.Dir(path); ; p = filepath.Dir(p) { s, err := os.Lstat(p) if err != nil { return nil, err } stat, ok := s.Sys().(*syscall.Stat_t) if !ok || stat.Uid != 0 || !s.IsDir() || s.Mode().Perm()&0022 != 0 { return nil, ErrFloor } if p == "/" { break } } fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0) if err != nil { return nil, err } f := os.NewFile(uintptr(fd), path) s, err := f.Stat() if err != nil { f.Close() return nil, err } stat, ok := s.Sys().(*syscall.Stat_t) if !ok || stat.Uid != 0 || !s.Mode().IsRegular() || s.Mode().Perm()&0022 != 0 { f.Close() return nil, ErrFloor } return f, nil } func (d Disk) Exec(path, platform string, args, env []string, roles ...string) error { f, err := OpenProtected(path) if err != nil { return err } defer f.Close() if _, err = d.VerifyFile(f, platform, roles...); err != nil { return err } return syscall.Exec("/proc/self/fd/"+strconv.Itoa(int(f.Fd())), append([]string{path}, args...), env) } func (d Disk) Run(ctx context.Context, path, platform string, args ...string) (string, error) { f, err := OpenProtected(path) if err != nil { return "", err } defer f.Close() if _, err = d.VerifyFile(f, platform); err != nil { return "", err } return runImage(ctx, f, args...) } // runImage runs the verified image open as f and returns its stdout only. // A caller parses that output (a version line, a JSON plan), so anything // the image writes to stderr, such as progress or an alert, must not be // mixed into it; stderr is kept for the error when the run fails. func runImage(ctx context.Context, f *os.File, args ...string) (string, error) { cmd := exec.CommandContext(ctx, "/proc/self/fd/3", args...) cmd.ExtraFiles = []*os.File{f} var stderr strings.Builder cmd.Stderr = &stderr out, err := cmd.Output() if err != nil { if msg := strings.TrimSpace(stderr.String()); msg != "" { err = fmt.Errorf("%w: %s", err, msg) } } return string(out), err } func ExecutingHash() (string, error) { f, err := os.Open("/proc/self/exe") if err != nil { return "", err } defer f.Close() image, err := os.Open(fmt.Sprintf("/proc/self/fd/%d", f.Fd())) if err != nil { return "", err } defer image.Close() return DigestReader(image) }