mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
Fixed #27397 -- Prevented integer overflows on integer field lookups.
This prevents a sqlite3 crash and address a potential DDoS vector on PostgreSQL caused by full-table-scans on overflows.
This commit is contained in:
parent
cc67344db9
commit
dde2537fbb
@ -1,7 +1,7 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from django.core.exceptions import EmptyResultSet
|
from django.core.exceptions import EmptyResultSet, FullResultSet
|
||||||
from django.db.models.expressions import Case, Expression, Func, Value, When
|
from django.db.models.expressions import Case, Expression, Func, Value, When
|
||||||
from django.db.models.fields import (
|
from django.db.models.fields import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
@ -389,6 +389,24 @@ class LessThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
|
|||||||
lookup_name = "lte"
|
lookup_name = "lte"
|
||||||
|
|
||||||
|
|
||||||
|
class IntegerFieldOverflow:
|
||||||
|
underflow_exception = EmptyResultSet
|
||||||
|
overflow_exception = EmptyResultSet
|
||||||
|
|
||||||
|
def process_rhs(self, compiler, connection):
|
||||||
|
rhs = self.rhs
|
||||||
|
if isinstance(rhs, int):
|
||||||
|
field_internal_type = self.lhs.output_field.get_internal_type()
|
||||||
|
min_value, max_value = connection.ops.integer_field_range(
|
||||||
|
field_internal_type
|
||||||
|
)
|
||||||
|
if min_value is not None and rhs < min_value:
|
||||||
|
raise self.underflow_exception
|
||||||
|
if max_value is not None and rhs > max_value:
|
||||||
|
raise self.overflow_exception
|
||||||
|
return super().process_rhs(compiler, connection)
|
||||||
|
|
||||||
|
|
||||||
class IntegerFieldFloatRounding:
|
class IntegerFieldFloatRounding:
|
||||||
"""
|
"""
|
||||||
Allow floats to work as query values for IntegerField. Without this, the
|
Allow floats to work as query values for IntegerField. Without this, the
|
||||||
@ -402,13 +420,30 @@ class IntegerFieldFloatRounding:
|
|||||||
|
|
||||||
|
|
||||||
@IntegerField.register_lookup
|
@IntegerField.register_lookup
|
||||||
class IntegerGreaterThanOrEqual(IntegerFieldFloatRounding, GreaterThanOrEqual):
|
class IntegerFieldExact(IntegerFieldOverflow, Exact):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@IntegerField.register_lookup
|
@IntegerField.register_lookup
|
||||||
class IntegerLessThan(IntegerFieldFloatRounding, LessThan):
|
class IntegerGreaterThan(IntegerFieldOverflow, GreaterThan):
|
||||||
pass
|
underflow_exception = FullResultSet
|
||||||
|
|
||||||
|
|
||||||
|
@IntegerField.register_lookup
|
||||||
|
class IntegerGreaterThanOrEqual(
|
||||||
|
IntegerFieldOverflow, IntegerFieldFloatRounding, GreaterThanOrEqual
|
||||||
|
):
|
||||||
|
underflow_exception = FullResultSet
|
||||||
|
|
||||||
|
|
||||||
|
@IntegerField.register_lookup
|
||||||
|
class IntegerLessThan(IntegerFieldOverflow, IntegerFieldFloatRounding, LessThan):
|
||||||
|
overflow_exception = FullResultSet
|
||||||
|
|
||||||
|
|
||||||
|
@IntegerField.register_lookup
|
||||||
|
class IntegerLessThanOrEqual(IntegerFieldOverflow, LessThanOrEqual):
|
||||||
|
overflow_exception = FullResultSet
|
||||||
|
|
||||||
|
|
||||||
@Field.register_lookup
|
@Field.register_lookup
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from unittest import SkipTest
|
||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import IntegrityError, connection, models
|
from django.db import IntegrityError, connection, models
|
||||||
@ -94,6 +96,43 @@ class IntegerFieldTests(TestCase):
|
|||||||
instance.value = max_value
|
instance.value = max_value
|
||||||
instance.full_clean()
|
instance.full_clean()
|
||||||
|
|
||||||
|
def test_backend_range_min_value_lookups(self):
|
||||||
|
min_value = self.backend_range[0]
|
||||||
|
if min_value is None:
|
||||||
|
raise SkipTest("Backend doesn't define an integer min value.")
|
||||||
|
underflow_value = min_value - 1
|
||||||
|
self.model.objects.create(value=min_value)
|
||||||
|
# A refresh of obj is necessary because last_insert_id() is bugged
|
||||||
|
# on MySQL and returns invalid values.
|
||||||
|
obj = self.model.objects.get(value=min_value)
|
||||||
|
with self.assertNumQueries(0), self.assertRaises(self.model.DoesNotExist):
|
||||||
|
self.model.objects.get(value=underflow_value)
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
self.assertEqual(self.model.objects.get(value__gt=underflow_value), obj)
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
self.assertEqual(self.model.objects.get(value__gte=underflow_value), obj)
|
||||||
|
with self.assertNumQueries(0), self.assertRaises(self.model.DoesNotExist):
|
||||||
|
self.model.objects.get(value__lt=underflow_value)
|
||||||
|
with self.assertNumQueries(0), self.assertRaises(self.model.DoesNotExist):
|
||||||
|
self.model.objects.get(value__lte=underflow_value)
|
||||||
|
|
||||||
|
def test_backend_range_max_value_lookups(self):
|
||||||
|
max_value = self.backend_range[-1]
|
||||||
|
if max_value is None:
|
||||||
|
raise SkipTest("Backend doesn't define an integer max value.")
|
||||||
|
overflow_value = max_value + 1
|
||||||
|
obj = self.model.objects.create(value=max_value)
|
||||||
|
with self.assertNumQueries(0), self.assertRaises(self.model.DoesNotExist):
|
||||||
|
self.model.objects.get(value=overflow_value)
|
||||||
|
with self.assertNumQueries(0), self.assertRaises(self.model.DoesNotExist):
|
||||||
|
self.model.objects.get(value__gt=overflow_value)
|
||||||
|
with self.assertNumQueries(0), self.assertRaises(self.model.DoesNotExist):
|
||||||
|
self.model.objects.get(value__gte=overflow_value)
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
self.assertEqual(self.model.objects.get(value__lt=overflow_value), obj)
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
self.assertEqual(self.model.objects.get(value__lte=overflow_value), obj)
|
||||||
|
|
||||||
def test_redundant_backend_range_validators(self):
|
def test_redundant_backend_range_validators(self):
|
||||||
"""
|
"""
|
||||||
If there are stricter validators than the ones from the database
|
If there are stricter validators than the ones from the database
|
||||||
|
Loading…
Reference in New Issue
Block a user