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 host
4
5import (
6 "context"
7 "errors"
8 "io/fs"
9 "os"
10 "path/filepath"
11 "slices"
12 "strconv"
13 "strings"
14
15 "github.com/carlosframework/platform/internal/hostbox"
16 "github.com/carlosframework/platform/internal/hostguard"
17 "github.com/carlosframework/platform/internal/instancectl"
18 "github.com/carlosframework/platform/internal/release"
19 "github.com/carlosframework/platform/internal/rootstate"
20)
21
22func isolatedGeneration(r release.GenerationReservation) bool {
23 return (r.Binding.Unit == "" || r.Binding.Unit == instancectl.UnitName(instancectl.RoleRun, r.Binding.Target.Host)) &&
24 (r.Process == (release.GenerationProcess{}) || r.Process.Containment == 1)
25}
26
27func retiredGenerationReceipt(ctx context.Context, view hostguard.FenceView, owner release.GenerationOwner) error {
28 if view.Host != owner.Host || view.Barrier != nil || !slices.Contains(view.Retired, hostguard.Captured{Incarnation: owner.Incarnation, Stamp: owner.Stamp}) {
29 return os.ErrPermission
30 }
31 for _, receipt := range view.Receipts {
32 if generationOwner(receipt.Target) == owner && verifyRetirementReceipt(ctx, receipt) == nil {
33 return nil
34 }
35 }
36 return os.ErrPermission
37}
38
39func productionRetiredGenerationAbsence(ctx context.Context, r release.GenerationReservation, view hostguard.FenceView) (string, error) {
40 if r.Binding.Path != release.VersionPath(release.OptDir(), r.Binding.Binding.Account, r.Binding.Version, r.Binding.Binding.App) {
41 return "", os.ErrPermission
42 }
43 if !isolatedGeneration(r) {
44 return productionGenerationAbsence(ctx, r)
45 }
46 t := r.Binding.Target
47 if !slices.Contains(view.Retired, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp}) {
48 return productionGenerationAbsence(ctx, r)
49 }
50 if err := retiredGenerationReceipt(ctx, view, t); err != nil {
51 return "", err
52 }
53 if os.Geteuid() != 0 {
54 return "", os.ErrPermission
55 }
56 if err := verifyStopKernelMounts("/sys/fs/cgroup", "/proc"); err != nil {
57 return "", err
58 }
59 current, err := generationRecordedTargets(ctx, r)
60 if err != nil {
61 return "", err
62 }
63 if err := generationRetiredController(ctx, current.Binding); err != nil {
64 return "", err
65 }
66 boot, err := (generationKernel{proc: "/proc"}).boot()
67 if err != nil {
68 return "", err
69 }
70 return generationInventoryDigest([]any{"retired-isolated", current, boot, view.Receipts}), nil
71}
72
73func retiredGenerationSnapshot(ctx context.Context, inv generationInventory, records []release.GenerationReservation) bool {
74 if len(inv.Installations) == 0 || len(inv.Launches) == 0 {
75 return false
76 }
77 views := map[string]hostguard.FenceView{}
78 all := map[string]hostguard.FenceView{}
79 for _, view := range inv.Installations {
80 in := view.Installation
81 all[view.Host] = view
82 if view.Barrier != nil {
83 return false
84 }
85 if !slices.Contains(view.Retired, hostguard.Captured{Incarnation: in.Incarnation, Stamp: in.Stamp}) {
86 continue
87 }
88 views[view.Host] = view
89 }
90 if len(views) == 0 {
91 return false
92 }
93 for _, r := range records {
94 if _, ok := all[r.Binding.Target.Host]; !ok {
95 return false
96 }
97 if _, retired := views[r.Binding.Target.Host]; !retired {
98 if !isolatedGeneration(r) {
99 return false
100 }
101 continue
102 }
103 if r.Retirement == "" || !r.Closed || !isolatedGeneration(r) || retiredGenerationReceipt(ctx, views[r.Binding.Target.Host], r.Binding.Target) != nil {
104 return false
105 }
106 }
107 for _, launch := range inv.Launches {
108 if _, ok := all[launch.Target.Host]; !ok {
109 return false
110 }
111 if _, retired := views[launch.Target.Host]; !retired {
112 continue
113 }
114 if !launch.WritersClosed || !launch.Completed.Valid() || retiredGenerationReceipt(ctx, views[launch.Target.Host], generationOwner(launch.Target)) != nil {
115 return false
116 }
117 for _, r := range launch.Reservations {
118 if !hostbox.IsUnitReservation(r) || r.State != hostbox.UnitSettled || !hostbox.SameLaunchOwner(r.Target, launch.Target) {
119 return false
120 }
121 matches := 0
122 for _, pin := range records {
123 if pin.Binding.Target == generationOwner(launch.Target) && pin.Binding.Attempt == r.ID {
124 matches++
125 }
126 }
127 if matches != 1 {
128 return false
129 }
130 }
131 }
132 return true
133}
134
135func emptyGenerationGroups(ctx context.Context, root string, active ...release.GenerationReservation) error {
136 return (generationKernel{proc: "/proc", cgroups: "/sys/fs/cgroup", contained: active}).emptyGroups(ctx, root)
137}
138
139func (k generationKernel) emptyGroups(ctx context.Context, root string) error {
140 count := 0
141 return filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
142 if path == root && errors.Is(err, os.ErrNotExist) {
143 return nil
144 }
145 if err != nil {
146 return err
147 }
148 if err := ctx.Err(); err != nil {
149 return err
150 }
151 count++
152 if count > 8192 || entry.Type()&os.ModeSymlink != 0 {
153 return os.ErrPermission
154 }
155 if !entry.IsDir() {
156 return nil
157 }
158 raw, err := os.ReadFile(filepath.Join(path, "cgroup.procs"))
159 if err != nil {
160 return err
161 }
162 for _, pid := range strings.Fields(string(raw)) {
163 if _, err := strconv.ParseUint(pid, 10, 32); err != nil {
164 return os.ErrPermission
165 }
166 task := filepath.Join(k.proc, pid)
167 status, err := generationRead(task + "/status")
168 if err != nil {
169 return err
170 }
171 ok, err := k.containedTask(ctx, task, status)
172 if err != nil {
173 return err
174 }
175 if !ok {
176 return os.ErrPermission
177 }
178 }
179 return nil
180 })
181}
182
183func productionRetiredGenerationKernelCensus(ctx context.Context) (string, string, error) {
184 records, ok := ctx.Value(generationCensusReservationsKey{}).([]release.GenerationReservation)
185 if !ok {
186 return "", "", os.ErrPermission
187 }
188 inv, err := productionLocalGenerationInventory(ctx)
189 if err != nil {
190 return "", "", err
191 }
192 if !retiredGenerationSnapshot(ctx, inv, records) {
193 return productionGenerationKernelCensus(ctx)
194 }
195 if os.Geteuid() != 0 {
196 return "", "", os.ErrPermission
197 }
198 if err := verifyStopKernelMounts("/sys/fs/cgroup", "/proc"); err != nil {
199 return "", "", err
200 }
201 k := generationKernel{proc: "/proc"}
202 boot, err := k.boot()
203 if err != nil {
204 return "", "", err
205 }
206 for _, launch := range inv.Launches {
207 retired := false
208 for _, view := range inv.Installations {
209 if view.Host == launch.Target.Host && slices.Contains(view.Retired, hostguard.Captured{Incarnation: launch.Target.Incarnation, Stamp: launch.Target.Stamp}) {
210 retired = true
211 }
212 }
213 if !retired {
214 continue
215 }
216 for _, r := range records {
217 if r.Binding.Target == generationOwner(launch.Target) {
218 if err := generationRetiredController(ctx, r.Binding); err != nil {
219 return "", "", err
220 }
221 }
222 }
223 for _, group := range launch.Groups {
224 if !filepath.IsLocal(group.RelativePath) || group.RelativePath == "." || filepath.Clean(group.RelativePath) != group.RelativePath {
225 return "", "", os.ErrPermission
226 }
227 if err := emptyGenerationGroups(ctx, filepath.Join("/sys/fs/cgroup", group.RelativePath)); err != nil {
228 return "", "", err
229 }
230 }
231 }
232 if err := emptyGenerationGroups(ctx, "/sys/fs/cgroup/carlos.slice/carlos-instances.slice", records...); err != nil {
233 return "", "", err
234 }
235 if err := checkedGenerationTree(ctx, filepath.Join(release.OptDir(), "versions")); err != nil {
236 return "", "", err
237 }
238 after, err := k.boot()
239 if err != nil || after != boot {
240 return "", "", os.ErrPermission
241 }
242 return boot, generationInventoryDigest([]any{"retired-isolated-census", inv, records, boot}), nil
243}
244
245func generationRetiredController(ctx context.Context, b release.GenerationBinding) error {
246 service, recheck := generationServiceReader(ctx)
247 k := generationKernel{proc: "/proc", service: service, recheck: recheck}
248 if k.controllerGone(ctx, b) == nil {
249 return nil
250 }
251 boot, err := k.boot()
252 if err != nil || boot != b.Target.BootID {
253 return os.ErrPermission
254 }
255 path := filepath.Join("/proc", strconv.Itoa(b.ControllerPID))
256 start, err := claimLaunchProcessStart("/proc", b.ControllerPID)
257 if err != nil || start != b.ControllerStart {
258 return os.ErrPermission
259 }
260 b, err = generationCurrentFiles(b, rootstate.OpenTrustedFile)
261 if err != nil {
262 return err
263 }
264 refs, err := generationTree(ctx, b)
265 if err != nil {
266 return err
267 }
268 tasks, err := generationNames(path + "/task")
269 if err != nil {
270 return err
271 }
272 for _, tid := range tasks {
273 if _, err := k.task(ctx, path+"/task/"+tid, b, nil, refs); err != nil {
274 return err
275 }
276 }
277 again, err := claimLaunchProcessStart("/proc", b.ControllerPID)
278 if err != nil || again != start {
279 return os.ErrPermission
280 }
281 return nil
282}
283