Skip to content

Commit f339b73

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

File tree

4 files changed

+57
-0
lines changed

4 files changed

+57
-0
lines changed

Lib/_pydatetime.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,11 @@ 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 "

Lib/test/datetimetester.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5133,6 +5133,27 @@ 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=2)
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+
51365157
def test_compat_unpickle(self):
51375158
tests = [
51385159
b'cdatetime\ndatetime\n'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.
4+
5+
# # Uncomment one of these "section:" lines to specify which section # this
6+
entry should go in in Misc/NEWS.d. # #.. section: Security #.. section: Core
7+
and Builtins #.. section: Library #.. section: Documentation #.. section:
8+
Tests #.. section: Build #.. section: Windows #.. section: macOS #..
9+
section: IDLE #.. section: Tools/Demos #.. section: C API
10+
11+
# Write your Misc/NEWS.d entry below. It should be a simple ReST paragraph.
12+
# Don't start with "- Issue #<n>: " or "- gh-issue-<n>: " or that sort of
13+
stuff.
14+
###########################################################################

Modules/_datetimemodule.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,6 +4169,14 @@ tzinfo_fromutc(PyObject *self, PyObject *dt)
41694169
if (result == NULL)
41704170
goto Fail;
41714171

4172+
if (!PyDateTime_Check(result)) {
4173+
PyErr_Format(PyExc_TypeError,
4174+
"datetime arithmetic on a subclass returned non-datetime "
4175+
"(type %.200s)",
4176+
Py_TYPE(result)->tp_name);
4177+
Py_CLEAR(result);
4178+
goto Fail;
4179+
}
41724180
Py_DECREF(dst);
41734181
dst = call_dst(GET_DT_TZINFO(dt), result);
41744182
if (dst == NULL)
@@ -4180,6 +4188,15 @@ tzinfo_fromutc(PyObject *self, PyObject *dt)
41804188
(PyDateTime_Delta *)dst, 1));
41814189
if (result == NULL)
41824190
goto Fail;
4191+
4192+
if (!PyDateTime_Check(result)) {
4193+
PyErr_Format(PyExc_TypeError,
4194+
"datetime arithmetic on a subclass returned "
4195+
"non-datetime (type %.200s)",
4196+
Py_TYPE(result)->tp_name);
4197+
Py_CLEAR(result);
4198+
goto Fail;
4199+
}
41834200
}
41844201
Py_DECREF(delta);
41854202
Py_DECREF(dst);

0 commit comments

Comments
 (0)