…-mwm8-39rw-8826)
rb_sqlite3_aggregator_step converts an aggregate's SQLite arguments into a
VALUE array allocated with xcalloc. That memory is not a GC root. sqlite3val2rb
allocates, so a GC triggered while converting a later argument can collect a
Ruby object already stored in an earlier slot and reuse it -- the step block
then receives a wrong or freed object, and above Ruby 3.4's 640-byte embedded
boundary (where every conversion mallocs) the process can segfault under
ordinary GC.
Needs arity >= 2: the argc == 1 branch stores into a stack local, which
conservative stack scanning already pins.
Fix: register each slot with rb_gc_register_address before filling any, so a GC
during a later conversion scans every VALUE already stored, and unregister
before xfree. This is the idiom nokogiri uses at xml_xpath_context.c.
Distinct from #723, which pins the callback instances stored as SQLite
user-data and does not touch this argument array. Both are needed.
Verified on the released precompiled artifact's source, ruby 3.4.10
arm64-darwin, system SQLite, two separately built extensions:
GC.stress, arity 2 before CORRUPT 3/3 after CLEAN 3/3
ordinary GC, ~4KB rows before SIGSEGV after CLEAN 8/8 (incl. the size that crashed before)
arity 1 (control) CLEAN both
patched under GC.stress CLEAN
test_multi_argument_step_arguments_survive_gc reproduces it: the step block
checks each argument against the column it came from, under GC.stress. It fails
on an unpatched build (a slot holds an unrelated object) and passes with this
change.
See GHSA-mwm8-39rw-8826.