oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package operator
4
5import (
6 "context"
7 "crypto/sha256"
8 "database/sql"
9 "database/sql/driver"
10 "encoding/hex"
11 "io"
12 "net/url"
13 "os"
14 "sync"
15 "time"
16
17 "golang.org/x/sys/unix"
18 "modernc.org/sqlite"
19)
20
21// ReadonlyExpectation is an observation required by the unmounted checker.
22// It is not launch authority. Verify must check the independently enrolled and
23// platform-verified handoff; deriving expectations from this database is invalid.
24type ReadonlyExpectation struct {
25 Path string
26 Device, Inode uint64
27 Length int64
28 SHA256 string
29}
30
31// InspectReadonly runs only in the dedicated non-serving checker process, after
32// its launch descriptors are closed. Verify owns launch/enrollment verification;
33// no production implementation exists yet. The callback must not open files,
34// start goroutines, retain the connection, or perform network or serving work.
35// Success is withheld until driver closure and the final exact byte comparison.
36func InspectReadonly(ctx context.Context, expected ReadonlyExpectation, verify func(context.Context, ReadonlyExpectation) error, inspect func(context.Context, *sql.Conn) error) error {
37 return inspectReadonly(ctx, expected, verify, inspect, nil)
38}
39
40func inspectReadonly(ctx context.Context, expected ReadonlyExpectation, verify func(context.Context, ReadonlyExpectation) error, inspect func(context.Context, *sql.Conn) error, hook func(string)) (err error) {
41 deadline, bounded := ctx.Deadline()
42 if !bounded || !deadline.After(time.Now()) || verify == nil || inspect == nil || expected.Length <= 0 || expected.Length > 256<<20 || len(expected.SHA256) != 64 {
43 return ErrRefused
44 }
45 hash, e := hex.DecodeString(expected.SHA256)
46 if e != nil || hex.EncodeToString(hash) != expected.SHA256 || expected.Device == 0 || expected.Inode == 0 {
47 return ErrRefused
48 }
49 ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
50 defer cancel()
51 deadline, _ = ctx.Deadline()
52 if verify(ctx, expected) != nil {
53 return ErrRefused
54 }
55 pin, id, e := pinExisting(expected.Path)
56 if e != nil {
57 return e
58 }
59 defer func() {
60 if pin.Close() != nil {
61 err = ErrUnavailable
62 }
63 }()
64 if id != (fileIdentity{expected.Device, expected.Inode}) {
65 return ErrRefused
66 }
67 c := &readonlyConnector{path: expected.Path, pin: pin, identity: id, deadline: deadline, sqlite: &sqlite.Driver{}, hook: hook}
68 if c.pathMatches() != nil || hashReadonly(ctx, pin, expected) != nil {
69 return ErrRefused
70 }
71 d := sql.OpenDB(c)
72 d.SetMaxOpenConns(1)
73 d.SetMaxIdleConns(1)
74 defer func() {
75 if d.Close() != nil {
76 err = ErrUnavailable
77 }
78 }()
79 conn, e := d.Conn(ctx)
80 if e != nil {
81 return ErrUnavailable
82 }
83 defer conn.Close()
84 if _, e = conn.ExecContext(ctx, "PRAGMA query_only=ON"); e != nil {
85 return ErrUnavailable
86 }
87 if _, e = conn.ExecContext(ctx, "PRAGMA temp_store=MEMORY"); e != nil {
88 return ErrUnavailable
89 }
90 var temp int
91 if conn.QueryRowContext(ctx, "PRAGMA temp_store").Scan(&temp) != nil || temp != 2 {
92 return ErrRefused
93 }
94 if _, e = conn.ExecContext(ctx, "BEGIN"); e != nil {
95 return ErrUnavailable
96 }
97 e = inspect(ctx, conn)
98 if _, end := conn.ExecContext(ctx, "ROLLBACK"); end != nil {
99 return ErrUnavailable
100 }
101 if e != nil {
102 if e == ErrUnavailable {
103 return ErrUnavailable
104 }
105 return ErrRefused
106 }
107 if conn.Close() != nil || d.Close() != nil {
108 return ErrUnavailable
109 }
110 if c.pathMatches() != nil || hashReadonly(ctx, pin, expected) != nil || verify(ctx, expected) != nil {
111 return ErrRefused
112 }
113 return nil
114}
115
116func hashReadonly(ctx context.Context, pin *os.File, expected ReadonlyExpectation) error {
117 info, e := pin.Stat()
118 if e != nil || info.Size() != expected.Length {
119 return ErrRefused
120 }
121 h := sha256.New()
122 buf := make([]byte, 64<<10)
123 for off := int64(0); off < expected.Length; {
124 if ctx.Err() != nil {
125 return ErrUnavailable
126 }
127 size := int64(len(buf))
128 if expected.Length-off < size {
129 size = expected.Length - off
130 }
131 n, e := pin.ReadAt(buf[:size], off)
132 if e != nil || n != int(size) {
133 return ErrUnavailable
134 }
135 h.Write(buf[:n])
136 off += int64(n)
137 }
138 if hex.EncodeToString(h.Sum(nil)) != expected.SHA256 {
139 return ErrRefused
140 }
141 info, e = pin.Stat()
142 if e != nil || info.Size() != expected.Length {
143 return ErrRefused
144 }
145 return nil
146}
147
148type readonlyConnector struct {
149 mu sync.Mutex
150 path string
151 pin *os.File
152 identity fileIdentity
153 deadline time.Time
154 sqlite *sqlite.Driver
155 active bool
156 hook func(string)
157}
158type readonlyConnection struct {
159 driver.Conn
160 owner *readonlyConnector
161 mainFD int
162 baseline map[int]descriptor
163 closed bool
164}
165
166func (c *readonlyConnector) Driver() driver.Driver { return c.sqlite }
167func (c *readonlyConnector) checkpoint(stage string) {
168 if c.hook != nil {
169 c.hook(stage)
170 }
171}
172func (c *readonlyConnector) pathMatches() error {
173 if !time.Now().Before(c.deadline) {
174 return ErrRefused
175 }
176 var s unix.Stat_t
177 if unix.Fstat(int(c.pin.Fd()), &s) != nil || (fileIdentity{uint64(s.Dev), s.Ino}) != c.identity || s.Nlink != 1 {
178 return ErrRefused
179 }
180 id, e := statExisting(c.path)
181 if e != nil || id != c.identity {
182 return ErrRefused
183 }
184 for _, suffix := range []string{"-wal", "-shm", "-journal"} {
185 _, e := os.Lstat(c.path + suffix)
186 if !os.IsNotExist(e) {
187 return ErrRefused
188 }
189 }
190 return nil
191}
192func (c *readonlyConnector) Connect(ctx context.Context) (driver.Conn, error) {
193 c.mu.Lock()
194 defer c.mu.Unlock()
195 if c.active || ctx.Err() != nil || c.pathMatches() != nil {
196 return nil, ErrRefused
197 }
198 baseline, e := descriptors()
199 if e != nil {
200 return nil, e
201 }
202 for _, d := range baseline {
203 if writable(d.flags) {
204 return nil, ErrRefused
205 }
206 }
207 c.checkpoint("before-driver")
208 u := url.URL{Scheme: "file", Path: c.path}
209 q := u.Query()
210 q.Set("mode", "ro")
211 q.Set("immutable", "1")
212 u.RawQuery = q.Encode()
213 native, e := c.sqlite.Open(u.String())
214 if e != nil {
215 return nil, ErrUnavailable
216 }
217 keep := false
218 defer func() {
219 if !keep {
220 native.Close()
221 }
222 }()
223 c.checkpoint("after-open")
224 all, e := descriptors()
225 if e != nil {
226 return nil, e
227 }
228 main := -1
229 for fd, d := range all {
230 if old, ok := baseline[fd]; ok {
231 if old != d {
232 return nil, ErrRefused
233 }
234 continue
235 }
236 if d.identity != c.identity || d.flags&unix.O_ACCMODE != unix.O_RDONLY || d.links != 1 || main != -1 {
237 return nil, ErrRefused
238 }
239 main = fd
240 }
241 if main < 0 {
242 return nil, ErrRefused
243 }
244 bound := &readonlyConnection{Conn: native, owner: c, mainFD: main, baseline: baseline}
245 if bound.check() != nil {
246 return nil, ErrRefused
247 }
248 query, ok := native.(driver.QueryerContext)
249 if !ok {
250 return nil, ErrRefused
251 }
252 rows, e := query.QueryContext(ctx, "PRAGMA database_list", nil)
253 if e != nil {
254 return nil, ErrUnavailable
255 }
256 values := make([]driver.Value, 3)
257 if rows.Next(values) != nil || values[1] != "main" || values[2] != c.path {
258 rows.Close()
259 return nil, ErrRefused
260 }
261 if rows.Next(values) != io.EOF {
262 rows.Close()
263 return nil, ErrRefused
264 }
265 if rows.Close() != nil || bound.check() != nil {
266 return nil, ErrUnavailable
267 }
268 c.active = true
269 keep = true
270 return bound, nil
271}
272func (c *readonlyConnection) check() error {
273 if c.closed || c.owner.pathMatches() != nil {
274 return ErrRefused
275 }
276 all, e := descriptors()
277 if e != nil {
278 return e
279 }
280 if len(all) != len(c.baseline)+1 {
281 return ErrRefused
282 }
283 for fd, old := range c.baseline {
284 if all[fd] != old {
285 return ErrRefused
286 }
287 }
288 main, ok := all[c.mainFD]
289 if !ok || main.identity != c.owner.identity || main.flags&unix.O_ACCMODE != unix.O_RDONLY || main.links != 1 {
290 return ErrRefused
291 }
292 return nil
293}
294func (c *readonlyConnection) Close() error {
295 c.owner.mu.Lock()
296 defer c.owner.mu.Unlock()
297 if c.closed {
298 return nil
299 }
300 c.closed = true
301 e := c.Conn.Close()
302 c.owner.active = false
303 return e
304}
305func (c *readonlyConnection) Prepare(string) (driver.Stmt, error) { return nil, ErrRefused }
306func (c *readonlyConnection) PrepareContext(context.Context, string) (driver.Stmt, error) {
307 return nil, ErrRefused
308}
309func (c *readonlyConnection) Begin() (driver.Tx, error) {
310 return c.BeginTx(context.Background(), driver.TxOptions{})
311}
312func (c *readonlyConnection) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
313 if c.check() != nil {
314 return nil, ErrRefused
315 }
316 x, ok := c.Conn.(driver.ConnBeginTx)
317 if !ok {
318 return nil, ErrRefused
319 }
320 tx, e := x.BeginTx(ctx, opts)
321 if e != nil {
322 return nil, e
323 }
324 if c.check() != nil {
325 tx.Rollback()
326 return nil, ErrUnavailable
327 }
328 return &readonlyTransaction{tx, c}, nil
329}
330func (c *readonlyConnection) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
331 if query != "ROLLBACK" && c.check() != nil {
332 return nil, ErrRefused
333 }
334 x, ok := c.Conn.(driver.ExecerContext)
335 if !ok {
336 return nil, ErrRefused
337 }
338 result, e := x.ExecContext(ctx, query, args)
339 if e != nil {
340 return nil, e
341 }
342 c.owner.checkpoint("after-exec")
343 if query != "ROLLBACK" && c.check() != nil {
344 return nil, ErrUnavailable
345 }
346 return result, nil
347}
348func (c *readonlyConnection) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
349 if c.check() != nil {
350 return nil, ErrRefused
351 }
352 x, ok := c.Conn.(driver.QueryerContext)
353 if !ok {
354 return nil, ErrRefused
355 }
356 rows, e := x.QueryContext(ctx, query, args)
357 if e != nil {
358 return nil, e
359 }
360 if c.check() != nil {
361 rows.Close()
362 return nil, ErrUnavailable
363 }
364 return &readonlyRows{rows, c}, nil
365}
366func (c *readonlyConnection) Ping(ctx context.Context) error {
367 if c.check() != nil {
368 return ErrRefused
369 }
370 p, ok := c.Conn.(driver.Pinger)
371 if !ok {
372 return ErrRefused
373 }
374 if e := p.Ping(ctx); e != nil {
375 return e
376 }
377 if c.check() != nil {
378 return ErrUnavailable
379 }
380 return nil
381}
382func (c *readonlyConnection) ResetSession(ctx context.Context) error {
383 if c.check() != nil {
384 return ErrRefused
385 }
386 if r, ok := c.Conn.(driver.SessionResetter); ok {
387 return r.ResetSession(ctx)
388 }
389 return nil
390}
391func (c *readonlyConnection) IsValid() bool { return c.check() == nil }
392
393type readonlyRows struct {
394 driver.Rows
395 c *readonlyConnection
396}
397
398func (r *readonlyRows) Next(values []driver.Value) error {
399 if r.c.check() != nil {
400 return ErrRefused
401 }
402 e := r.Rows.Next(values)
403 if e != nil {
404 return e
405 }
406 if r.c.check() != nil {
407 return ErrUnavailable
408 }
409 return nil
410}
411
412type readonlyTransaction struct {
413 driver.Tx
414 c *readonlyConnection
415}
416
417func (t *readonlyTransaction) Commit() error {
418 if t.c.check() != nil {
419 return ErrRefused
420 }
421 e := t.Tx.Commit()
422 t.c.owner.checkpoint("after-commit")
423 if e != nil {
424 return e
425 }
426 if t.c.check() != nil {
427 return ErrUnavailable
428 }
429 return nil
430}
431