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 "database/sql"
8 "database/sql/driver"
9 "io"
10 "net/url"
11 "os"
12 "path/filepath"
13 "strconv"
14 "strings"
15 "sync"
16 "time"
17
18 "golang.org/x/sys/unix"
19 "modernc.org/sqlite"
20)
21
22type fileIdentity struct{ device, inode uint64 }
23type descriptor struct {
24 identity fileIdentity
25 flags int
26 links uint64
27}
28type boundDatabase struct {
29 db *sql.DB
30 connector *boundConnector
31}
32type boundConnector struct {
33 mu sync.Mutex
34 path string
35 pin *os.File
36 identity fileIdentity
37 deadline time.Time
38 sqlite *sqlite.Driver
39 active bool
40 hook func(string)
41}
42type boundConnection struct {
43 driver.Conn
44 owner *boundConnector
45 mainFD int
46 siblings map[string]fileIdentity
47 closed bool
48}
49
50// newBoundDatabase proves file identity only. The unmounted command adapter must
51// first establish the separately verified launch context and signed authority.
52func newBoundDatabase(path string, device, inode uint64, deadline time.Time) (*boundDatabase, error) {
53 if device == 0 || inode == 0 || !deadline.After(time.Now()) {
54 return nil, ErrRefused
55 }
56 pin, id, e := pinExisting(path)
57 if e != nil {
58 return nil, e
59 }
60 if id != (fileIdentity{device, inode}) {
61 pin.Close()
62 return nil, ErrRefused
63 }
64 c := &boundConnector{path: path, pin: pin, identity: id, deadline: deadline, sqlite: &sqlite.Driver{}}
65 db := sql.OpenDB(c)
66 db.SetMaxOpenConns(1)
67 db.SetMaxIdleConns(1)
68 return &boundDatabase{db, c}, nil
69}
70func (b *boundDatabase) close() error {
71 e := b.db.Close()
72 b.connector.mu.Lock()
73 defer b.connector.mu.Unlock()
74 if b.connector.active {
75 return ErrUnavailable
76 }
77 if x := b.connector.pin.Close(); e == nil {
78 e = x
79 }
80 return e
81}
82func pinExisting(path string) (*os.File, fileIdentity, error) {
83 if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" || len(path) > 1024 || strings.ContainsRune(path, 0) {
84 return nil, fileIdentity{}, ErrRefused
85 }
86 fd, e := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
87 if e != nil {
88 return nil, fileIdentity{}, ErrUnavailable
89 }
90 parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
91 for i, part := range parts {
92 flags := unix.O_RDONLY | unix.O_NOFOLLOW | unix.O_CLOEXEC | unix.O_NONBLOCK
93 if i < len(parts)-1 {
94 flags |= unix.O_DIRECTORY
95 }
96 next, err := unix.Openat(fd, part, flags, 0)
97 unix.Close(fd)
98 if err != nil {
99 return nil, fileIdentity{}, ErrRefused
100 }
101 fd = next
102 }
103 var s unix.Stat_t
104 if unix.Fstat(fd, &s) != nil {
105 unix.Close(fd)
106 return nil, fileIdentity{}, ErrUnavailable
107 }
108 if s.Mode&unix.S_IFMT != unix.S_IFREG || s.Nlink != 1 || s.Dev == 0 || s.Ino == 0 {
109 unix.Close(fd)
110 return nil, fileIdentity{}, ErrRefused
111 }
112 return os.NewFile(uintptr(fd), path), fileIdentity{uint64(s.Dev), s.Ino}, nil
113}
114
115// statExisting does not open the regular file: closing a second descriptor for
116// a SQLite inode would release this process's POSIX locks on that inode.
117func statExisting(path string) (fileIdentity, error) {
118 if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" || len(path) > 1024 || strings.ContainsRune(path, 0) {
119 return fileIdentity{}, ErrRefused
120 }
121 fd, e := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
122 if e != nil {
123 return fileIdentity{}, ErrUnavailable
124 }
125 parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
126 for _, part := range parts[:len(parts)-1] {
127 next, err := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
128 unix.Close(fd)
129 if err != nil {
130 return fileIdentity{}, ErrRefused
131 }
132 fd = next
133 }
134 defer unix.Close(fd)
135 var s unix.Stat_t
136 if unix.Fstatat(fd, parts[len(parts)-1], &s, unix.AT_SYMLINK_NOFOLLOW) != nil {
137 return fileIdentity{}, ErrRefused
138 }
139 if s.Mode&unix.S_IFMT != unix.S_IFREG || s.Nlink != 1 || s.Dev == 0 || s.Ino == 0 {
140 return fileIdentity{}, ErrRefused
141 }
142 return fileIdentity{uint64(s.Dev), s.Ino}, nil
143}
144func descriptors() (map[int]descriptor, error) {
145 directory, e := os.Open("/proc/self/fd")
146 if e != nil {
147 return nil, ErrUnavailable
148 }
149 defer directory.Close()
150 entries, e := directory.Readdirnames(-1)
151 if e != nil {
152 return nil, ErrUnavailable
153 }
154 out := map[int]descriptor{}
155 for _, entry := range entries {
156 fd, e := strconv.Atoi(entry)
157 if e != nil {
158 return nil, ErrRefused
159 }
160 var s unix.Stat_t
161 if fd == int(directory.Fd()) {
162 continue
163 }
164 if e = unix.Fstat(fd, &s); e != nil {
165 return nil, ErrRefused
166 }
167 if s.Mode&unix.S_IFMT != unix.S_IFREG {
168 continue
169 }
170 flags, e := unix.FcntlInt(uintptr(fd), unix.F_GETFL, 0)
171 if e != nil {
172 return nil, ErrUnavailable
173 }
174 out[fd] = descriptor{fileIdentity{uint64(s.Dev), s.Ino}, flags, uint64(s.Nlink)}
175 }
176 return out, nil
177}
178func writable(flags int) bool { return flags&unix.O_ACCMODE != unix.O_RDONLY }
179func (c *boundConnector) Driver() driver.Driver { return c.sqlite }
180func (c *boundConnector) checkpoint(stage string) {
181 if c.hook != nil {
182 c.hook(stage)
183 }
184}
185func (c *boundConnector) pathMatches() error {
186 if !time.Now().Before(c.deadline) {
187 return ErrRefused
188 }
189 var stat unix.Stat_t
190 if unix.Fstat(int(c.pin.Fd()), &stat) != nil || (fileIdentity{uint64(stat.Dev), stat.Ino}) != c.identity || stat.Nlink != 1 {
191 return ErrRefused
192 }
193 id, e := statExisting(c.path)
194 if e != nil {
195 return e
196 }
197 if id != c.identity {
198 return ErrRefused
199 }
200 return nil
201}
202func (c *boundConnector) Connect(ctx context.Context) (driver.Conn, error) {
203 c.mu.Lock()
204 defer c.mu.Unlock()
205 if c.active || ctx.Err() != nil || c.pathMatches() != nil {
206 return nil, ErrRefused
207 }
208 c.checkpoint("before-inventory")
209 baseline, e := descriptors()
210 if e != nil {
211 return nil, e
212 }
213 for _, d := range baseline {
214 if writable(d.flags) {
215 return nil, ErrRefused
216 }
217 }
218 c.checkpoint("before-open")
219 if c.pathMatches() != nil {
220 return nil, ErrRefused
221 }
222 c.checkpoint("before-driver")
223 u := url.URL{Scheme: "file", Path: c.path}
224 q := u.Query()
225 q.Set("mode", "rw")
226 u.RawQuery = q.Encode()
227 native, e := c.sqlite.Open(u.String())
228 if e != nil {
229 return nil, ErrUnavailable
230 }
231 keep := false
232 defer func() {
233 if !keep {
234 native.Close()
235 }
236 }()
237 c.checkpoint("after-open")
238 current, e := descriptors()
239 if e != nil {
240 return nil, e
241 }
242 mainFD := -1
243 for fd, d := range current {
244 if !writable(d.flags) {
245 continue
246 }
247 if _, old := baseline[fd]; old {
248 return nil, ErrRefused
249 }
250 if d.identity == c.identity {
251 if d.flags&unix.O_ACCMODE != unix.O_RDWR || d.links != 1 || mainFD != -1 {
252 return nil, ErrRefused
253 }
254 mainFD = fd
255 }
256 }
257 if mainFD < 0 {
258 return nil, ErrRefused
259 }
260 bound := &boundConnection{Conn: native, owner: c, mainFD: mainFD, siblings: map[string]fileIdentity{}}
261 if e = bound.check(); e != nil {
262 return nil, e
263 }
264 query, ok := native.(driver.QueryerContext)
265 if !ok {
266 return nil, ErrRefused
267 }
268 rows, e := query.QueryContext(ctx, "PRAGMA database_list", nil)
269 if e != nil {
270 return nil, ErrUnavailable
271 }
272 values := make([]driver.Value, 3)
273 if rows.Next(values) != nil || values[1] != "main" || values[2] != c.path {
274 rows.Close()
275 return nil, ErrRefused
276 }
277 if e = rows.Next(values); e != io.EOF {
278 rows.Close()
279 return nil, ErrRefused
280 }
281 if rows.Close() != nil {
282 return nil, ErrUnavailable
283 }
284 c.checkpoint("after-metadata")
285 if e = bound.check(); e != nil {
286 return nil, e
287 }
288 c.active = true
289 keep = true
290 return bound, nil
291}
292func (c *boundConnection) check() error {
293 if c.closed || c.owner.pathMatches() != nil {
294 return ErrRefused
295 }
296 all, e := descriptors()
297 if e != nil {
298 return e
299 }
300 main, ok := all[c.mainFD]
301 if !ok || main.identity != c.owner.identity || main.flags&unix.O_ACCMODE != unix.O_RDWR || main.links != 1 {
302 return ErrRefused
303 }
304 count := 0
305 seenSiblings := map[string]bool{}
306 for fd, d := range all {
307 if !writable(d.flags) {
308 continue
309 }
310 if fd == c.mainFD {
311 count++
312 continue
313 }
314 if d.flags&unix.O_ACCMODE != unix.O_RDWR || d.links != 1 {
315 return ErrRefused
316 }
317 matched := false
318 for _, suffix := range []string{"-wal", "-shm"} {
319 id, e := statExisting(c.owner.path + suffix)
320 if e != nil {
321 continue
322 }
323 if id == d.identity {
324 if seenSiblings[suffix] {
325 return ErrRefused
326 }
327 seenSiblings[suffix] = true
328 if old, exists := c.siblings[suffix]; exists && old != id {
329 return ErrRefused
330 }
331 c.siblings[suffix] = id
332 matched = true
333 break
334 }
335 }
336 if !matched {
337 return ErrRefused
338 }
339 }
340 if count != 1 {
341 return ErrRefused
342 }
343 return nil
344}
345func (c *boundConnection) Close() error {
346 c.owner.mu.Lock()
347 defer c.owner.mu.Unlock()
348 if c.closed {
349 return nil
350 }
351 c.closed = true
352 e := c.Conn.Close()
353 c.owner.active = false
354 return e
355}
356func (c *boundConnection) Prepare(string) (driver.Stmt, error) { return nil, ErrRefused }
357func (c *boundConnection) PrepareContext(context.Context, string) (driver.Stmt, error) {
358 return nil, ErrRefused
359}
360func (c *boundConnection) Begin() (driver.Tx, error) {
361 return c.BeginTx(context.Background(), driver.TxOptions{})
362}
363func (c *boundConnection) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
364 if c.check() != nil {
365 return nil, ErrRefused
366 }
367 x, ok := c.Conn.(driver.ConnBeginTx)
368 if !ok {
369 return nil, ErrRefused
370 }
371 tx, e := x.BeginTx(ctx, opts)
372 if e != nil {
373 return nil, e
374 }
375 if c.check() != nil {
376 tx.Rollback()
377 return nil, ErrUnavailable
378 }
379 return &boundTransaction{tx, c}, nil
380}
381func (c *boundConnection) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
382 if query != "ROLLBACK" && c.check() != nil {
383 return nil, ErrRefused
384 }
385 x, ok := c.Conn.(driver.ExecerContext)
386 if !ok {
387 return nil, ErrRefused
388 }
389 result, e := x.ExecContext(ctx, query, args)
390 if e != nil {
391 return nil, e
392 }
393 c.owner.checkpoint("after-exec")
394 if query != "ROLLBACK" && c.check() != nil {
395 return nil, ErrUnavailable
396 }
397 return result, nil
398}
399func (c *boundConnection) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
400 if c.check() != nil {
401 return nil, ErrRefused
402 }
403 x, ok := c.Conn.(driver.QueryerContext)
404 if !ok {
405 return nil, ErrRefused
406 }
407 rows, e := x.QueryContext(ctx, query, args)
408 if e != nil {
409 return nil, e
410 }
411 if c.check() != nil {
412 rows.Close()
413 return nil, ErrUnavailable
414 }
415 return &boundRows{rows, c}, nil
416}
417func (c *boundConnection) Ping(ctx context.Context) error {
418 if c.check() != nil {
419 return ErrRefused
420 }
421 p, ok := c.Conn.(driver.Pinger)
422 if !ok {
423 return ErrRefused
424 }
425 if e := p.Ping(ctx); e != nil {
426 return e
427 }
428 if c.check() != nil {
429 return ErrUnavailable
430 }
431 return nil
432}
433func (c *boundConnection) ResetSession(ctx context.Context) error {
434 if c.check() != nil {
435 return ErrRefused
436 }
437 if r, ok := c.Conn.(driver.SessionResetter); ok {
438 return r.ResetSession(ctx)
439 }
440 return nil
441}
442func (c *boundConnection) IsValid() bool { return c.check() == nil }
443
444type boundRows struct {
445 driver.Rows
446 c *boundConnection
447}
448
449func (r *boundRows) Next(values []driver.Value) error {
450 if r.c.check() != nil {
451 return ErrRefused
452 }
453 e := r.Rows.Next(values)
454 if e != nil {
455 return e
456 }
457 if r.c.check() != nil {
458 return ErrUnavailable
459 }
460 return nil
461}
462
463type boundTransaction struct {
464 driver.Tx
465 c *boundConnection
466}
467
468func (t *boundTransaction) Commit() error {
469 if t.c.check() != nil {
470 return ErrRefused
471 }
472 e := t.Tx.Commit()
473 t.c.owner.checkpoint("after-commit")
474 if e != nil {
475 return e
476 }
477 if t.c.check() != nil {
478 return ErrUnavailable
479 }
480 return nil
481}
482