//go:build linux package operator import ( "context" "crypto/sha256" "database/sql" "database/sql/driver" "encoding/hex" "io" "net/url" "os" "sync" "time" "golang.org/x/sys/unix" "modernc.org/sqlite" ) // ReadonlyExpectation is an observation required by the unmounted checker. // It is not launch authority. Verify must check the independently enrolled and // platform-verified handoff; deriving expectations from this database is invalid. type ReadonlyExpectation struct { Path string Device, Inode uint64 Length int64 SHA256 string } // InspectReadonly runs only in the dedicated non-serving checker process, after // its launch descriptors are closed. Verify owns launch/enrollment verification; // no production implementation exists yet. The callback must not open files, // start goroutines, retain the connection, or perform network or serving work. // Success is withheld until driver closure and the final exact byte comparison. func InspectReadonly(ctx context.Context, expected ReadonlyExpectation, verify func(context.Context, ReadonlyExpectation) error, inspect func(context.Context, *sql.Conn) error) error { return inspectReadonly(ctx, expected, verify, inspect, nil) } func inspectReadonly(ctx context.Context, expected ReadonlyExpectation, verify func(context.Context, ReadonlyExpectation) error, inspect func(context.Context, *sql.Conn) error, hook func(string)) (err error) { deadline, bounded := ctx.Deadline() if !bounded || !deadline.After(time.Now()) || verify == nil || inspect == nil || expected.Length <= 0 || expected.Length > 256<<20 || len(expected.SHA256) != 64 { return ErrRefused } hash, e := hex.DecodeString(expected.SHA256) if e != nil || hex.EncodeToString(hash) != expected.SHA256 || expected.Device == 0 || expected.Inode == 0 { return ErrRefused } ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() deadline, _ = ctx.Deadline() if verify(ctx, expected) != nil { return ErrRefused } pin, id, e := pinExisting(expected.Path) if e != nil { return e } defer func() { if pin.Close() != nil { err = ErrUnavailable } }() if id != (fileIdentity{expected.Device, expected.Inode}) { return ErrRefused } c := &readonlyConnector{path: expected.Path, pin: pin, identity: id, deadline: deadline, sqlite: &sqlite.Driver{}, hook: hook} if c.pathMatches() != nil || hashReadonly(ctx, pin, expected) != nil { return ErrRefused } d := sql.OpenDB(c) d.SetMaxOpenConns(1) d.SetMaxIdleConns(1) defer func() { if d.Close() != nil { err = ErrUnavailable } }() conn, e := d.Conn(ctx) if e != nil { return ErrUnavailable } defer conn.Close() if _, e = conn.ExecContext(ctx, "PRAGMA query_only=ON"); e != nil { return ErrUnavailable } if _, e = conn.ExecContext(ctx, "PRAGMA temp_store=MEMORY"); e != nil { return ErrUnavailable } var temp int if conn.QueryRowContext(ctx, "PRAGMA temp_store").Scan(&temp) != nil || temp != 2 { return ErrRefused } if _, e = conn.ExecContext(ctx, "BEGIN"); e != nil { return ErrUnavailable } e = inspect(ctx, conn) if _, end := conn.ExecContext(ctx, "ROLLBACK"); end != nil { return ErrUnavailable } if e != nil { if e == ErrUnavailable { return ErrUnavailable } return ErrRefused } if conn.Close() != nil || d.Close() != nil { return ErrUnavailable } if c.pathMatches() != nil || hashReadonly(ctx, pin, expected) != nil || verify(ctx, expected) != nil { return ErrRefused } return nil } func hashReadonly(ctx context.Context, pin *os.File, expected ReadonlyExpectation) error { info, e := pin.Stat() if e != nil || info.Size() != expected.Length { return ErrRefused } h := sha256.New() buf := make([]byte, 64<<10) for off := int64(0); off < expected.Length; { if ctx.Err() != nil { return ErrUnavailable } size := int64(len(buf)) if expected.Length-off < size { size = expected.Length - off } n, e := pin.ReadAt(buf[:size], off) if e != nil || n != int(size) { return ErrUnavailable } h.Write(buf[:n]) off += int64(n) } if hex.EncodeToString(h.Sum(nil)) != expected.SHA256 { return ErrRefused } info, e = pin.Stat() if e != nil || info.Size() != expected.Length { return ErrRefused } return nil } type readonlyConnector struct { mu sync.Mutex path string pin *os.File identity fileIdentity deadline time.Time sqlite *sqlite.Driver active bool hook func(string) } type readonlyConnection struct { driver.Conn owner *readonlyConnector mainFD int baseline map[int]descriptor closed bool } func (c *readonlyConnector) Driver() driver.Driver { return c.sqlite } func (c *readonlyConnector) checkpoint(stage string) { if c.hook != nil { c.hook(stage) } } func (c *readonlyConnector) pathMatches() error { if !time.Now().Before(c.deadline) { return ErrRefused } var s unix.Stat_t if unix.Fstat(int(c.pin.Fd()), &s) != nil || (fileIdentity{uint64(s.Dev), s.Ino}) != c.identity || s.Nlink != 1 { return ErrRefused } id, e := statExisting(c.path) if e != nil || id != c.identity { return ErrRefused } for _, suffix := range []string{"-wal", "-shm", "-journal"} { _, e := os.Lstat(c.path + suffix) if !os.IsNotExist(e) { return ErrRefused } } return nil } func (c *readonlyConnector) Connect(ctx context.Context) (driver.Conn, error) { c.mu.Lock() defer c.mu.Unlock() if c.active || ctx.Err() != nil || c.pathMatches() != nil { return nil, ErrRefused } baseline, e := descriptors() if e != nil { return nil, e } for _, d := range baseline { if writable(d.flags) { return nil, ErrRefused } } c.checkpoint("before-driver") u := url.URL{Scheme: "file", Path: c.path} q := u.Query() q.Set("mode", "ro") q.Set("immutable", "1") u.RawQuery = q.Encode() native, e := c.sqlite.Open(u.String()) if e != nil { return nil, ErrUnavailable } keep := false defer func() { if !keep { native.Close() } }() c.checkpoint("after-open") all, e := descriptors() if e != nil { return nil, e } main := -1 for fd, d := range all { if old, ok := baseline[fd]; ok { if old != d { return nil, ErrRefused } continue } if d.identity != c.identity || d.flags&unix.O_ACCMODE != unix.O_RDONLY || d.links != 1 || main != -1 { return nil, ErrRefused } main = fd } if main < 0 { return nil, ErrRefused } bound := &readonlyConnection{Conn: native, owner: c, mainFD: main, baseline: baseline} if bound.check() != nil { return nil, ErrRefused } query, ok := native.(driver.QueryerContext) if !ok { return nil, ErrRefused } rows, e := query.QueryContext(ctx, "PRAGMA database_list", nil) if e != nil { return nil, ErrUnavailable } values := make([]driver.Value, 3) if rows.Next(values) != nil || values[1] != "main" || values[2] != c.path { rows.Close() return nil, ErrRefused } if rows.Next(values) != io.EOF { rows.Close() return nil, ErrRefused } if rows.Close() != nil || bound.check() != nil { return nil, ErrUnavailable } c.active = true keep = true return bound, nil } func (c *readonlyConnection) check() error { if c.closed || c.owner.pathMatches() != nil { return ErrRefused } all, e := descriptors() if e != nil { return e } if len(all) != len(c.baseline)+1 { return ErrRefused } for fd, old := range c.baseline { if all[fd] != old { return ErrRefused } } main, ok := all[c.mainFD] if !ok || main.identity != c.owner.identity || main.flags&unix.O_ACCMODE != unix.O_RDONLY || main.links != 1 { return ErrRefused } return nil } func (c *readonlyConnection) Close() error { c.owner.mu.Lock() defer c.owner.mu.Unlock() if c.closed { return nil } c.closed = true e := c.Conn.Close() c.owner.active = false return e } func (c *readonlyConnection) Prepare(string) (driver.Stmt, error) { return nil, ErrRefused } func (c *readonlyConnection) PrepareContext(context.Context, string) (driver.Stmt, error) { return nil, ErrRefused } func (c *readonlyConnection) Begin() (driver.Tx, error) { return c.BeginTx(context.Background(), driver.TxOptions{}) } func (c *readonlyConnection) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { if c.check() != nil { return nil, ErrRefused } x, ok := c.Conn.(driver.ConnBeginTx) if !ok { return nil, ErrRefused } tx, e := x.BeginTx(ctx, opts) if e != nil { return nil, e } if c.check() != nil { tx.Rollback() return nil, ErrUnavailable } return &readonlyTransaction{tx, c}, nil } func (c *readonlyConnection) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { if query != "ROLLBACK" && c.check() != nil { return nil, ErrRefused } x, ok := c.Conn.(driver.ExecerContext) if !ok { return nil, ErrRefused } result, e := x.ExecContext(ctx, query, args) if e != nil { return nil, e } c.owner.checkpoint("after-exec") if query != "ROLLBACK" && c.check() != nil { return nil, ErrUnavailable } return result, nil } func (c *readonlyConnection) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { if c.check() != nil { return nil, ErrRefused } x, ok := c.Conn.(driver.QueryerContext) if !ok { return nil, ErrRefused } rows, e := x.QueryContext(ctx, query, args) if e != nil { return nil, e } if c.check() != nil { rows.Close() return nil, ErrUnavailable } return &readonlyRows{rows, c}, nil } func (c *readonlyConnection) Ping(ctx context.Context) error { if c.check() != nil { return ErrRefused } p, ok := c.Conn.(driver.Pinger) if !ok { return ErrRefused } if e := p.Ping(ctx); e != nil { return e } if c.check() != nil { return ErrUnavailable } return nil } func (c *readonlyConnection) ResetSession(ctx context.Context) error { if c.check() != nil { return ErrRefused } if r, ok := c.Conn.(driver.SessionResetter); ok { return r.ResetSession(ctx) } return nil } func (c *readonlyConnection) IsValid() bool { return c.check() == nil } type readonlyRows struct { driver.Rows c *readonlyConnection } func (r *readonlyRows) Next(values []driver.Value) error { if r.c.check() != nil { return ErrRefused } e := r.Rows.Next(values) if e != nil { return e } if r.c.check() != nil { return ErrUnavailable } return nil } type readonlyTransaction struct { driver.Tx c *readonlyConnection } func (t *readonlyTransaction) Commit() error { if t.c.check() != nil { return ErrRefused } e := t.Tx.Commit() t.c.owner.checkpoint("after-commit") if e != nil { return e } if t.c.check() != nil { return ErrUnavailable } return nil }