Skip to content

Commit afa85ee

Browse files
committed
gh-145166: Fix crash in tzinfo.fromutc() when subclass __new__ returns non-datetime
1 parent 4c95ad8 commit afa85ee

File tree

4 files changed

+96
-7
lines changed

4 files changed

+96
-7
lines changed

Lib/_pydatetime.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1348,11 +1348,22 @@ def fromutc(self, dt):
13481348
delta = dtoff - dtdst
13491349
if delta:
13501350
dt += delta
1351+
if not isinstance(dt, datetime):
1352+
raise TypeError(
1353+
f"datetime arithmetic on a subclass returned non-datetime "
1354+
f"(type {type(dt).__name__})"
1355+
)
13511356
dtdst = dt.dst()
13521357
if dtdst is None:
13531358
raise ValueError("fromutc(): dt.dst gave inconsistent "
13541359
"results; cannot convert")
1355-
return dt + dtdst
1360+
result = dt + dtdst
1361+
if not isinstance(result, datetime):
1362+
raise TypeError(
1363+
f"datetime arithmetic on a subclass returned non-datetime "
1364+
f"(type {type(result).__name__})"
1365+
)
1366+
return result
13561367

13571368
# Pickle support.
13581369

@@ -2063,6 +2074,11 @@ def utctimetuple(self):
20632074
offset = self.utcoffset()
20642075
if offset:
20652076
self -= offset
2077+
if not isinstance(self, datetime):
2078+
raise TypeError(
2079+
f"datetime arithmetic on a subclass returned non-datetime "
2080+
f"(type {type(self).__name__})"
2081+
)
20662082
y, m, d = self.year, self.month, self.day
20672083
hh, mm, ss = self.hour, self.minute, self.second
20682084
return _build_struct_time(y, m, d, hh, mm, ss, 0)

Lib/test/datetimetester.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5133,6 +5133,68 @@ def test_pickling(self):
51335133
self.assertEqual(derived.tzname(), 'cookie')
51345134
self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
51355135

5136+
def test_fromutc_subclass_new_returns_non_datetime(self):
5137+
call_count = 0
5138+
5139+
class EvilDatetime(self.theclass):
5140+
def __new__(cls, *args, **kwargs):
5141+
nonlocal call_count
5142+
call_count += 1
5143+
if call_count > 1:
5144+
return bytearray(b'\x00' * 200)
5145+
return super().__new__(cls, *args, **kwargs)
5146+
5147+
class SimpleTZ(tzinfo):
5148+
def utcoffset(self, dt): return timedelta(hours=1)
5149+
def dst(self, dt): return timedelta(hours=1)
5150+
def tzname(self, dt): return "Test"
5151+
5152+
tz = SimpleTZ()
5153+
dt = EvilDatetime(2000, 1, 1, 12, 0, 0, tzinfo=tz)
5154+
with self.assertRaises(TypeError):
5155+
tz.fromutc(dt)
5156+
5157+
def test_fromutc_subclass_new_returns_non_datetime_with_delta(self):
5158+
call_count = 0
5159+
5160+
class EvilDatetime(self.theclass):
5161+
def __new__(cls, *args, **kwargs):
5162+
nonlocal call_count
5163+
call_count += 1
5164+
if call_count > 1:
5165+
return bytearray(b'\x00' * 200)
5166+
return super().__new__(cls, *args, **kwargs)
5167+
class SimpleTZ(tzinfo):
5168+
def utcoffset(self, dt): return timedelta(hours=2)
5169+
def dst(self, dt): return timedelta(hours=1)
5170+
def tzname(self, dt): return "Test"
5171+
5172+
tz = SimpleTZ()
5173+
dt = EvilDatetime(2000, 1, 1, 12, 0, 0, tzinfo=tz)
5174+
with self.assertRaises(TypeError):
5175+
tz.fromutc(dt)
5176+
5177+
def test_utctimetuple_subclass_new_returns_non_datetime(self):
5178+
call_count = 0
5179+
5180+
class EvilDatetime(self.theclass):
5181+
def __new__(cls, *args, **kwargs):
5182+
nonlocal call_count
5183+
call_count += 1
5184+
if call_count > 1:
5185+
return bytearray(b'\x00' * 200)
5186+
return super().__new__(cls, *args, **kwargs)
5187+
5188+
class SimpleTZ(tzinfo):
5189+
def utcoffset(self, dt): return timedelta(hours=5)
5190+
def dst(self, dt): return timedelta(0)
5191+
def tzname(self, dt): return "Test"
5192+
5193+
tz = SimpleTZ()
5194+
dt = EvilDatetime(2000, 6, 15, 12, 0, 0, tzinfo=tz)
5195+
with self.assertRaises(TypeError):
5196+
dt.utctimetuple()
5197+
51365198
def test_compat_unpickle(self):
51375199
tests = [
51385200
b'cdatetime\ndatetime\n'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix crash in :meth:`datetime.tzinfo.fromutc` when a
2+
:class:`~datetime.datetime` subclass ``__new__`` returns a non-datetime
3+
object. A :exc:`TypeError` is now raised instead.

Modules/_datetimemodule.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4168,7 +4168,6 @@ tzinfo_fromutc(PyObject *self, PyObject *dt)
41684168
result = add_datetime_timedelta((PyDateTime_DateTime *)dt, delta, 1);
41694169
if (result == NULL)
41704170
goto Fail;
4171-
41724171
Py_DECREF(dst);
41734172
dst = call_dst(GET_DT_TZINFO(dt), result);
41744173
if (dst == NULL)
@@ -6210,11 +6209,20 @@ add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta,
62106209
return NULL;
62116210
}
62126211

6213-
return new_datetime_subclass_ex(year, month, day,
6214-
hour, minute, second, microsecond,
6215-
HASTZINFO(date) ? date->tzinfo : Py_None,
6216-
Py_TYPE(date));
6217-
}
6212+
PyObject *result = new_datetime_subclass_ex(year, month, day,
6213+
hour, minute, second, microsecond,
6214+
HASTZINFO(date) ? date->tzinfo : Py_None,
6215+
Py_TYPE(date));
6216+
if (result != NULL && !PyDateTime_Check(result)) {
6217+
PyErr_Format(PyExc_TypeError,
6218+
"datetime arithmetic on a subclass returned "
6219+
"non-datetime (type %.200s)",
6220+
Py_TYPE(result)->tp_name);
6221+
Py_DECREF(result);
6222+
return NULL;
6223+
}
6224+
return result;
6225+
}
62186226

62196227
static PyObject *
62206228
datetime_add(PyObject *left, PyObject *right)

0 commit comments

Comments
 (0)