From 6029333d1f60a97a3e7810f0fd50cd596dbf9728 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Tue, 6 Apr 2021 20:06:18 -0700 Subject: gc: fix astonishing bug in weak hash processing. This is a flaw that has been in the code since the initial implementation in 2009. Weak hash tables are only partially marked during the initial garbage collection marking phase. They are put into a global list, which is then walked again to do the weak processing: to expire items which are not reachable, and then finish walking the table objects. Problem is, the code assumes that this late processing will not discover more hash tables and put them into that global list. This creates a problem when weak hash table contain weak hash tables, such as in the important and very common case when a global variable (binding stored in a weak hash table) contains a weak hash table! These hash tables discovered during weak hash table processing are partially marked, and left that way. The result is that their table vectors get prematurely scavenged by the garbage collector, and then fall victim to use-after-free crashing. Note: do_iters doesn't have this bug. Though the reachable_iters list resembles reachable_weak_hashes, the key --- hash.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/hash.c b/hash.c index 2cdbdae2..69b5eee0 100644 --- a/hash.c +++ b/hash.c @@ -1193,9 +1193,11 @@ val hash_equal(val obj, val seed) */ static void do_weak_tables(void) { - struct hash *h; + struct hash *h = reachable_weak_hashes; - for (h = reachable_weak_hashes; h != 0; h = h->next) { + reachable_weak_hashes = 0; + + for (; h != 0; h = h->next) { cnum i, c; /* The table of a weak hash was spuriously reached by conservative GC; it's a waste of time doing weak processing, since all keys and @@ -1290,9 +1292,10 @@ static void do_weak_tables(void) } } - /* Done with weak processing; clear out the list in preparation for - the next gc round. */ - reachable_weak_hashes = 0; + /* More weak hashes were discovered during weak processing. + Do another round. */ + if (reachable_weak_hashes) + do_weak_tables(); } static void do_iters(void) -- cgit v1.2.3