summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--iseq.c4
-rw-r--r--version.h2
-rw-r--r--vm_core.h1
-rw-r--r--vm_insnhelper.c19
4 files changed, 23 insertions, 3 deletions
diff --git a/iseq.c b/iseq.c
index 16625a6f0e..244cba6e01 100644
--- a/iseq.c
+++ b/iseq.c
@@ -113,7 +113,9 @@ remove_from_constant_cache(ID id, IC ic)
st_table *ics = (st_table *)lookup_result;
st_delete(ics, &ic_data, NULL);
- if (ics->num_entries == 0) {
+ if (ics->num_entries == 0 &&
+ // See comment in vm_track_constant_cache on why we need this check
+ id != vm->inserting_constant_cache_id) {
rb_id_table_delete(vm->constant_cache, id);
st_free_table(ics);
}
diff --git a/version.h b/version.h
index 30cc09bf05..3e58ef691d 100644
--- a/version.h
+++ b/version.h
@@ -11,7 +11,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 6
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
-#define RUBY_PATCHLEVEL 241
+#define RUBY_PATCHLEVEL 242
#include "ruby/version.h"
#include "ruby/internal/abi.h"
diff --git a/vm_core.h b/vm_core.h
index a8c1e6b917..20566b1141 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -724,6 +724,7 @@ typedef struct rb_vm_struct {
// and Qtrue as values. It is used when inline constant caches need to be
// invalidated or ISEQs are being freed.
struct rb_id_table *constant_cache;
+ ID inserting_constant_cache_id;
#ifndef VM_GLOBAL_CC_CACHE_TABLE_SIZE
#define VM_GLOBAL_CC_CACHE_TABLE_SIZE 1023
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index cf0631d292..2ed3681797 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -5289,7 +5289,8 @@ rb_vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *pt
static void
vm_track_constant_cache(ID id, void *ic)
{
- struct rb_id_table *const_cache = GET_VM()->constant_cache;
+ rb_vm_t *vm = GET_VM();
+ struct rb_id_table *const_cache = vm->constant_cache;
VALUE lookup_result;
st_table *ics;
@@ -5301,7 +5302,23 @@ vm_track_constant_cache(ID id, void *ic)
rb_id_table_insert(const_cache, id, (VALUE)ics);
}
+ /* The call below to st_insert could allocate which could trigger a GC.
+ * If it triggers a GC, it may free an iseq that also holds a cache to this
+ * constant. If that iseq is the last iseq with a cache to this constant, then
+ * it will free this ST table, which would cause an use-after-free during this
+ * st_insert.
+ *
+ * So to fix this issue, we store the ID that is currently being inserted
+ * and, in remove_from_constant_cache, we don't free the ST table for ID
+ * equal to this one.
+ *
+ * See [Bug #20921].
+ */
+ vm->inserting_constant_cache_id = id;
+
st_insert(ics, (st_data_t) ic, (st_data_t) Qtrue);
+
+ vm->inserting_constant_cache_id = (ID)0;
}
static void