carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package rootstate
2
3import (
4 "context"
5 "errors"
6 "io"
7 "os"
8 "path/filepath"
9 "strings"
10 "time"
11
12 "github.com/carlosframework/platform/internal/safefile"
13 "golang.org/x/sys/unix"
14)
15
16type directory struct {
17 file *os.File
18 owner uint32
19 public bool
20}
21
22// Open validates every ancestor, including /. It never repairs ownership.
23// Creation is only possible for root, beneath an already checked parent.
24func Open(path string, create, public bool) (Store, error) {
25 if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" || (create && os.Geteuid() != 0) {
26 return nil, os.ErrPermission
27 }
28 f, err := os.Open("/")
29 if err != nil {
30 return nil, err
31 }
32 defer f.Close()
33 return descend(f, strings.Split(strings.TrimPrefix(path, "/"), "/"), create, public, 0)
34}
35
36func trusted(fd int, owner uint32, kind uint32) error {
37 var st unix.Stat_t
38 if err := unix.Fstat(fd, &st); err != nil {
39 return err
40 }
41 if st.Uid != owner || st.Mode&unix.S_IFMT != kind || st.Mode&0022 != 0 || (kind == unix.S_IFREG && st.Nlink != 1) {
42 return os.ErrPermission
43 }
44 return nil
45}
46
47// The owner argument is private; tests use a real user-owned descriptor.
48func descend(parent *os.File, parts []string, create, public bool, owner uint32) (Store, error) {
49 return descendSync(parent, parts, create, public, owner, unix.Fsync)
50}
51
52// syncParent is injected per traversal, never through a mutable global.
53func descendSync(parent *os.File, parts []string, create, public bool, owner uint32, syncParent func(int) error) (Store, error) {
54 fd, err := unix.FcntlInt(parent.Fd(), unix.F_DUPFD_CLOEXEC, 0)
55 if err != nil {
56 return nil, err
57 }
58 defer func() {
59 if fd >= 0 {
60 unix.Close(fd)
61 }
62 }()
63 if err := trusted(fd, owner, unix.S_IFDIR); err != nil {
64 return nil, err
65 }
66 mode := uint32(0700)
67 if public {
68 mode = 0755
69 }
70 for _, name := range parts {
71 if !component(name) && !(public && !create && component(strings.ReplaceAll(name, "@", "_"))) {
72 return nil, os.ErrPermission
73 }
74 next, e := unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
75 if errors.Is(e, unix.ENOENT) && create {
76 if e = unix.Mkdirat(fd, name, mode); e != nil && !errors.Is(e, unix.EEXIST) {
77 return nil, e
78 }
79 next, e = unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
80 }
81 if e != nil {
82 return nil, e
83 }
84 if e = trusted(next, owner, unix.S_IFDIR); e != nil {
85 unix.Close(next)
86 return nil, e
87 }
88 var st unix.Stat_t
89 if e = unix.Fstat(next, &st); e != nil || (public && st.Mode&0055 != 0055) {
90 unix.Close(next)
91 if e != nil {
92 return nil, e
93 }
94 return nil, os.ErrPermission
95 }
96 // A prior mkdir may be visible after its parent sync failed. Sync
97 // existing entries too, so retry cannot report an undurable path.
98 if create {
99 if e = syncParent(fd); e != nil {
100 unix.Close(next)
101 return nil, e
102 }
103 }
104 unix.Close(fd)
105 fd = next
106 }
107 var final unix.Stat_t
108 if err := unix.Fstat(fd, &final); err != nil {
109 return nil, err
110 }
111 if !public && final.Mode&0077 != 0 {
112 return nil, os.ErrPermission
113 }
114 f := os.NewFile(uintptr(fd), "rootstate")
115 fd = -1
116 return &directory{file: f, owner: owner, public: public}, nil
117}
118
119func (d *directory) Close() error { return d.file.Close() }
120func (d *directory) Sync() error {
121 if err := d.check(); err != nil {
122 return err
123 }
124 return d.file.Sync()
125}
126func (d *directory) check() error { return trusted(int(d.file.Fd()), d.owner, unix.S_IFDIR) }
127func (d *directory) Read(name string) ([]byte, error) {
128 if !component(name) {
129 return nil, os.ErrPermission
130 }
131 if err := d.check(); err != nil {
132 return nil, err
133 }
134 f, err := safefile.OpenFileAt(int(d.file.Fd()), name, os.O_RDONLY, 0)
135 if err != nil {
136 return nil, err
137 }
138 defer f.Close()
139 if err := trusted(int(f.Fd()), d.owner, unix.S_IFREG); err != nil {
140 return nil, err
141 }
142 b, err := io.ReadAll(io.LimitReader(f, MaxRecord+1))
143 if err == nil && len(b) > MaxRecord {
144 err = os.ErrPermission
145 }
146 return b, err
147}
148
149func (d *directory) Write(name string, body []byte) error {
150 return d.write(name, body, safefile.WriteAt)
151}
152
153func (d *directory) write(name string, body []byte, write func(int, string, []byte, os.FileMode) error) error {
154 if !component(name) || len(body) > MaxRecord || uint32(os.Geteuid()) != d.owner {
155 return os.ErrPermission
156 }
157 if err := d.check(); err != nil {
158 return err
159 }
160 if _, err := d.Read(name); err != nil && !errors.Is(err, os.ErrNotExist) {
161 return err
162 }
163 return write(int(d.file.Fd()), name, body, fileMode(d.public))
164}
165
166func (d *directory) Names() ([]string, error) {
167 if err := d.check(); err != nil {
168 return nil, err
169 }
170 // A fresh description gives each scan its own directory offset.
171 fd, err := unix.Openat(int(d.file.Fd()), ".", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
172 if err != nil {
173 return nil, err
174 }
175 f := os.NewFile(uintptr(fd), "rootstate")
176 defer f.Close()
177 entries, err := f.ReadDir(-1)
178 if err != nil {
179 return nil, err
180 }
181 var names []string
182 for _, entry := range entries {
183 if !component(entry.Name()) {
184 return nil, os.ErrPermission
185 }
186 names = append(names, entry.Name())
187 }
188 return names, nil
189}
190
191// Lock serializes root readers and writers on an inode the agent cannot replace.
192func (d *directory) Lock(ctx context.Context, name string) (func(), error) {
193 if ctx == nil || !component(name) || uint32(os.Geteuid()) != d.owner {
194 return nil, os.ErrPermission
195 }
196 if err := ctx.Err(); err != nil {
197 return nil, err
198 }
199 if err := d.check(); err != nil {
200 return nil, err
201 }
202 f, err := safefile.OpenFileAt(int(d.file.Fd()), name, os.O_RDWR|os.O_CREATE, 0600)
203 if err != nil {
204 return nil, err
205 }
206 if err := trusted(int(f.Fd()), d.owner, unix.S_IFREG); err != nil {
207 f.Close()
208 return nil, err
209 }
210 tick := time.NewTicker(10 * time.Millisecond)
211 defer tick.Stop()
212 for {
213 if err := ctx.Err(); err != nil {
214 f.Close()
215 return nil, err
216 }
217 err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
218 if err == nil {
219 return func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN); _ = f.Close() }, nil
220 }
221 if !errors.Is(err, unix.EAGAIN) && !errors.Is(err, unix.EWOULDBLOCK) && !errors.Is(err, unix.EINTR) {
222 f.Close()
223 return nil, err
224 }
225 select {
226 case <-ctx.Done():
227 f.Close()
228 return nil, ctx.Err()
229 case <-tick.C:
230 }
231 }
232}
233
234func (d *directory) Remove(name string) error {
235 if !component(name) || uint32(os.Geteuid()) != d.owner {
236 return os.ErrPermission
237 }
238 if err := d.check(); err != nil {
239 return err
240 }
241 if _, err := d.Read(name); err != nil && !errors.Is(err, os.ErrNotExist) {
242 return err
243 }
244 if err := unix.Unlinkat(int(d.file.Fd()), name, 0); err != nil && !errors.Is(err, unix.ENOENT) {
245 return err
246 }
247 return unix.Fsync(int(d.file.Fd()))
248}
249