Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit aa80f49

Browse files
mxsashatimgraham
authored andcommitted
[1.4.x] Fixed queries that may return unexpected results on MySQL due to typecasting.
This is a security fix. Disclosure will follow shortly. Backport of 75c0d4e from master
1 parent 1170f28 commit aa80f49

File tree

6 files changed

+154
-2
lines changed

6 files changed

+154
-2
lines changed

django/db/models/fields/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,12 @@ def __init__(self, verbose_name=None, name=None, path='', match=None,
911911
kwargs['max_length'] = kwargs.get('max_length', 100)
912912
Field.__init__(self, verbose_name, name, **kwargs)
913913

914+
def get_prep_value(self, value):
915+
value = super(FilePathField, self).get_prep_value(value)
916+
if value is None:
917+
return None
918+
return smart_unicode(value)
919+
914920
def formfield(self, **kwargs):
915921
defaults = {
916922
'path': self.path,
@@ -1010,6 +1016,12 @@ def __init__(self, *args, **kwargs):
10101016
kwargs['max_length'] = 15
10111017
Field.__init__(self, *args, **kwargs)
10121018

1019+
def get_prep_value(self, value):
1020+
value = super(IPAddressField, self).get_prep_value(value)
1021+
if value is None:
1022+
return None
1023+
return smart_unicode(value)
1024+
10131025
def get_internal_type(self):
10141026
return "IPAddressField"
10151027

@@ -1047,12 +1059,14 @@ def get_db_prep_value(self, value, connection, prepared=False):
10471059
return value or None
10481060

10491061
def get_prep_value(self, value):
1062+
if value is None:
1063+
return value
10501064
if value and ':' in value:
10511065
try:
10521066
return clean_ipv6_address(value, self.unpack_ipv4)
10531067
except exceptions.ValidationError:
10541068
pass
1055-
return value
1069+
return smart_unicode(value)
10561070

10571071
def formfield(self, **kwargs):
10581072
defaults = {'form_class': forms.GenericIPAddressField}

docs/howto/custom-model-fields.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,16 @@ For example::
482482
return ''.join([''.join(l) for l in (value.north,
483483
value.east, value.south, value.west)])
484484

485+
.. warning::
486+
487+
If your custom field uses the ``CHAR``, ``VARCHAR`` or ``TEXT``
488+
types for MySQL, you must make sure that :meth:`.get_prep_value`
489+
always returns a string type. MySQL performs flexible and unexpected
490+
matching when a query is performed on these types and the provided
491+
value is an integer, which can cause queries to include unexpected
492+
objects in their results. This problem cannot occur if you always
493+
return a string type from :meth:`.get_prep_value`.
494+
485495
Converting query values to database values
486496
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
487497

docs/ref/databases.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,22 @@ MySQL does not support the ``NOWAIT`` option to the ``SELECT ... FOR UPDATE``
432432
statement. If ``select_for_update()`` is used with ``nowait=True`` then a
433433
``DatabaseError`` will be raised.
434434

435+
Automatic typecasting can cause unexpected results
436+
--------------------------------------------------
437+
438+
When performing a query on a string type, but with an integer value, MySQL will
439+
coerce the types of all values in the table to an integer before performing the
440+
comparison. If your table contains the values ``'abc'``, ``'def'`` and you
441+
query for ``WHERE mycolumn=0``, both rows will match. Similarly, ``WHERE mycolumn=1``
442+
will match the value ``'abc1'``. Therefore, string type fields included in Django
443+
will always cast the value to a string before using it in a query.
444+
445+
If you implement custom model fields that inherit from :class:`~django.db.models.Field`
446+
directly, are overriding :meth:`~django.db.models.Field.get_prep_value`, or use
447+
:meth:`extra() <django.db.models.query.QuerySet.extra>` or
448+
:meth:`raw() <django.db.models.Manager.raw>`, you should ensure that you
449+
perform the appropriate typecasting.
450+
435451
.. _sqlite-notes:
436452

437453
SQLite notes

docs/ref/models/querysets.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,16 @@ of the arguments is required, but you should use at least one of them.
10411041

10421042
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
10431043

1044+
.. warning::
1045+
1046+
If you are performing queries on MySQL, note that MySQL's silent type coercion
1047+
may cause unexpected results when mixing types. If you query on a string
1048+
type column, but with an integer value, MySQL will coerce the types of all values
1049+
in the table to an integer before performing the comparison. For example, if your
1050+
table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
1051+
both rows will match. To prevent this, perform the correct typecasting
1052+
before using the value in a query.
1053+
10441054
defer
10451055
~~~~~
10461056

docs/topics/db/sql.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ options that make it very powerful.
6969
database, but does nothing to enforce that. If the query does not
7070
return rows, a (possibly cryptic) error will result.
7171

72+
.. warning::
73+
74+
If you are performing queries on MySQL, note that MySQL's silent type coercion
75+
may cause unexpected results when mixing types. If you query on a string
76+
type column, but with an integer value, MySQL will coerce the types of all values
77+
in the table to an integer before performing the comparison. For example, if your
78+
table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
79+
both rows will match. To prevent this, perform the correct typecasting
80+
before using the value in a query.
81+
7282
Mapping query fields to model fields
7383
------------------------------------
7484

tests/regressiontests/model_fields/tests.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66
from django import test
77
from django import forms
88
from django.core.exceptions import ValidationError
9+
from django.db.models.fields import (
10+
AutoField, BigIntegerField, BooleanField, CharField,
11+
CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField,
12+
EmailField, FilePathField, FloatField, IntegerField, IPAddressField,
13+
GenericIPAddressField, NullBooleanField, PositiveIntegerField,
14+
PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField,
15+
TimeField, URLField)
916
from django.db import models
10-
from django.db.models.fields.files import FieldFile
17+
from django.db.models.fields.files import FileField, ImageField, FieldFile
1118
from django.utils import unittest
1219

1320
from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post,
@@ -373,3 +380,88 @@ def test_changed(self):
373380
field = d._meta.get_field('myfile')
374381
field.save_form_data(d, 'else.txt')
375382
self.assertEqual(d.myfile, 'else.txt')
383+
384+
385+
class PrepValueTest(test.TestCase):
386+
def test_AutoField(self):
387+
self.assertIsInstance(AutoField(primary_key=True).get_prep_value(1), int)
388+
389+
def test_BigIntegerField(self):
390+
self.assertIsInstance(BigIntegerField().get_prep_value(long(9999999999999999999)), long)
391+
392+
def test_BooleanField(self):
393+
self.assertIsInstance(BooleanField().get_prep_value(True), bool)
394+
395+
def test_CharField(self):
396+
self.assertIsInstance(CharField().get_prep_value(''), str)
397+
self.assertIsInstance(CharField().get_prep_value(0), unicode)
398+
399+
def test_CommaSeparatedIntegerField(self):
400+
self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value('1,2'), str)
401+
self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value(0), unicode)
402+
403+
def test_DateField(self):
404+
self.assertIsInstance(DateField().get_prep_value(datetime.date.today()), datetime.date)
405+
406+
def test_DateTimeField(self):
407+
self.assertIsInstance(DateTimeField().get_prep_value(datetime.datetime.now()), datetime.datetime)
408+
409+
def test_DecimalField(self):
410+
self.assertIsInstance(DecimalField().get_prep_value(Decimal('1.2')), Decimal)
411+
412+
def test_EmailField(self):
413+
self.assertIsInstance(EmailField().get_prep_value('[email protected]'), str)
414+
415+
def test_FileField(self):
416+
self.assertIsInstance(FileField().get_prep_value('filename.ext'), unicode)
417+
self.assertIsInstance(FileField().get_prep_value(0), unicode)
418+
419+
def test_FilePathField(self):
420+
self.assertIsInstance(FilePathField().get_prep_value('tests.py'), unicode)
421+
self.assertIsInstance(FilePathField().get_prep_value(0), unicode)
422+
423+
def test_FloatField(self):
424+
self.assertIsInstance(FloatField().get_prep_value(1.2), float)
425+
426+
def test_ImageField(self):
427+
self.assertIsInstance(ImageField().get_prep_value('filename.ext'), unicode)
428+
429+
def test_IntegerField(self):
430+
self.assertIsInstance(IntegerField().get_prep_value(1), int)
431+
432+
def test_IPAddressField(self):
433+
self.assertIsInstance(IPAddressField().get_prep_value('127.0.0.1'), unicode)
434+
self.assertIsInstance(IPAddressField().get_prep_value(0), unicode)
435+
436+
def test_GenericIPAddressField(self):
437+
self.assertIsInstance(GenericIPAddressField().get_prep_value('127.0.0.1'), unicode)
438+
self.assertIsInstance(GenericIPAddressField().get_prep_value(0), unicode)
439+
440+
def test_NullBooleanField(self):
441+
self.assertIsInstance(NullBooleanField().get_prep_value(True), bool)
442+
443+
def test_PositiveIntegerField(self):
444+
self.assertIsInstance(PositiveIntegerField().get_prep_value(1), int)
445+
446+
def test_PositiveSmallIntegerField(self):
447+
self.assertIsInstance(PositiveSmallIntegerField().get_prep_value(1), int)
448+
449+
def test_SlugField(self):
450+
self.assertIsInstance(SlugField().get_prep_value('slug'), str)
451+
self.assertIsInstance(SlugField().get_prep_value(0), unicode)
452+
453+
def test_SmallIntegerField(self):
454+
self.assertIsInstance(SmallIntegerField().get_prep_value(1), int)
455+
456+
def test_TextField(self):
457+
self.assertIsInstance(TextField().get_prep_value('Abc'), str)
458+
self.assertIsInstance(TextField().get_prep_value(0), unicode)
459+
460+
def test_TimeField(self):
461+
self.assertIsInstance(
462+
TimeField().get_prep_value(datetime.datetime.now().time()),
463+
datetime.time)
464+
465+
def test_URLField(self):
466+
self.assertIsInstance(URLField().get_prep_value('http://domain.com'), str)
467+

0 commit comments

Comments
 (0)