carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "net"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "strconv"
12 "strings"
13 "time"
14
15 "github.com/carlosframework/platform/internal/rootstate"
16 "github.com/carlosframework/platform/internal/safefile"
17 "golang.org/x/sys/unix"
18)
19
20func captureClaimUnitSocket(path, boot string) (claimUnitSocketIdentity, error) {
21 parent, err := safefile.OpenDir(filepath.Dir(path))
22 if err != nil {
23 return claimUnitSocketIdentity{}, err
24 }
25 defer parent.Close()
26 return claimUnitSocketAt(int(parent.Fd()), filepath.Base(path), boot)
27}
28
29func claimUnitSocketAt(parent int, name, boot string) (claimUnitSocketIdentity, error) {
30 var st unix.Stat_t
31 if err := unix.Fstatat(parent, name, &st, unix.AT_SYMLINK_NOFOLLOW); err != nil {
32 return claimUnitSocketIdentity{}, err
33 }
34 if boot == "" || st.Mode&unix.S_IFMT != unix.S_IFSOCK || st.Nlink != 1 {
35 return claimUnitSocketIdentity{}, os.ErrPermission
36 }
37 return claimUnitSocketIdentity{Boot: boot, Device: uint64(st.Dev), Inode: st.Ino, UID: st.Uid, GID: st.Gid, Mode: st.Mode}, nil
38}
39
40func parkClaimUnitSocket(source, archive, boot string, want claimUnitSocketIdentity) error {
41 return parkClaimUnitSocketWith(source, archive, boot, want, unix.Renameat2)
42}
43
44func parkClaimUnitSocketWith(source, archive, boot string, want claimUnitSocketIdentity, rename func(int, string, int, string, uint) error) error {
45 if want.Boot == "" || want.Device == 0 || want.Inode == 0 || want.Mode&unix.S_IFMT != unix.S_IFSOCK || boot == "" {
46 return os.ErrPermission
47 }
48 src, err := safefile.OpenDir(filepath.Dir(source))
49 if err != nil {
50 return err
51 }
52 defer src.Close()
53 dst, err := safefile.OpenDir(filepath.Dir(archive))
54 if err != nil {
55 return err
56 }
57 defer dst.Close()
58 sfd, dfd := int(src.Fd()), int(dst.Fd())
59 sname, dname := filepath.Base(source), filepath.Base(archive)
60 old, sourceErr := claimUnitSocketAt(sfd, sname, boot)
61 parked, archiveErr := claimUnitSocketAt(dfd, dname, boot)
62 if errors.Is(sourceErr, os.ErrNotExist) {
63 if errors.Is(archiveErr, os.ErrNotExist) {
64 return errors.Join(src.Sync(), dst.Sync())
65 }
66 if archiveErr != nil || parked != want {
67 return os.ErrPermission
68 }
69 return errors.Join(src.Sync(), dst.Sync())
70 }
71 if sourceErr != nil || old != want || !errors.Is(archiveErr, os.ErrNotExist) {
72 return os.ErrPermission
73 }
74 if err = rename(sfd, sname, dfd, dname, unix.RENAME_NOREPLACE); err != nil {
75 return err
76 }
77 parked, err = claimUnitSocketAt(dfd, dname, boot)
78 if err != nil || parked != want {
79 // Preserve a raced replacement; never overwrite a new source name.
80 restore := unix.Renameat2(dfd, dname, sfd, sname, unix.RENAME_NOREPLACE)
81 return errors.Join(os.ErrPermission, err, restore, src.Sync(), dst.Sync())
82 }
83 return errors.Join(src.Sync(), dst.Sync())
84}
85
86func captureProductionClaimUnitSocket(ctx context.Context, s claimUnitShape, p claimUnitPredecessor) (claimUnitSocketIdentity, error) {
87 if !claimFlatSocket(s) {
88 return claimUnitSocketIdentity{}, os.ErrPermission
89 }
90 boot, err := claimUnitBoot()
91 if err != nil {
92 return claimUnitSocketIdentity{}, err
93 }
94 before, err := captureClaimUnitSocket(s.Socket, boot)
95 if err != nil {
96 return before, err
97 }
98 out, err := exec.CommandContext(ctx, "systemctl", "show", "-p", "MainPID", "--value", p.Unit).Output()
99 if err != nil {
100 return before, err
101 }
102 pid, err := strconv.Atoi(strings.TrimSpace(string(out)))
103 if err != nil || pid <= 1 {
104 return before, os.ErrPermission
105 }
106 conn, err := (&net.Dialer{Timeout: time.Second}).DialContext(ctx, "unix", s.Socket)
107 if err != nil {
108 return before, err
109 }
110 defer conn.Close()
111 if err = claimProbePeer(conn, pid, "0::/"+p.Group); err != nil {
112 return before, err
113 }
114 after, err := captureClaimUnitSocket(s.Socket, boot)
115 if err != nil || after != before {
116 return before, os.ErrPermission
117 }
118 return before, nil
119}
120
121func parkProductionClaimUnitSocket(b claimUnitBinding, id claimUnitSocketIdentity) error {
122 if !b.valid() || !claimFlatSocket(b.Shape) {
123 return os.ErrPermission
124 }
125 boot, err := claimUnitBoot()
126 if err != nil {
127 return err
128 }
129 const dir = "/run/carlos-control/unit-migrations"
130 store, err := rootstate.Open(dir, true, false)
131 if err != nil {
132 return err
133 }
134 defer store.Close()
135 raw, err := json.Marshal(b)
136 if err != nil {
137 return err
138 }
139 return parkClaimUnitSocket(b.Shape.Socket, filepath.Join(dir, claimUnitDigest(raw)+".sock"), boot, id)
140}
141