data: collect-safe movable-pointer FFI knowledge
ober
edef9ab2f51df7076e2727ddf51465456ab0753f
--- a/data/anti-patterns.sexp +++ b/data/anti-patterns.sexp @@ -5964,4 +5964,25 @@ ("tools" "jerboa_check_balance" "jerboa_verify" - "jerboa_compile_check"))) + "jerboa_compile_check")) + (("advice" + . + "Declare the binding with void* and bounce through a scoped native buffer: foreign-alloc + foreign-set!/foreign-ref copy + foreign-free inside dynamic-wind (see call-with-rustls-io-buffer in (std net tls-rustls)). Keep the public bytevector signature unchanged so call sites don't change. Audit with: rg \"__collect_safe\" -A1 | rg \"u8\\*|string\". Blocking calls WITHOUT __collect_safe are safe from this (GC cannot run) but pin the scheduler; either make them fast/nonblocking or use the void* bounce pattern.") + ("avoid" + . + "Never pass a movable Scheme bytevector/string as u8*/string to a __collect_safe foreign-procedure. The call releases the Chez TC mutex while parked in a syscall; another thread triggers GC; the moving collector relocates the bytevector; native code then reads/writes the stale address. Result: heap corruption and later \"Exception: invalid memory reference\" segfaults in unrelated-looking places (observed as SEGFAULT ctx=tui.agent-worker in jcode's rustls HTTPS streaming).") + ("id" . "collect-safe-u8-star-movable-buffer") + ("kinds" "ffi" "bug-fix" "codegen") + ("pattern" + . + "(foreign-procedure __collect_safe \"...\" (... u8* ...) ...)") + ("severity" . "high") + ("tags" "ffi" "collect-safe" "u8*" "gc" "segfault" + "invalid-memory-reference" "foreign-alloc" "void*") + ("title" + . + "__collect_safe FFI with u8* (movable Scheme bytevector) segfaults after GC") + ("tools" + "jerboa_ffi_null_safety" + "jerboa_security_scan" + "jerboa_ffi_type_check"))) --- a/data/error-fixes.sexp +++ b/data/error-fixes.sexp @@ -3374,4 +3374,16 @@ "Do not pass (make-time ...) to Chez `sleep` when (jerboa prelude) is imported: the prelude shadows make-time with the (std datetime) constructor, which produces a dt-raw record, not a Chez time record. Use the prelude's own (sleep-ms ms) instead (it internally uses a private %chez-make-time). If you must build a duration by hand, re-import Chez's make-time under a rename: (rename (only (chezscheme) make-time) (make-time %chez-make-time)).") ("id" . "sleep-prelude-make-time-type-mismatch") ("pattern" . "is not a time record of type time-duration") + ("type" . "runtime")) + (("code_example" + . + ";; BAD (heap corruption -> segfault after GC while blocked):\n;; (foreign-procedure __collect_safe \"jerboa_tls_read\" (unsigned-64 u8* unsigned-64) int)\n;; GOOD: void* + scoped foreign buffer\n(def c-read (foreign-procedure __collect_safe \"jerboa_tls_read\" (unsigned-64 void* unsigned-64) int))\n;; copy through foreign-alloc'd memory, freed via dynamic-wind") + ("explanation" + . + "A __collect_safe foreign call releases the Chez TC mutex while parked in the kernel, so the moving collector can relocate a bytevector/string passed as u8*/u16*/u32*/string; native code then reads/writes the stale address, corrupting the heap. The crash often surfaces later in an unrelated-looking thread (seen in production as SEGFAULT ctx=tui.agent-worker in jcode's crash.log during rustls HTTPS streaming).") + ("fix" + . + "Never pass movable Scheme pointers to __collect_safe bindings. Redeclare with void* and bounce through a scoped foreign-alloc buffer (copy in/out, foreign-free via dynamic-wind) -- the call-with-rustls-io-buffer pattern in (std net tls-rustls). Audit with: rg \"__collect_safe\" -A1 | rg \"u8\\*|string\". Blocking calls without __collect_safe are GC-safe but pin the scheduler.") + ("id" . "collect-safe-movable-pointer-segfault") + ("pattern" . "invalid memory reference|Some debugging context lost") ("type" . "runtime")))