From 5978f2f114a5a669a39cdc46c457a3ea8ee24056 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 19 Dec 2024 12:28:21 -0500 Subject: [PATCH] Fix use-after-free in vm_ccs_free() `struct rb_callcache *` point to an imemo object on the GC heap when pushed into `struct rb_class_cc_entries`, but by the time vm_ccs_free() runs, the entire GC page the imemo was on could already be deallocated. With the right conditions, vm_ccs_free() wrote to freed memory. rb_objspace_garbage_object_p() by itself is not enough to determine liveness. I conjectured this situation to be possible in using hints from crashes in the wild. With c37bdfa5311be0aa8503b995299fb9547cede0a6 ("Make asan_poison_object poison the whole slot"), the in-tree test suite now recreates this scenario[^1][^2][^3]. Use rb_gc_pointer_to_heap_p(). Other uses of rb_objspace_garbage_object_p() could be making the same mistake, but correcting them might introduce serious performance regressions, so leave them alone for now. [^1]: http://ci.rvm.jp/results/trunk_asan@ruby-sp1/5477412 [^2]: http://ci.rvm.jp/results/trunk_asan@ruby-sp1/5477445 [^3]: http://ci.rvm.jp/results/trunk_asan@ruby-sp1/5477448 --- imemo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imemo.c b/imemo.c index f146051d81..93aa125052 100644 --- a/imemo.c +++ b/imemo.c @@ -463,7 +463,8 @@ vm_ccs_free(struct rb_class_cc_entries *ccs, int alive, VALUE klass) if (!alive) { void *ptr = asan_unpoison_object_temporary((VALUE)cc); // ccs can be free'ed. - if (!rb_objspace_garbage_object_p((VALUE)cc) && + if (rb_gc_pointer_to_heap_p((VALUE)cc) && + !rb_objspace_garbage_object_p((VALUE)cc) && IMEMO_TYPE_P(cc, imemo_callcache) && cc->klass == klass) { // OK. maybe target cc.