Merge pull request from GHSA-5jqp-qgf6-3pvh

* fix infinite loop in datetime parsing

* add change description

* switch to set a max datetime number
This commit is contained in:
Samuel Colvin
2021-05-11 18:08:48 +01:00
committed by GitHub
parent a6154514c1
commit 7e83fdd256
3 changed files with 51 additions and 4 deletions
+2
View File
@@ -0,0 +1,2 @@
**Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')`
(or their negative values) does not cause an infinite loop.
+7
View File
@@ -58,6 +58,8 @@ EPOCH = datetime(1970, 1, 1)
# if greater than this, the number is in ms, if less than or equal it's in seconds
# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
MS_WATERSHED = int(2e10)
# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9
MAX_NUMBER = int(3e20)
StrBytesIntFloat = Union[str, bytes, int, float]
@@ -73,6 +75,11 @@ def get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[Non
def from_unix_seconds(seconds: Union[int, float]) -> datetime:
if seconds > MAX_NUMBER:
return datetime.max
elif seconds < -MAX_NUMBER:
return datetime.min
while abs(seconds) > MS_WATERSHED:
seconds /= 1000
dt = EPOCH + timedelta(seconds=seconds)
+42 -4
View File
@@ -42,11 +42,20 @@ def create_tz(minutes):
(1_549_316_052_104, date(2019, 2, 4)), # nowish in ms
(1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs
(1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns
('infinity', date(9999, 12, 31)),
('inf', date(9999, 12, 31)),
(float('inf'), date(9999, 12, 31)),
('infinity ', date(9999, 12, 31)),
(int('1' + '0' * 100), date(9999, 12, 31)),
(1e1000, date(9999, 12, 31)),
('-infinity', date(1, 1, 1)),
('-inf', date(1, 1, 1)),
('nan', ValueError),
],
)
def test_date_parsing(value, result):
if result == errors.DateError:
with pytest.raises(errors.DateError):
if type(result) == type and issubclass(result, Exception):
with pytest.raises(result):
parse_date(value)
else:
assert parse_date(value) == result
@@ -123,11 +132,19 @@ def test_time_parsing(value, result):
(1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms
(1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs
(1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns
('infinity', datetime(9999, 12, 31, 23, 59, 59, 999999)),
('inf', datetime(9999, 12, 31, 23, 59, 59, 999999)),
('inf ', datetime(9999, 12, 31, 23, 59, 59, 999999)),
(1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)),
(float('inf'), datetime(9999, 12, 31, 23, 59, 59, 999999)),
('-infinity', datetime(1, 1, 1, 0, 0)),
('-inf', datetime(1, 1, 1, 0, 0)),
('nan', ValueError),
],
)
def test_datetime_parsing(value, result):
if result == errors.DateTimeError:
with pytest.raises(errors.DateTimeError):
if type(result) == type and issubclass(result, Exception):
with pytest.raises(result):
parse_datetime(value)
else:
assert parse_datetime(value) == result
@@ -251,3 +268,24 @@ def test_unicode_decode_error(field):
'type': 'value_error.unicodedecode',
'msg': "'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte",
}
def test_nan():
class Model(BaseModel):
dt: datetime
d: date
with pytest.raises(ValidationError) as exc_info:
Model(dt='nan', d='nan')
assert exc_info.value.errors() == [
{
'loc': ('dt',),
'msg': 'cannot convert float NaN to integer',
'type': 'value_error',
},
{
'loc': ('d',),
'msg': 'cannot convert float NaN to integer',
'type': 'value_error',
},
]