Skip to content

Commit bdde15b

Browse files
committed
Merge pull request from GHSA-5jqp-qgf6-3pvh
* fix infinite loop in datetime parsing * add change description * switch to set a max datetime number
1 parent d2b0501 commit bdde15b

File tree

3 files changed

+51
-4
lines changed

3 files changed

+51
-4
lines changed

changes/2776-samuelcolvin.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
**Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')`
2+
(or their negative values) does not cause an infinite loop.

pydantic/datetime_parse.py

+7
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
# if greater than this, the number is in ms, if less than or equal it's in seconds
6161
# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
6262
MS_WATERSHED = int(2e10)
63+
# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9
64+
MAX_NUMBER = int(3e20)
6365
StrBytesIntFloat = Union[str, bytes, int, float]
6466

6567

@@ -75,6 +77,11 @@ def get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[Non
7577

7678

7779
def from_unix_seconds(seconds: Union[int, float]) -> datetime:
80+
if seconds > MAX_NUMBER:
81+
return datetime.max
82+
elif seconds < -MAX_NUMBER:
83+
return datetime.min
84+
7885
while abs(seconds) > MS_WATERSHED:
7986
seconds /= 1000
8087
dt = EPOCH + timedelta(seconds=seconds)

tests/test_datetime_parse.py

+42-4
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,20 @@ def create_tz(minutes):
4242
(1_549_316_052_104, date(2019, 2, 4)), # nowish in ms
4343
(1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs
4444
(1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns
45+
('infinity', date(9999, 12, 31)),
46+
('inf', date(9999, 12, 31)),
47+
(float('inf'), date(9999, 12, 31)),
48+
('infinity ', date(9999, 12, 31)),
49+
(int('1' + '0' * 100), date(9999, 12, 31)),
50+
(1e1000, date(9999, 12, 31)),
51+
('-infinity', date(1, 1, 1)),
52+
('-inf', date(1, 1, 1)),
53+
('nan', ValueError),
4554
],
4655
)
4756
def test_date_parsing(value, result):
48-
if result == errors.DateError:
49-
with pytest.raises(errors.DateError):
57+
if type(result) == type and issubclass(result, Exception):
58+
with pytest.raises(result):
5059
parse_date(value)
5160
else:
5261
assert parse_date(value) == result
@@ -114,11 +123,19 @@ def test_time_parsing(value, result):
114123
(1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms
115124
(1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs
116125
(1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns
126+
('infinity', datetime(9999, 12, 31, 23, 59, 59, 999999)),
127+
('inf', datetime(9999, 12, 31, 23, 59, 59, 999999)),
128+
('inf ', datetime(9999, 12, 31, 23, 59, 59, 999999)),
129+
(1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)),
130+
(float('inf'), datetime(9999, 12, 31, 23, 59, 59, 999999)),
131+
('-infinity', datetime(1, 1, 1, 0, 0)),
132+
('-inf', datetime(1, 1, 1, 0, 0)),
133+
('nan', ValueError),
117134
],
118135
)
119136
def test_datetime_parsing(value, result):
120-
if result == errors.DateTimeError:
121-
with pytest.raises(errors.DateTimeError):
137+
if type(result) == type and issubclass(result, Exception):
138+
with pytest.raises(result):
122139
parse_datetime(value)
123140
else:
124141
assert parse_datetime(value) == result
@@ -242,3 +259,24 @@ class Model(BaseModel):
242259
'type': 'value_error.unicodedecode',
243260
'msg': "'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte",
244261
}
262+
263+
264+
def test_nan():
265+
class Model(BaseModel):
266+
dt: datetime
267+
d: date
268+
269+
with pytest.raises(ValidationError) as exc_info:
270+
Model(dt='nan', d='nan')
271+
assert exc_info.value.errors() == [
272+
{
273+
'loc': ('dt',),
274+
'msg': 'cannot convert float NaN to integer',
275+
'type': 'value_error',
276+
},
277+
{
278+
'loc': ('d',),
279+
'msg': 'cannot convert float NaN to integer',
280+
'type': 'value_error',
281+
},
282+
]

0 commit comments

Comments
 (0)