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 instanceexec
4
5import (
6 "encoding/json"
7 "errors"
8 "io"
9 "net"
10 "os"
11 "strings"
12 "time"
13
14 "github.com/carlosframework/platform/internal/instancectl"
15 "golang.org/x/sys/unix"
16)
17
18func scopedClaimInit(argv0 string, argv, env []string, snapshot string) error {
19 return runInitChecked(argv0, argv, env, func() error {
20 if err := ScopeAbstractUnix(); err != nil {
21 return err
22 }
23 return ScopeClaimGeneration()
24 }, func() error {
25 return checkClaimExec(snapshot, instancectl.ClaimExecSocket, 0, claimBootTime)
26 })
27}
28func claimBootTime() (string, int64, error) {
29 b, err := os.ReadFile(instancectl.ClaimExecBootID)
30 if err != nil {
31 return "", 0, err
32 }
33 var ts unix.Timespec
34 if err = unix.ClockGettime(unix.CLOCK_BOOTTIME, &ts); err != nil {
35 return "", 0, err
36 }
37 return strings.TrimSpace(string(b)), ts.Nano(), nil
38}
39
40// The path is a root-controlled ExecStart argument, never tenant environment
41// or a field of launch.run.json. Its read-only bind cannot be opted out of.
42func checkClaimExec(snapshot, socket string, owner uint32, now func() (string, int64, error)) error {
43 fd, err := unix.Open(snapshot, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
44 if err != nil {
45 return err
46 }
47 f := os.NewFile(uintptr(fd), snapshot)
48 defer f.Close()
49 var st unix.Stat_t
50 if err = unix.Fstat(fd, &st); err != nil {
51 return err
52 }
53 if st.Uid != owner || st.Mode&unix.S_IFMT != unix.S_IFREG || st.Mode&0222 != 0 {
54 return instancectl.ErrClaimUnit
55 }
56 var want instancectl.ClaimExecPermit
57 if err = json.NewDecoder(io.LimitReader(f, 4096)).Decode(&want); err != nil {
58 return err
59 }
60 if !want.Valid() || want.Containment != 1 {
61 return instancectl.ErrClaimUnit
62 }
63 conn, err := net.DialTimeout("unix", socket, 5*time.Second)
64 if err != nil {
65 return err
66 }
67 defer conn.Close()
68 if err = conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
69 return err
70 }
71 raw, err := conn.(*net.UnixConn).SyscallConn()
72 if err != nil {
73 return err
74 }
75 var cred *unix.Ucred
76 var peerErr error
77 if err = raw.Control(func(fd uintptr) { cred, peerErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED) }); err != nil {
78 return err
79 }
80 if peerErr != nil {
81 return peerErr
82 }
83 if cred.Uid != owner {
84 return instancectl.ErrClaimUnit
85 }
86 if err = json.NewEncoder(conn).Encode(want); err != nil {
87 return err
88 }
89 var got instancectl.ClaimExecPermit
90 if err = json.NewDecoder(io.LimitReader(conn, 4096)).Decode(&got); err != nil {
91 return err
92 }
93 if got != want {
94 return instancectl.ErrClaimUnit
95 }
96 // Explicit close precedes the final time sample. No IO or namespace setup
97 // follows this sample before runInitChecked calls ForkExec.
98 if err = conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
99 return err
100 }
101 boot, n, err := now()
102 if err != nil {
103 return err
104 }
105 if boot != want.BootID || n >= want.SafeUntil {
106 return instancectl.ErrClaimUnit
107 }
108 return nil
109}
110