mirror of
git://git.code.sf.net/p/sbcl/sbcl
synced 2026-09-10 07:26:40 -04:00
Fix GC control stack stale pointer crashes on ARM64 safepoint builds
On ARM64 safepoint builds with precise GC and separate control/C
stacks (!C_STACK_IS_CONTROL_STACK), the GC scans the control stack
precisely — every pointer-shaped word is treated as a live reference
and transported. However, dead variables in active call frames may
hold stale pointers to already-freed from-space objects. Transporting
these corrupts the heap, typically manifesting as random crashes after
GC or "bad widetag" errors.
This does not affect x86/x86-64 (conservative stack scanning pins
rather than transports, masking stale pointers) or ARM64 Linux (uses
signal-based GC stop, not safepoints).
Three coordinated fixes:
1. gencgc.c: Limit control stack scan range. On safepoint builds, GC
runs as Lisp code (SUB-GC) on the control stack, so thread->csp
includes SUB-GC's own frames which may contain uninitialized data.
Temporarily limit scanning to the interrupted code's CSP (saved in
the interrupt context by fake_foreign_function_call), which excludes
the GC infrastructure frames.
2. gc-common.c (scavenge_control_stack): Validate from-space pointers
before transport. For list pointers, verify the target page is
PAGE_TYPE_CONS (on precise GC, cons cells are only on cons pages).
For other pointers, verify the header widetag is valid and its
lowtag matches the pointer's lowtag. Invalid pointers are zeroed.
3. safepoint.c: Scrub stale control stack frames after
undo_fake_foreign_function_call in the safepoint handler. This
reduces the window for stale pointers to accumulate across GC
cycles.
All changes are guarded by:
#if defined(LISP_FEATURE_SB_SAFEPOINT) && \
!defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK)
No impact on other platforms.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6aa247c9d4
commit
1984d3675f
|
|
@ -2442,6 +2442,40 @@ scavenge_control_stack(struct thread *th)
|
|||
#ifdef LISP_FEATURE_MARK_REGION_GC
|
||||
mr_preserve_object(word);
|
||||
#else
|
||||
#if defined(LISP_FEATURE_SB_SAFEPOINT) && !defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK)
|
||||
/* On safepoint builds with precise GC and separate stacks (ARM64),
|
||||
* dead variables in active frames may contain stale pointers to
|
||||
* from-space addresses that have been freed/reused. Without stack
|
||||
* maps, the GC cannot distinguish live from dead variables.
|
||||
* Transporting garbage data corrupts the heap, so validate that
|
||||
* from-space pointers actually point to plausible objects before
|
||||
* allowing scav1 to transport them. Zero invalid slots. */
|
||||
{
|
||||
page_index_t pg = find_page_index((void*)word);
|
||||
if (pg >= 0 && page_table[pg].gen == from_space) {
|
||||
lispobj *target = native_pointer(word);
|
||||
if (!forwarding_pointer_p(target)) {
|
||||
int valid;
|
||||
if (lowtag_of(word) == LIST_POINTER_LOWTAG) {
|
||||
/* Cons pointers should target cons pages */
|
||||
valid = (page_table[pg].type == PAGE_TYPE_CONS);
|
||||
} else {
|
||||
/* Headered objects: validate header widetag and
|
||||
* check that the widetag's lowtag matches the
|
||||
* pointer's lowtag */
|
||||
int widetag = *target & WIDETAG_MASK;
|
||||
valid = other_immediate_lowtag_p(widetag)
|
||||
&& LOWTAG_FOR_WIDETAG(widetag)
|
||||
&& LOWTAG_FOR_WIDETAG(widetag) == lowtag_of(word);
|
||||
}
|
||||
if (!valid) {
|
||||
*object_ptr = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
scav1(object_ptr, word);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3516,7 +3516,36 @@ garbage_collect_generation(generation_index_t generation, int raise,
|
|||
#if !defined(LISP_FEATURE_MIPS) && defined(reg_CODE) // interrupt contexts already pinned everything they see
|
||||
scavenge_interrupt_contexts(th);
|
||||
#endif
|
||||
#if defined(LISP_FEATURE_SB_SAFEPOINT) && !defined(LISP_FEATURE_C_STACK_IS_CONTROL_STACK)
|
||||
/* On safepoint builds with separate control and C stacks (ARM64),
|
||||
* GC runs as Lisp code (SUB-GC) on the control stack. The current
|
||||
* thread->csp includes SUB-GC's frames, which may contain stale or
|
||||
* uninitialized pointers. Precise scanning of these frames crashes
|
||||
* when it encounters stale pointers to freed from-space objects.
|
||||
*
|
||||
* Fix: temporarily limit the scan to the interrupted code's CSP
|
||||
* (from the interrupt context stored by fake_foreign_function_call).
|
||||
* The interrupt context registers are already scavenged by
|
||||
* scavenge_interrupt_contexts above. */
|
||||
{
|
||||
lispobj *saved_csp = access_control_stack_pointer(th);
|
||||
int ctx_idx = fixnum_value(read_TLS(FREE_INTERRUPT_CONTEXT_INDEX, th));
|
||||
if (ctx_idx > 0) {
|
||||
os_context_t *ctx = nth_interrupt_context(ctx_idx - 1, th);
|
||||
lispobj *interrupted_csp =
|
||||
(lispobj*)(uword_t)(*os_context_register_addr(ctx, reg_CSP));
|
||||
/* build_fake_control_stack_frames places a 4-word frame above
|
||||
* the interrupted CSP. Include it in the scan. */
|
||||
lispobj *limit = interrupted_csp + 4;
|
||||
if (limit < saved_csp)
|
||||
access_control_stack_pointer(th) = limit;
|
||||
}
|
||||
scavenge_control_stack(th);
|
||||
access_control_stack_pointer(th) = saved_csp;
|
||||
}
|
||||
#else
|
||||
scavenge_control_stack(th);
|
||||
#endif
|
||||
}
|
||||
|
||||
# ifdef LISP_FEATURE_SB_SAFEPOINT
|
||||
|
|
|
|||
|
|
@ -1066,6 +1066,17 @@ handle_safepoint_violation(os_context_t *ctx, os_vm_address_t fault_address)
|
|||
fake_foreign_function_call(ctx);
|
||||
thread_in_lisp_raised(ctx);
|
||||
undo_fake_foreign_function_call(ctx);
|
||||
/* Scrub stale Lisp frames left on the control stack by SUB-GC
|
||||
* and thruption handlers. undo_fake_foreign_function_call
|
||||
* zeroed thread->csp (it IS foreign_function_call_active_p on
|
||||
* ARM64), so temporarily restore it for scrubbing. */
|
||||
{
|
||||
lispobj *csp = (lispobj*)(uword_t)
|
||||
(*os_context_register_addr(ctx, reg_CSP));
|
||||
access_control_stack_pointer(self) = csp;
|
||||
scrub_thread_control_stack(self);
|
||||
access_control_stack_pointer(self) = 0;
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue