carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package hostfloor
4
5import (
6 "context"
7 "fmt"
8 "golang.org/x/sys/unix"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "strconv"
13 "strings"
14 "syscall"
15)
16
17func OpenProtected(path string) (*os.File, error) {
18 if !filepath.IsAbs(path) || filepath.Clean(path) != path {
19 return nil, ErrFloor
20 }
21 // Every parent is root-owned and cannot be changed by a platform worker.
22 for p := filepath.Dir(path); ; p = filepath.Dir(p) {
23 s, err := os.Lstat(p)
24 if err != nil {
25 return nil, err
26 }
27 stat, ok := s.Sys().(*syscall.Stat_t)
28 if !ok || stat.Uid != 0 || !s.IsDir() || s.Mode().Perm()&0022 != 0 {
29 return nil, ErrFloor
30 }
31 if p == "/" {
32 break
33 }
34 }
35 fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0)
36 if err != nil {
37 return nil, err
38 }
39 f := os.NewFile(uintptr(fd), path)
40 s, err := f.Stat()
41 if err != nil {
42 f.Close()
43 return nil, err
44 }
45 stat, ok := s.Sys().(*syscall.Stat_t)
46 if !ok || stat.Uid != 0 || !s.Mode().IsRegular() || s.Mode().Perm()&0022 != 0 {
47 f.Close()
48 return nil, ErrFloor
49 }
50 return f, nil
51}
52func (d Disk) Exec(path, platform string, args, env []string, roles ...string) error {
53 f, err := OpenProtected(path)
54 if err != nil {
55 return err
56 }
57 defer f.Close()
58 if _, err = d.VerifyFile(f, platform, roles...); err != nil {
59 return err
60 }
61 return syscall.Exec("/proc/self/fd/"+strconv.Itoa(int(f.Fd())), append([]string{path}, args...), env)
62}
63func (d Disk) Run(ctx context.Context, path, platform string, args ...string) (string, error) {
64 f, err := OpenProtected(path)
65 if err != nil {
66 return "", err
67 }
68 defer f.Close()
69 if _, err = d.VerifyFile(f, platform); err != nil {
70 return "", err
71 }
72 return runImage(ctx, f, args...)
73}
74
75// runImage runs the verified image open as f and returns its stdout only.
76// A caller parses that output (a version line, a JSON plan), so anything
77// the image writes to stderr, such as progress or an alert, must not be
78// mixed into it; stderr is kept for the error when the run fails.
79func runImage(ctx context.Context, f *os.File, args ...string) (string, error) {
80 cmd := exec.CommandContext(ctx, "/proc/self/fd/3", args...)
81 cmd.ExtraFiles = []*os.File{f}
82 var stderr strings.Builder
83 cmd.Stderr = &stderr
84 out, err := cmd.Output()
85 if err != nil {
86 if msg := strings.TrimSpace(stderr.String()); msg != "" {
87 err = fmt.Errorf("%w: %s", err, msg)
88 }
89 }
90 return string(out), err
91}
92func ExecutingHash() (string, error) {
93 f, err := os.Open("/proc/self/exe")
94 if err != nil {
95 return "", err
96 }
97 defer f.Close()
98 image, err := os.Open(fmt.Sprintf("/proc/self/fd/%d", f.Fd()))
99 if err != nil {
100 return "", err
101 }
102 defer image.Close()
103 return DigestReader(image)
104}
105