merge revision(s) a84c99468f: [Backport #19575]

Fix crash in Time on 32-bit systems

	[Bug #19575]

	struct vtm is packed causing it to have a size that is not aligned on
	32-bit systems. When allocating it on the stack, it will have unaligned
	addresses which means that the fields won't be marked by the GC when
	scanning the stack (since the GC only marks aligned addresses). This can
	cause crashes when the fields are heap allocated objects like Bignums.

	This commit moves the flags in struct time_object into struct vtm for
	space efficiency and removes the need for packing.

	This is an example of a crash:

	    ruby(rb_print_backtrace+0xd) [0x56848945] ../src/vm_dump.c:785
	    ruby(rb_vm_bugreport) ../src/vm_dump.c:1101
	    ruby(rb_assert_failure+0x7a) [0x56671857] ../src/error.c:878
	    ruby(vm_search_cc+0x0) [0x56666e47] ../src/vm_method.c:1366
	    ruby(rb_vm_search_method_slowpath) ../src/vm_insnhelper.c:2090
	    ruby(callable_method_entry+0x5) [0x568232d3] ../src/vm_method.c:1406
	    ruby(rb_callable_method_entry) ../src/vm_method.c:1413
	    ruby(gccct_method_search_slowpath) ../src/vm_eval.c:427
	    ruby(gccct_method_search+0x20f) [0x568237ef] ../src/vm_eval.c:476
	    ruby(opt_equality_by_mid_slowpath+0x2c) [0x5682388c] ../src/vm_insnhelper.c:2338
	    ruby(rb_equal+0x37) [0x566fe577] ../src/object.c:133
	    ruby(rb_big_eq+0x34) [0x56876ee4] ../src/bignum.c:5554
	    ruby(rb_int_equal+0x14) [0x566f3ed4] ../src/numeric.c:4640
	    ruby(rb_int_equal) ../src/numeric.c:4634
	    ruby(vm_call0_cfunc_with_frame+0x6d) [0x568303c2] ../src/vm_eval.c:148
	    ruby(vm_call0_cfunc) ../src/vm_eval.c:162
	    ruby(vm_call0_body) ../src/vm_eval.c:208
	    ruby(rb_funcallv_scope+0xd1) [0x56833971] ../src/vm_eval.c:85
	    ruby(RB_TEST+0x0) [0x567e8488] ../src/time.c:78
	    ruby(eq) ../src/time.c:78
	    ruby(small_vtm_sub) ../src/time.c:1523
	    ruby(timelocalw+0x23b) [0x567f3e9b] ../src/time.c:1593
	    ruby(time_s_alloc+0x0) [0x567f536b] ../src/time.c:3698
	    ruby(time_new_timew) ../src/time.c:2694
	    ruby(time_s_mktime) ../src/time.c:3698
	---
	 test/ruby/test_time.rb |  7 ++-----
	 time.c                 | 57 ++++++++++++++++++++++++--------------------------
	 timev.h                |  7 +++++--
	 3 files changed, 34 insertions(+), 37 deletions(-)
This commit is contained in:
nagachika 2023-07-17 09:45:57 +09:00
parent 9fb94407b9
commit cb8d656100
4 changed files with 39 additions and 41 deletions

View file

@ -1403,11 +1403,8 @@ class TestTime < Test::Unit::TestCase
else
RbConfig::SIZEOF["void*"] # Same size as VALUE
end
expect =
GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] +
sizeof_timew +
RbConfig::SIZEOF["void*"] * 4 + 5 + # vtm
1 # tzmode, tm_got
sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8
expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm
assert_equal expect, ObjectSpace.memsize_of(t)
rescue LoadError => e
omit "failed to load objspace: #{e.message}"

60
time.c
View file

@ -1761,32 +1761,30 @@ localtimew(wideval_t timew, struct vtm *result)
#define TIME_TZMODE_FIXOFF 2
#define TIME_TZMODE_UNINITIALIZED 3
PACKED_STRUCT_UNALIGNED(struct time_object {
struct time_object {
wideval_t timew; /* time_t value * TIME_SCALE. possibly Rational. */
struct vtm vtm;
unsigned int tzmode:3; /* 0:localtime 1:utc 2:fixoff 3:uninitialized */
unsigned int tm_got:1;
});
};
#define GetTimeval(obj, tobj) ((tobj) = get_timeval(obj))
#define GetNewTimeval(obj, tobj) ((tobj) = get_new_timeval(obj))
#define IsTimeval(obj) rb_typeddata_is_kind_of((obj), &time_data_type)
#define TIME_INIT_P(tobj) ((tobj)->tzmode != TIME_TZMODE_UNINITIALIZED)
#define TIME_INIT_P(tobj) ((tobj)->vtm.tzmode != TIME_TZMODE_UNINITIALIZED)
#define TZMODE_UTC_P(tobj) ((tobj)->tzmode == TIME_TZMODE_UTC)
#define TZMODE_SET_UTC(tobj) ((tobj)->tzmode = TIME_TZMODE_UTC)
#define TZMODE_UTC_P(tobj) ((tobj)->vtm.tzmode == TIME_TZMODE_UTC)
#define TZMODE_SET_UTC(tobj) ((tobj)->vtm.tzmode = TIME_TZMODE_UTC)
#define TZMODE_LOCALTIME_P(tobj) ((tobj)->tzmode == TIME_TZMODE_LOCALTIME)
#define TZMODE_SET_LOCALTIME(tobj) ((tobj)->tzmode = TIME_TZMODE_LOCALTIME)
#define TZMODE_LOCALTIME_P(tobj) ((tobj)->vtm.tzmode == TIME_TZMODE_LOCALTIME)
#define TZMODE_SET_LOCALTIME(tobj) ((tobj)->vtm.tzmode = TIME_TZMODE_LOCALTIME)
#define TZMODE_FIXOFF_P(tobj) ((tobj)->tzmode == TIME_TZMODE_FIXOFF)
#define TZMODE_FIXOFF_P(tobj) ((tobj)->vtm.tzmode == TIME_TZMODE_FIXOFF)
#define TZMODE_SET_FIXOFF(tobj, off) \
((tobj)->tzmode = TIME_TZMODE_FIXOFF, \
((tobj)->vtm.tzmode = TIME_TZMODE_FIXOFF, \
(tobj)->vtm.utc_offset = (off))
#define TZMODE_COPY(tobj1, tobj2) \
((tobj1)->tzmode = (tobj2)->tzmode, \
((tobj1)->vtm.tzmode = (tobj2)->vtm.tzmode, \
(tobj1)->vtm.utc_offset = (tobj2)->vtm.utc_offset, \
(tobj1)->vtm.zone = (tobj2)->vtm.zone)
@ -1794,7 +1792,7 @@ static int zone_localtime(VALUE zone, VALUE time);
static VALUE time_get_tm(VALUE, struct time_object *);
#define MAKE_TM(time, tobj) \
do { \
if ((tobj)->tm_got == 0) { \
if ((tobj)->vtm.tm_got == 0) { \
time_get_tm((time), (tobj)); \
} \
} while (0)
@ -1813,7 +1811,7 @@ force_make_tm(VALUE time, struct time_object *tobj)
if (!NIL_P(zone) && zone != str_empty && zone != str_utc) {
if (zone_localtime(zone, time)) return;
}
tobj->tm_got = 0;
tobj->vtm.tm_got = 0;
time_get_tm(time, tobj);
}
@ -1849,8 +1847,8 @@ time_s_alloc(VALUE klass)
struct time_object *tobj;
obj = TypedData_Make_Struct(klass, struct time_object, &time_data_type, tobj);
tobj->tzmode = TIME_TZMODE_UNINITIALIZED;
tobj->tm_got=0;
tobj->vtm.tzmode = TIME_TZMODE_UNINITIALIZED;
tobj->vtm.tm_got = 0;
tobj->timew = WINT2FIXWV(0);
tobj->vtm.zone = Qnil;
@ -1957,7 +1955,7 @@ time_init_now(rb_execution_context_t *ec, VALUE time, VALUE zone)
time_modify(time);
GetNewTimeval(time, tobj);
TZMODE_SET_LOCALTIME(tobj);
tobj->tm_got=0;
tobj->vtm.tm_got=0;
tobj->timew = WINT2FIXWV(0);
rb_timespec_now(&ts);
tobj->timew = timenano2timew(ts.tv_sec, ts.tv_nsec);
@ -1984,7 +1982,7 @@ time_set_utc_offset(VALUE time, VALUE off)
time_modify(time);
GetTimeval(time, tobj);
tobj->tm_got = 0;
tobj->vtm.tm_got = 0;
tobj->vtm.zone = Qnil;
TZMODE_SET_FIXOFF(tobj, off);
@ -2354,7 +2352,7 @@ zone_localtime(VALUE zone, VALUE time)
if (UNDEF_P(local)) return 0;
s = extract_vtm(local, &tobj->vtm, subsecx);
tobj->tm_got = 1;
tobj->vtm.tm_got = 1;
zone_set_offset(zone, tobj, s, t);
zone_set_dst(zone, tobj, tm);
return 1;
@ -2448,7 +2446,7 @@ time_init_vtm(VALUE time, struct vtm vtm, VALUE zone)
tobj->timew = timegmw(&vtm);
vtm_day_wraparound(&vtm);
tobj->vtm = vtm;
tobj->tm_got = 1;
tobj->vtm.tm_got = 1;
TZMODE_SET_LOCALTIME(tobj);
if (zone_timelocal(zone, time)) {
return time;
@ -2464,13 +2462,13 @@ time_init_vtm(VALUE time, struct vtm vtm, VALUE zone)
vtm.isdst = 0; /* No DST in UTC */
vtm_day_wraparound(&vtm);
tobj->vtm = vtm;
tobj->tm_got = 1;
tobj->vtm.tm_got = 1;
TZMODE_SET_UTC(tobj);
return time;
}
TZMODE_SET_LOCALTIME(tobj);
tobj->tm_got=0;
tobj->vtm.tm_got=0;
tobj->timew = WINT2FIXWV(0);
if (!NIL_P(vtm.utc_offset)) {
@ -3985,7 +3983,7 @@ time_localtime(VALUE time)
GetTimeval(time, tobj);
if (TZMODE_LOCALTIME_P(tobj)) {
if (tobj->tm_got)
if (tobj->vtm.tm_got)
return time;
}
else {
@ -4001,7 +3999,7 @@ time_localtime(VALUE time)
rb_raise(rb_eArgError, "localtime error");
tobj->vtm = vtm;
tobj->tm_got = 1;
tobj->vtm.tm_got = 1;
TZMODE_SET_LOCALTIME(tobj);
return time;
}
@ -4088,7 +4086,7 @@ time_gmtime(VALUE time)
GetTimeval(time, tobj);
if (TZMODE_UTC_P(tobj)) {
if (tobj->tm_got)
if (tobj->vtm.tm_got)
return time;
}
else {
@ -4099,7 +4097,7 @@ time_gmtime(VALUE time)
GMTIMEW(tobj->timew, &vtm);
tobj->vtm = vtm;
tobj->tm_got = 1;
tobj->vtm.tm_got = 1;
TZMODE_SET_UTC(tobj);
return time;
}
@ -4113,7 +4111,7 @@ time_fixoff(VALUE time)
GetTimeval(time, tobj);
if (TZMODE_FIXOFF_P(tobj)) {
if (tobj->tm_got)
if (tobj->vtm.tm_got)
return time;
}
else {
@ -4132,7 +4130,7 @@ time_fixoff(VALUE time)
tobj->vtm.zone = zone;
vtm_add_offset(&tobj->vtm, off, +1);
tobj->tm_got = 1;
tobj->vtm.tm_got = 1;
TZMODE_SET_FIXOFF(tobj, off);
return time;
}
@ -5495,8 +5493,9 @@ end_submicro: ;
GetNewTimeval(time, tobj);
TZMODE_SET_LOCALTIME(tobj);
tobj->tm_got = 0;
tobj->vtm.tm_got = 0;
tobj->timew = timew;
if (gmt) {
TZMODE_SET_UTC(tobj);
}
@ -5559,7 +5558,8 @@ tm_from_time(VALUE klass, VALUE time)
v->subsecx = INT2FIX(0);
v->zone = Qnil;
ttm->vtm = *v;
ttm->tm_got = 1;
ttm->vtm.tm_got = 1;
TZMODE_SET_UTC(ttm);
return tm;
#else

11
timev.h
View file

@ -2,10 +2,7 @@
#define RUBY_TIMEV_H
#include "ruby/ruby.h"
#if 0
struct vtm {/* dummy for TAGS */};
#endif
PACKED_STRUCT_UNALIGNED(struct vtm {
struct vtm {
VALUE year; /* 2000 for example. Integer. */
VALUE subsecx; /* 0 <= subsecx < TIME_SCALE. possibly Rational. */
VALUE utc_offset; /* -3600 as -01:00 for example. possibly Rational. */
@ -18,7 +15,11 @@ PACKED_STRUCT_UNALIGNED(struct vtm {
unsigned int sec:6; /* 0..60 */
unsigned int wday:3; /* 0:Sunday, 1:Monday, ..., 6:Saturday 7:init */
unsigned int isdst:2; /* 0:StandardTime 1:DayLightSavingTime 3:init */
});
/* Flags for struct time_object */
unsigned int tzmode:3; /* 0:localtime 1:utc 2:fixoff 3:uninitialized */
unsigned int tm_got:1;
};
#define TIME_SCALE 1000000000

View file

@ -11,7 +11,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 2
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
#define RUBY_PATCHLEVEL 79
#define RUBY_PATCHLEVEL 80
#include "ruby/version.h"
#include "ruby/internal/abi.h"