Skip to content

Commit 158fb6a

Browse files
committed
Avoid reading TypedData type after dfree
[Bug #22101] (Backport)
1 parent 2674c91 commit 158fb6a

3 files changed

Lines changed: 60 additions & 6 deletions

File tree

ext/-test-/typeddata/typeddata.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,42 @@ test_make(VALUE klass, VALUE num)
3333
return Qnil;
3434
}
3535

36+
/*
37+
* Used to verify that rb_data_free does not read rb_data_type_t after dfree.
38+
* This intentionally frees the type descriptor from dfree so ASAN can catch
39+
* stale post-dfree reads.
40+
*/
41+
typedef struct {
42+
rb_data_type_t *type;
43+
char padding[4096];
44+
} dynamic_type_data;
45+
46+
static void
47+
dynamic_type_free(void *ptr)
48+
{
49+
dynamic_type_data *data = ptr;
50+
xfree(data->type);
51+
}
52+
53+
static VALUE
54+
test_dynamic_type(VALUE klass)
55+
{
56+
rb_data_type_t *type;
57+
dynamic_type_data *data;
58+
VALUE obj;
59+
60+
type = ALLOC(rb_data_type_t);
61+
memset(type, 0, sizeof(rb_data_type_t));
62+
type->wrap_struct_name = "dynamic_typed_data";
63+
type->function.dfree = dynamic_type_free;
64+
type->flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE;
65+
66+
obj = TypedData_Make_Struct(klass, dynamic_type_data, type, data);
67+
data->type = type;
68+
69+
return obj;
70+
}
71+
3672
void
3773
Init_typeddata(void)
3874
{
@@ -41,4 +77,5 @@ Init_typeddata(void)
4177
rb_define_alloc_func(klass, test_alloc);
4278
rb_define_singleton_method(klass, "check", test_check, 1);
4379
rb_define_singleton_method(klass, "make", test_make, 1);
80+
rb_define_singleton_method(klass, "dynamic_type", test_dynamic_type, 0);
4481
}

gc.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,29 +1164,37 @@ make_io_zombie(void *objspace, VALUE obj)
11641164
static bool
11651165
rb_data_free(void *objspace, VALUE obj)
11661166
{
1167-
void *data = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
1167+
bool typed = RTYPEDDATA_P(obj);
1168+
void *data = typed ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
11681169
if (data) {
11691170
int free_immediately = false;
1171+
bool embedded = false;
1172+
bool free_embeddable_data = false;
11701173
void (*dfree)(void *);
11711174

1172-
if (RTYPEDDATA_P(obj)) {
1173-
free_immediately = (RTYPEDDATA(obj)->type->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0;
1174-
dfree = RTYPEDDATA(obj)->type->function.dfree;
1175+
if (typed) {
1176+
const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
1177+
dfree = type->function.dfree;
1178+
if (dfree) {
1179+
embedded = RTYPEDDATA_EMBEDDED_P(obj);
1180+
free_immediately = (type->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0;
1181+
free_embeddable_data = (type->flags & RUBY_TYPED_EMBEDDABLE) && !embedded;
1182+
}
11751183
}
11761184
else {
11771185
dfree = RDATA(obj)->dfree;
11781186
}
11791187

11801188
if (dfree) {
11811189
if (dfree == RUBY_DEFAULT_FREE) {
1182-
if (!RTYPEDDATA_P(obj) || !RTYPEDDATA_EMBEDDED_P(obj)) {
1190+
if (!typed || !embedded) {
11831191
xfree(data);
11841192
RB_DEBUG_COUNTER_INC(obj_data_xfree);
11851193
}
11861194
}
11871195
else if (free_immediately) {
11881196
(*dfree)(data);
1189-
if (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_EMBEDDABLE && !RTYPEDDATA_EMBEDDED_P(obj)) {
1197+
if (free_embeddable_data) {
11901198
xfree(data);
11911199
}
11921200

test/-ext-/typeddata/test_typeddata.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,13 @@ def test_deferred_free
2626
Bug::TypedData.make(n)
2727
end;
2828
end
29+
30+
def test_dynamic_data_type_free
31+
assert_ruby_status([], "#{<<-"begin;"}\n#{<<-"end;"}")
32+
require "-test-/typeddata"
33+
begin;
34+
100.times { Bug::TypedData.dynamic_type }
35+
GC.start
36+
end;
37+
end
2938
end

0 commit comments

Comments
 (0)