diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c0253f86b..542e240cc 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -814,6 +814,7 @@ class IntegerField(WritableField): type_label = 'integer' form_field_class = forms.IntegerField empty = 0 + max_digits = 39 # len(str(2**128)) default_error_messages = { 'invalid': _('Enter a whole number.'), @@ -835,7 +836,9 @@ class IntegerField(WritableField): return None try: - value = int(str(value)) + str_value = str(value) + validators.MaxLengthValidator(self.max_digits)(str_value) + value = int(str_value) except (ValueError, TypeError): raise ValidationError(self.error_messages['invalid']) return value @@ -846,6 +849,7 @@ class FloatField(WritableField): type_label = 'float' form_field_class = forms.FloatField empty = 0 + max_digits = 17 # IEEE-754 double can get at most 17 significant digits. default_error_messages = { 'invalid': _("'%s' value must be a float."), @@ -856,6 +860,7 @@ class FloatField(WritableField): return None try: + validators.MaxLengthValidator(self.max_digits)(str(value)) return float(value) except (TypeError, ValueError): msg = self.error_messages['invalid'] % value @@ -898,6 +903,9 @@ class DecimalField(WritableField): return None value = smart_text(value).strip() try: + # to not change behavior something similar to + # .validate(self, value) should be run + # validators.MaxLengthValidator(self.max_digits)(str(value)) value = Decimal(value) except DecimalException: raise ValidationError(self.error_messages['invalid']) diff --git a/tests/test_fields.py b/tests/test_fields.py index 0ddbe48b5..5517170bb 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -965,6 +965,53 @@ class FieldCallableDefault(TestCase): self.assertEqual(into, {'field': 'foo bar'}) +class CanHangOnHugeNumbers(TestCase): + """ + Test that number fields will not hang on a very large input. The + test and one approach to handling it is not intended as a final solution + but a starting point for a discussion. + + The main concern is when using a serializer in the context of the rest + framework. A malicious user could send a large number as a string + representation (here we use a small example, str(2**256), that is big + enough for demonstration). Since the number is converted to an integer + in fields.IntegerField.from_native function without checking the length + of the input, the system can hang. One solution is to use a length + validation before converting, however this seems a little messy. + Similar concerns exist in FloatField and DecimalField where a similar + solution could be used be used. + """ + def test_huge_numbers_do_not_cause_a_hang(self): + + class NumberModel(models.Model): + integer = models.IntegerField( + validators=[validators.MaxValueValidator(2 ** 32)]) + decimal = models.DecimalField(max_digits=17) + double = models.FloatField() + + class NumberSerializer(serializers.ModelSerializer): + class Meta: + model = NumberModel + + number = NumberModel(integer=1, decimal=3.14, double=2.7182818284) + + serializer = NumberSerializer( + number, data={"integer": 2, "decimal": 3.0, "double": 2.7}) + self.assertTrue(serializer.is_valid()) + + serializer = NumberSerializer( + number, data={"integer": str(2 ** 256)}, partial=True) + self.assertFalse(serializer.is_valid()) + + serializer = NumberSerializer( + number, data={"decimal": '3.14159265358979323846264'}, partial=True) + self.assertFalse(serializer.is_valid()) + + serializer = NumberSerializer( + number, data={"double": '2.718281828459045235360287'}, partial=True) + self.assertFalse(serializer.is_valid()) + + class CustomIntegerField(TestCase): """ Test that custom fields apply min_value and max_value constraints