Skip to content

Commit

Permalink
prevent long strings as int inputs, fix #1477
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Sep 5, 2022
1 parent 317bef3 commit 0685c8d
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 0 deletions.
3 changes: 3 additions & 0 deletions changes/1477-samuelcolvin.md
@@ -0,0 +1,3 @@
Prevent long (length > `10_000`) strings/bytes as input to int fields, see
[python/cpython#95778](https://github.com/python/cpython/issues/95778) and
[CVE-2020-10735](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735)
13 changes: 13 additions & 0 deletions pydantic/validators.py
Expand Up @@ -120,10 +120,23 @@ def bool_validator(v: Any) -> bool:
raise errors.BoolError()


# matches the default limit cpython, see https://github.com/python/cpython/pull/96500
max_str_int = 4_300


def int_validator(v: Any) -> int:
if isinstance(v, int) and not (v is True or v is False):
return v

# see https://github.com/pydantic/pydantic/issues/1477 and in turn, https://github.com/python/cpython/issues/95778
# this check should be unnecessary once patch releases are out for 3.7, 3.8, 3.9 and 3.10
# but better to check here until then.
# NOTICE: this does not full protect user from the DOS risk since the standard library JSON implementation
# (and other std lib modules like xml) use `int()` and are called before this, the best workaround is to
# 1. update to the latest patch release of python, 2. use a different JSON library like ujson
if isinstance(v, (str, bytes, bytearray)) and len(v) > max_str_int:
raise errors.IntegerError()

try:
return int(v)
except (TypeError, ValueError, OverflowError):
Expand Down
35 changes: 35 additions & 0 deletions tests/test_edge_cases.py
Expand Up @@ -2038,3 +2038,38 @@ class Custom:
__fields__ = True

assert not issubclass(Custom, BaseModel)


def test_long_int():
"""
see https://github.com/pydantic/pydantic/issues/1477 and in turn, https://github.com/python/cpython/issues/95778
"""

class Model(BaseModel):
x: int

assert Model(x='1' * 10_000).x == int('1' * 10_000)
assert Model(x=b'1' * 10_000).x == int('1' * 10_000)
assert Model(x=bytearray(b'1' * 10_000)).x == int('1' * 10_000)

too_long = '1' * 10_001
with pytest.raises(ValidationError) as exc_info:
Model(x=too_long)

assert exc_info.value.errors() == [
{
'loc': ('x',),
'msg': 'value is not a valid integer',
'type': 'type_error.integer',
},
]

too_long_b = too_long.encode('utf-8')
with pytest.raises(ValidationError):
Model(x=too_long_b)
with pytest.raises(ValidationError):
Model(x=bytearray(too_long_b))

# this used to hang indefinitely
with pytest.raises(ValidationError):
Model(x='1' * (10**7))

0 comments on commit 0685c8d

Please sign in to comment.