Skip to content

Commit 8625105

Browse files
committed
Avoid reading TypedData type after dfree
[Bug #22101]
1 parent e4caab7 commit 8625105

3 files changed

Lines changed: 54 additions & 7 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: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,22 +1458,23 @@ rb_data_free(void *objspace, VALUE obj)
14581458
{
14591459
void *data = RTYPEDDATA_GET_DATA(obj);
14601460
if (data) {
1461-
int free_immediately = false;
1462-
void (*dfree)(void *);
1463-
1464-
free_immediately = (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0;
1465-
dfree = RTYPEDDATA_TYPE(obj)->function.dfree;
1461+
const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
1462+
void (*dfree)(void *) = type->function.dfree;
14661463

14671464
if (dfree) {
1465+
bool embedded = RTYPEDDATA_EMBEDDED_P(obj);
1466+
int free_immediately = (type->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0;
1467+
bool free_embeddable_data = RB_DATA_TYPE_EMBEDDABLE_P(type) && !embedded;
1468+
14681469
if (dfree == RUBY_DEFAULT_FREE) {
1469-
if (!RTYPEDDATA_EMBEDDED_P(obj)) {
1470+
if (!embedded) {
14701471
xfree(data);
14711472
RB_DEBUG_COUNTER_INC(obj_data_xfree);
14721473
}
14731474
}
14741475
else if (free_immediately) {
14751476
(*dfree)(data);
1476-
if (RTYPEDDATA_EMBEDDABLE_P(obj) && !RTYPEDDATA_EMBEDDED_P(obj)) {
1477+
if (free_embeddable_data) {
14771478
xfree(data);
14781479
}
14791480

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)