carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "errors"
6 "os"
7 "path/filepath"
8 "sort"
9 "strings"
10
11 "github.com/carlosframework/platform/internal/rootstate"
12)
13
14// RootGenerationPins is read under release exclusion, held through destruction.
15// Verify must reconcile the records with root installation/launch inventory and
16// kernel references. An empty directory alone never establishes completeness.
17type RootGenerationPins struct {
18 Ledger *RootGenerations
19 Verify func(context.Context, []GenerationReservation) error
20 Reconcile func(context.Context, []GenerationReservation) (GenerationEvidence, error)
21}
22
23func (s RootGenerationPins) StagedPins(ctx context.Context, optDir string) (StagedPaths, error) {
24 if s.Ledger == nil || s.Ledger.Store == nil || !filepath.IsAbs(optDir) {
25 return nil, os.ErrPermission
26 }
27 records, err := s.Ledger.Reservations(ctx)
28 if err != nil {
29 return nil, err
30 }
31 pins := StagedPaths{}
32 for _, r := range records {
33 b := r.Binding
34 if b.Path != VersionPath(optDir, b.Binding.Account, b.Version, b.Binding.App) {
35 return nil, os.ErrPermission
36 }
37 if r.Retirement != "" {
38 continue
39 }
40 for _, item := range []struct {
41 path string
42 expected, proof rootstate.FileIdentity
43 }{
44 {b.Path, b.Executable, b.ExecutableProof}, {b.Tree, b.TreeIdentity, b.TreeProof},
45 } {
46 if err := generationFileIdentity(item.path, item.expected, item.proof, b.Target.BootID); err != nil {
47 return nil, err
48 }
49 }
50 pins.add(b.Path)
51 pins.add(b.Tree)
52 }
53 if s.Reconcile != nil {
54 if _, err := s.Ledger.Certify(ctx, records, s.Reconcile); err != nil {
55 return nil, err
56 }
57 return pins, nil
58 }
59 if s.Verify == nil {
60 return nil, os.ErrPermission
61 }
62 if err := s.Verify(ctx, records); err != nil {
63 return nil, err
64 }
65 return pins, nil
66}
67
68func generationFileIdentity(path string, expected, proof rootstate.FileIdentity, boot string) error {
69 if proof.Boot != "" {
70 if proof.Boot != boot || proof.Device != expected.Device || proof.Inode != expected.Inode {
71 return os.ErrPermission
72 }
73 var f *os.File
74 var err error
75 if proof.Mode&0170000 == 0040000 {
76 f, err = rootstate.OpenAuthorityDirectory(path)
77 } else {
78 f, err = rootstate.OpenAuthorityFile(path)
79 }
80 if err != nil {
81 return err
82 }
83 defer f.Close()
84 actual, err := rootstate.PersistentIdentity(f)
85 if err != nil {
86 return err
87 }
88 if !GenerationFileProof(path, proof, actual) {
89 return os.ErrPermission
90 }
91 return nil
92 }
93 info, err := os.Lstat(path)
94 if err != nil {
95 return err
96 }
97 if info.Mode()&os.ModeSymlink != 0 {
98 return os.ErrPermission
99 }
100 f, err := os.Open(path)
101 if err != nil {
102 return err
103 }
104 defer f.Close()
105 actual, err := rootstate.Identity(f)
106 if err != nil {
107 return err
108 }
109 observed, err := rootstate.ObjectIdentity(f)
110 if err != nil {
111 return err
112 }
113 if boot == "" || observed.Boot != boot || actual != expected {
114 return os.ErrPermission
115 }
116 return nil
117}
118
119// WithRootGenerationPins protects new writes as well as replacement of a
120// differing artifact: a removed executable can still be held by a generation.
121func WithRootGenerationPins(source PinSource) PassOption {
122 return func(c *passConfig) { c.generationPins = source }
123}
124
125// Sibling repair can change a reserved tree even when its binary already
126// matches. Check only when a write is needed, preserving the legacy no-op path.
127func stageGenerationSibling(optDir, acct, app, ver string, raw []byte, checks []stageCheck) error {
128 for _, check := range checks {
129 if check.beforeWrite != nil {
130 if err := check.beforeWrite(StagedManifestPath(optDir, acct, ver, app)); err != nil {
131 return err
132 }
133 }
134 }
135 return StageManifest(optDir, acct, ver, app, raw)
136}
137
138func (c passConfig) generationWriteCheck(ctx context.Context, optDir, path string) error {
139 if c.generationPins == nil {
140 return nil
141 }
142 // Creating a wholly absent version tree cannot destroy an existing pin.
143 // Keep fresh signed recovery available while pin completeness is unknown.
144 rel, err := filepath.Rel(filepath.Join(optDir, "versions"), path)
145 version, _, ok := strings.Cut(rel, string(filepath.Separator))
146 if err != nil || !filepath.IsLocal(rel) || !ok || !ValidVersion(version) {
147 return os.ErrPermission
148 }
149 if _, err := os.Lstat(filepath.Join(optDir, "versions", version)); errors.Is(err, os.ErrNotExist) {
150 return nil
151 } else if err != nil {
152 return err
153 }
154 pins, err := c.generationPins.StagedPins(ctx, optDir)
155 if err != nil {
156 return err
157 }
158 pinned, err := pins.Refers(path)
159 if err != nil {
160 return err
161 }
162 if pinned {
163 return ErrVersionReused
164 }
165 for parent := filepath.Dir(path); parent != optDir && parent != filepath.Dir(parent); parent = filepath.Dir(parent) {
166 pinned, err := pins.Refers(parent)
167 if err != nil {
168 return err
169 }
170 if pinned {
171 return ErrVersionReused
172 }
173 }
174 return nil
175}
176
177// Reservations validates and recommits every retained record under release exclusion.
178func (g *RootGenerations) Reservations(ctx context.Context) ([]GenerationReservation, error) {
179 if g == nil || g.Store == nil {
180 return nil, os.ErrPermission
181 }
182 names, err := g.Store.Names()
183 if err != nil {
184 return nil, err
185 }
186 sort.Strings(names)
187 records := make([]GenerationReservation, 0, len(names))
188 for _, name := range names {
189 if name == generationCompletenessKey {
190 if _, err := g.Completeness(); err != nil {
191 return nil, err
192 }
193 continue
194 }
195 if err := ctx.Err(); err != nil {
196 return nil, err
197 }
198 raw, err := g.Store.Read(name)
199 if err != nil {
200 return nil, err
201 }
202 var r GenerationReservation
203 if len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &r) != nil || !validGeneration(r) || generationKey(r.Binding) != name {
204 return nil, os.ErrPermission
205 }
206 // Read retries the directory sync before this record can inform a grant.
207 r, err = g.Read(r.Binding)
208 if err != nil {
209 return nil, err
210 }
211 records = append(records, r)
212 }
213 return records, nil
214}
215