carlos / platform Public

Copy SQLite callback memory through pinned runtime buffers

claude agent, operated by paul@keymail.dev a7eea082014df64bacae5acf69a0b3de4726406c parent 9d2fd89
5 files changed, +217 −38
  • internal/vfsspike/locks_linux.go +6 −6
  • internal/vfsspike/memory_linux.go +52 −0
  • internal/vfsspike/memory_linux_test.go +124 −0
  • internal/vfsspike/vfs_linux.go +33 −29
  • internal/vfsspike/vfs_linux_test.go +2 −3

CI passed — run details

diff --git a/internal/vfsspike/locks_linux.go b/internal/vfsspike/locks_linux.go
index 4aca9b967..5d1282499 100644
--- a/internal/vfsspike/locks_linux.go
+++ b/internal/vfsspike/locks_linux.go
@@ -105,9 +105,9 @@ func reservedFile(t *libc.TLS, p, out uintptr) int32 {
f := getFile(p)
f.v.mu.Lock()
defer f.v.mu.Unlock()
- *(*int32)(unsafe.Pointer(out)) = 0
+ foreignStore(t, out, int32(0))
if f.lock >= sqlite.SQLITE_LOCK_RESERVED {
- *(*int32)(unsafe.Pointer(out)) = 1
+ foreignStore(t, out, int32(1))
return sqlite.SQLITE_OK
}
l := unix.Flock_t{Type: unix.F_WRLCK, Whence: 0, Start: pending + 1, Len: 1}
@@ -115,7 +115,7 @@ func reservedFile(t *libc.TLS, p, out uintptr) int32 {
return sqlite.SQLITE_IOERR_CHECKRESERVEDLOCK
}
if l.Type != unix.F_UNLCK {
- *(*int32)(unsafe.Pointer(out)) = 1
+ foreignStore(t, out, int32(1))
}
return sqlite.SQLITE_OK
}
@@ -158,7 +158,7 @@ func ensureSHM(f *file) int32 {
return sqlite.SQLITE_OK
}
func shmMap(t *libc.TLS, p uintptr, page, size, extend int32, out uintptr) int32 {
- *(*uintptr)(unsafe.Pointer(out)) = 0
+ foreignStore(t, out, uintptr(0))
f := getFile(p)
f.v.mu.Lock()
defer f.v.mu.Unlock()
@@ -172,7 +172,7 @@ func shmMap(t *libc.TLS, p uintptr, page, size, extend int32, out uintptr) int32
if len(b) != int(size) {
return sqlite.SQLITE_IOERR_SHMMAP
}
- *(*uintptr)(unsafe.Pointer(out)) = uintptr(unsafe.Pointer(&b[0]))
+ foreignStore(t, out, uintptr(unsafe.Pointer(&b[0])))
return sqlite.SQLITE_OK
}
var s unix.Stat_t
@@ -193,7 +193,7 @@ func shmMap(t *libc.TLS, p uintptr, page, size, extend int32, out uintptr) int32
return sqlite.SQLITE_IOERR_SHMMAP
}
f.regions[page] = b
- *(*uintptr)(unsafe.Pointer(out)) = uintptr(unsafe.Pointer(&b[0]))
+ foreignStore(t, out, uintptr(unsafe.Pointer(&b[0])))
return sqlite.SQLITE_OK
}
func shmLock(t *libc.TLS, p uintptr, offset, n, flags int32) int32 {
diff --git a/internal/vfsspike/memory_linux.go b/internal/vfsspike/memory_linux.go
new file mode 100644
index 000000000..4b38109ce
--- /dev/null
+++ b/internal/vfsspike/memory_linux.go
@@ -0,0 +1,52 @@
+//go:build linux && (amd64 || arm64)
+
+package vfsspike
+
+import (
+ "runtime"
+ "unsafe"
+
+ "modernc.org/libc"
+ sqlite "modernc.org/sqlite/lib"
+)
+
+type foreignValue interface {
+ byte | int32 | int64 | uintptr | float64 | sqlite.Tsqlite3_file | sqlite.Tsqlite3_io_methods | sqlite.Tsqlite3_vfs
+}
+
+// SQLite owns callback addresses. Copy through its libc bridge while the Go
+// staging object is pinned; these types contain no Go pointers.
+func foreignStore[T foreignValue](tls *libc.TLS, dst uintptr, value T) {
+ var pin runtime.Pinner
+ pin.Pin(&value)
+ defer pin.Unpin()
+ libc.Xmemcpy(tls, dst, uintptr(unsafe.Pointer(&value)), uint64(unsafe.Sizeof(value)))
+}
+
+func foreignLoad[T foreignValue](tls *libc.TLS, src uintptr) (value T) {
+ var pin runtime.Pinner
+ pin.Pin(&value)
+ defer pin.Unpin()
+ libc.Xmemcpy(tls, uintptr(unsafe.Pointer(&value)), src, uint64(unsafe.Sizeof(value)))
+ return value
+}
+
+func copyToForeign(tls *libc.TLS, dst uintptr, value []byte) {
+ if len(value) == 0 {
+ return
+ }
+ var pin runtime.Pinner
+ pin.Pin(&value[0])
+ defer pin.Unpin()
+ libc.Xmemcpy(tls, dst, uintptr(unsafe.Pointer(&value[0])), uint64(len(value)))
+}
+
+func copyFromForeign(tls *libc.TLS, value []byte, src uintptr) {
+ if len(value) == 0 {
+ return
+ }
+ var pin runtime.Pinner
+ pin.Pin(&value[0])
+ defer pin.Unpin()
+ libc.Xmemcpy(tls, uintptr(unsafe.Pointer(&value[0])), src, uint64(len(value)))
+}
diff --git a/internal/vfsspike/memory_linux_test.go b/internal/vfsspike/memory_linux_test.go
new file mode 100644
index 000000000..d05218cb0
--- /dev/null
+++ b/internal/vfsspike/memory_linux_test.go
@@ -0,0 +1,124 @@
+//go:build linux && (amd64 || arm64)
+
+package vfsspike
+
+import (
+ "bytes"
+ "testing"
+ "unsafe"
+
+ "modernc.org/libc"
+ sqlite "modernc.org/sqlite/lib"
+)
+
+func sqliteBuffer(t *testing.T, tls *libc.TLS, n uint64) uintptr {
+ t.Helper()
+ p := sqlite.Xsqlite3_malloc64(tls, n)
+ if p == 0 {
+ t.Fatalf("sqlite3_malloc64(%d) returned nil", n)
+ }
+ t.Cleanup(func() { sqlite.Xsqlite3_free(tls, p) })
+ return p
+}
+
+func openMainCallback(t *testing.T, v *VFS) uintptr {
+ t.Helper()
+ z, err := libc.CString(v.main)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { libc.Xfree(v.tls, z) })
+ p := sqliteBuffer(t, v.tls, uint64(unsafe.Sizeof(sqlite.Tsqlite3_file{})))
+ flags := int32(sqlite.SQLITE_OPEN_MAIN_DB | sqlite.SQLITE_OPEN_READWRITE)
+ if rc := openFile(v.tls, v.ptr, z, p, flags, 0); rc != sqlite.SQLITE_OK {
+ t.Fatalf("openFile = %d, want SQLITE_OK", rc)
+ }
+ t.Cleanup(func() {
+ if getFile(p) != nil {
+ if rc := closeFile(v.tls, p); rc != sqlite.SQLITE_OK {
+ t.Errorf("closeFile = %d, want SQLITE_OK", rc)
+ }
+ }
+ })
+ return p
+}
+
+func TestForeignMemoryRoundTrip(t *testing.T) {
+ v, _ := setup(t)
+ p := sqliteBuffer(t, v.tls, 16)
+
+ foreignStore(v.tls, p, int64(0x102030405060708))
+ if got := foreignLoad[int64](v.tls, p); got != 0x102030405060708 {
+ t.Fatalf("foreignLoad = %#x", got)
+ }
+
+ want := []byte{0, 1, 2, 0xfe, 0xff}
+ copyToForeign(v.tls, p, want)
+ got := make([]byte, len(want))
+ copyFromForeign(v.tls, got, p)
+ if !bytes.Equal(got, want) {
+ t.Fatalf("copied bytes = %v, want %v", got, want)
+ }
+
+ copyToForeign(v.tls, 0, nil)
+ copyFromForeign(v.tls, nil, 0)
+}
+
+func TestCallbackReadWriteAndShortReadZeroFill(t *testing.T) {
+ v, _ := setup(t)
+ f := openMainCallback(t, v)
+ src := sqliteBuffer(t, v.tls, 3)
+ copyToForeign(v.tls, src, []byte("abc"))
+ if rc := writeFile(v.tls, f, src, 3, 4); rc != sqlite.SQLITE_OK {
+ t.Fatalf("writeFile = %d, want SQLITE_OK", rc)
+ }
+
+ out := sqliteBuffer(t, v.tls, 6)
+ copyToForeign(v.tls, out, bytes.Repeat([]byte{0xa5}, 6))
+ if rc := readFile(v.tls, f, out, 6, 4); rc != sqlite.SQLITE_IOERR_SHORT_READ {
+ t.Fatalf("readFile = %d, want SQLITE_IOERR_SHORT_READ", rc)
+ }
+ got := make([]byte, 6)
+ copyFromForeign(v.tls, got, out)
+ if want := []byte{'a', 'b', 'c', 0, 0, 0}; !bytes.Equal(got, want) {
+ t.Fatalf("short read buffer = %v, want %v", got, want)
+ }
+}
+
+func TestCallbackForeignOutputs(t *testing.T) {
+ v, _ := setup(t)
+ f := openMainCallback(t, v)
+ z, err := libc.CString(v.main)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer libc.Xfree(v.tls, z)
+
+ path := sqliteBuffer(t, v.tls, 16)
+ copyToForeign(v.tls, path, bytes.Repeat([]byte{0xa5}, 16))
+ if rc := fullPath(v.tls, v.ptr, z, 16, path); rc != sqlite.SQLITE_OK {
+ t.Fatalf("fullPath = %d, want SQLITE_OK", rc)
+ }
+ gotPath := make([]byte, 16)
+ copyFromForeign(v.tls, gotPath, path)
+ wantPrefix := append([]byte(v.main), 0)
+ if !bytes.Equal(gotPath[:len(wantPrefix)], wantPrefix) {
+ t.Fatalf("full path prefix = %v, want %v", gotPath[:len(wantPrefix)], wantPrefix)
+ }
+ if !bytes.Equal(gotPath[len(wantPrefix):], bytes.Repeat([]byte{0xa5}, 16-len(wantPrefix))) {
+ t.Fatalf("fullPath changed bytes beyond NUL: %v", gotPath)
+ }
+
+ scalar := sqliteBuffer(t, v.tls, 8)
+ foreignStore(v.tls, scalar, int32(-1))
+ if rc := accessFile(v.tls, v.ptr, z, sqlite.SQLITE_ACCESS_EXISTS, scalar); rc != sqlite.SQLITE_OK || foreignLoad[int32](v.tls, scalar) != 1 {
+ t.Fatalf("accessFile = %d, out %d", rc, foreignLoad[int32](v.tls, scalar))
+ }
+ if rc := currentTime64(v.tls, v.ptr, scalar); rc != sqlite.SQLITE_OK || foreignLoad[int64](v.tls, scalar) <= 0 {
+ t.Fatalf("currentTime64 = %d, out %d", rc, foreignLoad[int64](v.tls, scalar))
+ }
+ foreignStore(v.tls, scalar, int32(-1))
+ if rc := controlFile(v.tls, f, sqlite.SQLITE_FCNTL_LOCKSTATE, scalar); rc != sqlite.SQLITE_OK || foreignLoad[int32](v.tls, scalar) != 0 {
+ t.Fatalf("controlFile = %d, out %d", rc, foreignLoad[int32](v.tls, scalar))
+ }
+}
diff --git a/internal/vfsspike/vfs_linux.go b/internal/vfsspike/vfs_linux.go
index 896cde8f6..4a6ffeeda 100644
--- a/internal/vfsspike/vfs_linux.go
+++ b/internal/vfsspike/vfs_linux.go
@@ -60,10 +60,10 @@ var registry = struct {
}{vfs: make(map[uintptr]*VFS), files: make(map[uintptr]*file), inodes: make(map[identity]bool)}
var barrier atomic.Uint64
-func fp[T any](f T) uintptr { return *(*uintptr)(unsafe.Pointer(&f)) }
-func bytesAt(p uintptr, n int) []byte { return unsafe.Slice((*byte)(unsafe.Pointer(p)), n) }
-func getVFS(p uintptr) *VFS { registry.RLock(); defer registry.RUnlock(); return registry.vfs[p] }
-func getFile(p uintptr) *file { registry.RLock(); defer registry.RUnlock(); return registry.files[p] }
+func fp[T any](f T) uintptr { return *(*uintptr)(unsafe.Pointer(&f)) }
+
+func getVFS(p uintptr) *VFS { registry.RLock(); defer registry.RUnlock(); return registry.vfs[p] }
+func getFile(p uintptr) *file { registry.RLock(); defer registry.RUnlock(); return registry.files[p] }
func same(s *unix.Stat_t, id identity) bool {
return s.Dev == id.dev && s.Ino == id.ino && s.Mode&unix.S_IFMT == unix.S_IFREG
}
@@ -122,17 +122,17 @@ func Register(data, meta *os.File, name string) (*VFS, error) {
err = unix.ENOMEM
return nil, err
}
- *(*sqlite.Tsqlite3_io_methods)(unsafe.Pointer(v.methods)) = sqlite.Tsqlite3_io_methods{
+ foreignStore(v.tls, v.methods, sqlite.Tsqlite3_io_methods{
FiVersion: 2, FxClose: fp(closeFile), FxRead: fp(readFile), FxWrite: fp(writeFile), FxTruncate: fp(truncateFile), FxSync: fp(syncFile), FxFileSize: fp(sizeFile),
FxLock: fp(lockFile), FxUnlock: fp(unlockFile), FxCheckReservedLock: fp(reservedFile), FxFileControl: fp(controlFile), FxSectorSize: fp(sectorSize), FxDeviceCharacteristics: fp(characteristics),
FxShmMap: fp(shmMap), FxShmLock: fp(shmLock), FxShmBarrier: fp(shmBarrier), FxShmUnmap: fp(shmUnmap),
- }
- *(*sqlite.Tsqlite3_vfs)(unsafe.Pointer(v.ptr)) = sqlite.Tsqlite3_vfs{
+ })
+ foreignStore(v.tls, v.ptr, sqlite.Tsqlite3_vfs{
FiVersion: 2, FszOsFile: int32(unsafe.Sizeof(sqlite.Tsqlite3_file{})), FmxPathname: 255, FzName: v.cname,
FxOpen: fp(openFile), FxDelete: fp(deleteFile), FxAccess: fp(accessFile), FxFullPathname: fp(fullPath),
FxDlOpen: fp(dlOpen), FxDlError: fp(dlError), FxDlSym: fp(dlSym), FxDlClose: fp(dlClose),
FxRandomness: fp(randomness), FxSleep: fp(sleep), FxCurrentTime: fp(currentTime), FxCurrentTimeInt64: fp(currentTime64),
- }
+ })
registry.Lock()
registry.vfs[v.ptr] = v
registry.Unlock()
@@ -194,7 +194,7 @@ func (v *VFS) recheck() error {
return nil
}
func openFile(tls *libc.TLS, p, z, out uintptr, flags int32, outFlags uintptr) int32 {
- (*sqlite.Tsqlite3_file)(unsafe.Pointer(out)).FpMethods = 0
+ foreignStore(tls, out, sqlite.Tsqlite3_file{})
v := getVFS(p)
if v == nil {
return sqlite.SQLITE_CANTOPEN
@@ -262,9 +262,9 @@ func openFile(tls *libc.TLS, p, z, out uintptr, flags int32, outFlags uintptr) i
registry.Lock()
registry.files[out] = f
registry.Unlock()
- (*sqlite.Tsqlite3_file)(unsafe.Pointer(out)).FpMethods = v.methods
+ foreignStore(tls, out, sqlite.Tsqlite3_file{FpMethods: v.methods})
if outFlags != 0 {
- *(*int32)(unsafe.Pointer(outFlags)) = flags
+ foreignStore(tls, outFlags, flags)
}
return sqlite.SQLITE_OK
}
@@ -286,7 +286,7 @@ func closeFile(tls *libc.TLS, p uintptr) int32 {
registry.Lock()
delete(registry.files, p)
registry.Unlock()
- (*sqlite.Tsqlite3_file)(unsafe.Pointer(p)).FpMethods = 0
+ foreignStore(tls, p, sqlite.Tsqlite3_file{})
if err != nil {
return sqlite.SQLITE_IOERR_CLOSE
}
@@ -294,20 +294,22 @@ func closeFile(tls *libc.TLS, p uintptr) int32 {
}
func readFile(tls *libc.TLS, p, b uintptr, n int32, off int64) int32 {
f := getFile(p)
- buf := bytesAt(b, int(n))
+ buf := make([]byte, int(n))
got, err := unix.Pread(f.fd, buf, off)
if err != nil && err != io.EOF {
return sqlite.SQLITE_IOERR_READ
}
+ clear(buf[got:])
+ copyToForeign(tls, b, buf)
if got < len(buf) {
- clear(buf[got:])
return sqlite.SQLITE_IOERR_SHORT_READ
}
return sqlite.SQLITE_OK
}
func writeFile(tls *libc.TLS, p, b uintptr, n int32, off int64) int32 {
f := getFile(p)
- buf := bytesAt(b, int(n))
+ buf := make([]byte, int(n))
+ copyFromForeign(tls, buf, b)
for len(buf) > 0 {
got, err := unix.Pwrite(f.fd, buf, off)
if err != nil || got == 0 {
@@ -339,7 +341,7 @@ func sizeFile(tls *libc.TLS, p, out uintptr) int32 {
if unix.Fstat(getFile(p).fd, &s) != nil {
return sqlite.SQLITE_IOERR_FSTAT
}
- *(*int64)(unsafe.Pointer(out)) = s.Size
+ foreignStore(tls, out, s.Size)
return sqlite.SQLITE_OK
}
func fullPath(tls *libc.TLS, p, z uintptr, n int32, out uintptr) int32 {
@@ -348,13 +350,14 @@ func fullPath(tls *libc.TLS, p, z uintptr, n int32, out uintptr) int32 {
if !v.allowed(name) || len(name) >= int(n) {
return sqlite.SQLITE_CANTOPEN
}
- buf := bytesAt(out, int(n))
+ buf := make([]byte, len(name)+1)
copy(buf, name)
buf[len(name)] = 0
+ copyToForeign(tls, out, buf)
return sqlite.SQLITE_OK
}
func accessFile(tls *libc.TLS, p, z uintptr, flags int32, out uintptr) int32 {
- *(*int32)(unsafe.Pointer(out)) = 0
+ foreignStore(tls, out, int32(0))
v := getVFS(p)
name := libc.GoString(z)
if !v.allowed(name) {
@@ -374,7 +377,7 @@ func accessFile(tls *libc.TLS, p, z uintptr, flags int32, out uintptr) int32 {
if name == v.main && !same(&s, v.expected) {
return sqlite.SQLITE_CANTOPEN
}
- *(*int32)(unsafe.Pointer(out)) = 1
+ foreignStore(tls, out, int32(1))
return sqlite.SQLITE_OK
}
func deleteFile(tls *libc.TLS, p, z uintptr, syncDir int32) int32 {
@@ -394,16 +397,17 @@ func deleteFile(tls *libc.TLS, p, z uintptr, syncDir int32) int32 {
func dlOpen(*libc.TLS, uintptr, uintptr) uintptr { return 0 }
func dlError(tls *libc.TLS, p uintptr, n int32, out uintptr) {
if n > 0 {
- *(*byte)(unsafe.Pointer(out)) = 0
+ foreignStore(tls, out, byte(0))
}
}
func dlSym(*libc.TLS, uintptr, uintptr, uintptr) uintptr { return 0 }
func dlClose(*libc.TLS, uintptr, uintptr) {}
func randomness(tls *libc.TLS, p uintptr, n int32, out uintptr) int32 {
- b := bytesAt(out, int(n))
+ b := make([]byte, int(n))
if _, err := rand.Read(b); err != nil {
return 0
}
+ copyToForeign(tls, out, b)
return n
}
func sleep(tls *libc.TLS, p uintptr, n int32) int32 {
@@ -411,11 +415,11 @@ func sleep(tls *libc.TLS, p uintptr, n int32) int32 {
return n
}
func currentTime64(tls *libc.TLS, p, out uintptr) int32 {
- *(*int64)(unsafe.Pointer(out)) = time.Now().UnixMilli() + 210866760000000
+ foreignStore(tls, out, time.Now().UnixMilli()+210866760000000)
return sqlite.SQLITE_OK
}
func currentTime(tls *libc.TLS, p, out uintptr) int32 {
- *(*float64)(unsafe.Pointer(out)) = float64(time.Now().UnixMilli())/86400000 + 2440587.5
+ foreignStore(tls, out, float64(time.Now().UnixMilli())/86400000+2440587.5)
return sqlite.SQLITE_OK
}
func sectorSize(*libc.TLS, uintptr) int32 { return 4096 }
@@ -428,24 +432,24 @@ func controlFile(tls *libc.TLS, p uintptr, op int32, arg uintptr) int32 {
if f.v.recheck() != nil {
moved = 1
}
- *(*int32)(unsafe.Pointer(arg)) = moved
+ foreignStore(tls, arg, moved)
return sqlite.SQLITE_OK
case sqlite.SQLITE_FCNTL_LOCKSTATE:
- *(*int32)(unsafe.Pointer(arg)) = f.lock
+ foreignStore(tls, arg, f.lock)
return sqlite.SQLITE_OK
case sqlite.SQLITE_FCNTL_PERSIST_WAL:
- v := *(*int32)(unsafe.Pointer(arg))
+ v := foreignLoad[int32](tls, arg)
if v >= 0 {
f.persistent = v != 0
}
if f.persistent {
- *(*int32)(unsafe.Pointer(arg)) = 1
+ foreignStore(tls, arg, int32(1))
} else {
- *(*int32)(unsafe.Pointer(arg)) = 0
+ foreignStore(tls, arg, int32(0))
}
return sqlite.SQLITE_OK
case sqlite.SQLITE_FCNTL_POWERSAFE_OVERWRITE:
- *(*int32)(unsafe.Pointer(arg)) = 0
+ foreignStore(tls, arg, int32(0))
return sqlite.SQLITE_OK
default:
return sqlite.SQLITE_NOTFOUND
diff --git a/internal/vfsspike/vfs_linux_test.go b/internal/vfsspike/vfs_linux_test.go
index 8b73c432c..efdfa386e 100644
--- a/internal/vfsspike/vfs_linux_test.go
+++ b/internal/vfsspike/vfs_linux_test.go
@@ -286,12 +286,11 @@ func TestRefusedOpenClearsMethods(t *testing.T) {
t.Fatal("allocation")
}
defer sqlite.Xsqlite3_free(v.tls, out)
- output := (*sqlite.Tsqlite3_file)(unsafe.Pointer(out))
- output.FpMethods = v.methods
+ foreignStore(v.tls, out, sqlite.Tsqlite3_file{FpMethods: v.methods})
if rc := openFile(v.tls, tc.ptr, z, out, sqlite.SQLITE_OPEN_MAIN_DB|sqlite.SQLITE_OPEN_READWRITE, 0); rc == sqlite.SQLITE_OK {
t.Fatal("open accepted")
}
- if output.FpMethods != 0 {
+ if foreignLoad[sqlite.Tsqlite3_file](v.tls, out).FpMethods != 0 {
t.Fatal("refused open retained methods")
}
})