Skip to content

Commit 80e0dd3

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 00a128a commit 80e0dd3

File tree

3 files changed

+51
-4
lines changed

3 files changed

+51
-4
lines changed

changes/2776-samuelcolvin.md

Lines changed: 2 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
# if greater than this, the number is in ms, if less than or equal it's in seconds
5959
# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
6060
MS_WATERSHED = int(2e10)
61+
# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9
62+
MAX_NUMBER = int(3e20)
6163
StrBytesIntFloat = Union[str, bytes, int, float]
6264

6365

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

7476

7577
def from_unix_seconds(seconds: Union[int, float]) -> datetime:
78+
if seconds > MAX_NUMBER:
79+
return datetime.max
80+
elif seconds < -MAX_NUMBER:
81+
return datetime.min
82+
7683
while abs(seconds) > MS_WATERSHED:
7784
seconds /= 1000
7885
dt = EPOCH + timedelta(seconds=seconds)

tests/test_datetime_parse.py

Lines changed: 42 additions & 4 deletions
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
@@ -123,11 +132,19 @@ def test_time_parsing(value, result):
123132
(1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms
124133
(1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs
125134
(1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns
135+
('infinity', datetime(9999, 12, 31, 23, 59, 59, 999999)),
136+
('inf', datetime(9999, 12, 31, 23, 59, 59, 999999)),
137+
('inf ', datetime(9999, 12, 31, 23, 59, 59, 999999)),
138+
(1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)),
139+
(float('inf'), datetime(9999, 12, 31, 23, 59, 59, 999999)),
140+
('-infinity', datetime(1, 1, 1, 0, 0)),
141+
('-inf', datetime(1, 1, 1, 0, 0)),
142+
('nan', ValueError),
126143
],
127144
)
128145
def test_datetime_parsing(value, result):
129-
if result == errors.DateTimeError:
130-
with pytest.raises(errors.DateTimeError):
146+
if type(result) == type and issubclass(result, Exception):
147+
with pytest.raises(result):
131148
parse_datetime(value)
132149
else:
133150
assert parse_datetime(value) == result
@@ -251,3 +268,24 @@ class Model(BaseModel):
251268
'type': 'value_error.unicodedecode',
252269
'msg': "'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte",
253270
}
271+
272+
273+
def test_nan():
274+
class Model(BaseModel):
275+
dt: datetime
276+
d: date
277+
278+
with pytest.raises(ValidationError) as exc_info:
279+
Model(dt='nan', d='nan')
280+
assert exc_info.value.errors() == [
281+
{
282+
'loc': ('dt',),
283+
'msg': 'cannot convert float NaN to integer',
284+
'type': 'value_error',
285+
},
286+
{
287+
'loc': ('d',),
288+
'msg': 'cannot convert float NaN to integer',
289+
'type': 'value_error',
290+
},
291+
]

0 commit comments

Comments
 (0)