mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	[1.7.x] Fixed queries that may return unexpected results on MySQL due to typecasting.
This is a security fix. Disclosure will follow shortly.
Backport of 75c0d4ea3a from master
			
			
This commit is contained in:
		| @@ -1509,6 +1509,12 @@ class FilePathField(Field): | |||||||
|             del kwargs["max_length"] |             del kwargs["max_length"] | ||||||
|         return name, path, args, kwargs |         return name, path, args, kwargs | ||||||
|  |  | ||||||
|  |     def get_prep_value(self, value): | ||||||
|  |         value = super(FilePathField, self).get_prep_value(value) | ||||||
|  |         if value is None: | ||||||
|  |             return None | ||||||
|  |         return six.text_type(value) | ||||||
|  |  | ||||||
|     def formfield(self, **kwargs): |     def formfield(self, **kwargs): | ||||||
|         defaults = { |         defaults = { | ||||||
|             'path': self.path, |             'path': self.path, | ||||||
| @@ -1642,6 +1648,12 @@ class IPAddressField(Field): | |||||||
|         del kwargs['max_length'] |         del kwargs['max_length'] | ||||||
|         return name, path, args, kwargs |         return name, path, args, kwargs | ||||||
|  |  | ||||||
|  |     def get_prep_value(self, value): | ||||||
|  |         value = super(IPAddressField, self).get_prep_value(value) | ||||||
|  |         if value is None: | ||||||
|  |             return None | ||||||
|  |         return six.text_type(value) | ||||||
|  |  | ||||||
|     def get_internal_type(self): |     def get_internal_type(self): | ||||||
|         return "IPAddressField" |         return "IPAddressField" | ||||||
|  |  | ||||||
| @@ -1711,12 +1723,14 @@ class GenericIPAddressField(Field): | |||||||
|  |  | ||||||
|     def get_prep_value(self, value): |     def get_prep_value(self, value): | ||||||
|         value = super(GenericIPAddressField, self).get_prep_value(value) |         value = super(GenericIPAddressField, self).get_prep_value(value) | ||||||
|  |         if value is None: | ||||||
|  |             return None | ||||||
|         if value and ':' in value: |         if value and ':' in value: | ||||||
|             try: |             try: | ||||||
|                 return clean_ipv6_address(value, self.unpack_ipv4) |                 return clean_ipv6_address(value, self.unpack_ipv4) | ||||||
|             except exceptions.ValidationError: |             except exceptions.ValidationError: | ||||||
|                 pass |                 pass | ||||||
|         return value |         return six.text_type(value) | ||||||
|  |  | ||||||
|     def formfield(self, **kwargs): |     def formfield(self, **kwargs): | ||||||
|         defaults = { |         defaults = { | ||||||
|   | |||||||
| @@ -593,6 +593,17 @@ For example:: | |||||||
|             return ''.join([''.join(l) for l in (value.north, |             return ''.join([''.join(l) for l in (value.north, | ||||||
|                     value.east, value.south, value.west)]) |                     value.east, value.south, value.west)]) | ||||||
|  |  | ||||||
|  | .. warning:: | ||||||
|  |  | ||||||
|  |     If your custom field uses the ``CHAR``, ``VARCHAR`` or ``TEXT`` | ||||||
|  |     types for MySQL, you must make sure that :meth:`.get_prep_value` | ||||||
|  |     always returns a string type. MySQL performs flexible and unexpected | ||||||
|  |     matching when a query is performed on these types and the provided | ||||||
|  |     value is an integer, which can cause queries to include unexpected | ||||||
|  |     objects in their results. This problem cannot occur if you always | ||||||
|  |     return a string type from :meth:`.get_prep_value`. | ||||||
|  |  | ||||||
|  |  | ||||||
| Converting query values to database values | Converting query values to database values | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -532,6 +532,22 @@ MySQL does not support the ``NOWAIT`` option to the ``SELECT ... FOR UPDATE`` | |||||||
| statement. If ``select_for_update()`` is used with ``nowait=True`` then a | statement. If ``select_for_update()`` is used with ``nowait=True`` then a | ||||||
| ``DatabaseError`` will be raised. | ``DatabaseError`` will be raised. | ||||||
|  |  | ||||||
|  | Automatic typecasting can cause unexpected results | ||||||
|  | -------------------------------------------------- | ||||||
|  |  | ||||||
|  | When performing a query on a string type, but with an integer value, MySQL will | ||||||
|  | coerce the types of all values in the table to an integer before performing the | ||||||
|  | comparison. If your table contains the values ``'abc'``, ``'def'`` and you | ||||||
|  | query for ``WHERE mycolumn=0``, both rows will match. Similarly, ``WHERE mycolumn=1`` | ||||||
|  | will match the value ``'abc1'``. Therefore, string type fields included in Django | ||||||
|  | will always cast the value to a string before using it in a query. | ||||||
|  |  | ||||||
|  | If you implement custom model fields that inherit from :class:`~django.db.models.Field` | ||||||
|  | directly, are overriding :meth:`~django.db.models.Field.get_prep_value`, or use | ||||||
|  | :meth:`extra() <django.db.models.query.QuerySet.extra>` or | ||||||
|  | :meth:`raw() <django.db.models.Manager.raw>`, you should ensure that you | ||||||
|  | perform the appropriate typecasting. | ||||||
|  |  | ||||||
| .. _sqlite-notes: | .. _sqlite-notes: | ||||||
|  |  | ||||||
| SQLite notes | SQLite notes | ||||||
|   | |||||||
| @@ -1202,6 +1202,16 @@ of the arguments is required, but you should use at least one of them. | |||||||
|  |  | ||||||
|       Entry.objects.extra(where=['headline=%s'], params=['Lennon']) |       Entry.objects.extra(where=['headline=%s'], params=['Lennon']) | ||||||
|  |  | ||||||
|  | .. warning:: | ||||||
|  |  | ||||||
|  |     If you are performing queries on MySQL, note that MySQL's silent type coercion | ||||||
|  |     may cause unexpected results when mixing types. If you query on a string | ||||||
|  |     type column, but with an integer value, MySQL will coerce the types of all values | ||||||
|  |     in the table to an integer before performing the comparison. For example, if your | ||||||
|  |     table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``, | ||||||
|  |     both rows will match. To prevent this, perform the correct typecasting | ||||||
|  |     before using the value in a query. | ||||||
|  |  | ||||||
| defer | defer | ||||||
| ~~~~~ | ~~~~~ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -66,6 +66,16 @@ options that make it very powerful. | |||||||
|     database, but does nothing to enforce that. If the query does not |     database, but does nothing to enforce that. If the query does not | ||||||
|     return rows, a (possibly cryptic) error will result. |     return rows, a (possibly cryptic) error will result. | ||||||
|  |  | ||||||
|  | .. warning:: | ||||||
|  |  | ||||||
|  |     If you are performing queries on MySQL, note that MySQL's silent type coercion | ||||||
|  |     may cause unexpected results when mixing types. If you query on a string | ||||||
|  |     type column, but with an integer value, MySQL will coerce the types of all values | ||||||
|  |     in the table to an integer before performing the comparison. For example, if your | ||||||
|  |     table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``, | ||||||
|  |     both rows will match. To prevent this, perform the correct typecasting | ||||||
|  |     before using the value in a query. | ||||||
|  |  | ||||||
| Mapping query fields to model fields | Mapping query fields to model fields | ||||||
| ------------------------------------ | ------------------------------------ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -673,12 +673,20 @@ class PromiseTest(test.TestCase): | |||||||
|         self.assertIsInstance( |         self.assertIsInstance( | ||||||
|             CharField().get_prep_value(lazy_func()), |             CharField().get_prep_value(lazy_func()), | ||||||
|             six.text_type) |             six.text_type) | ||||||
|  |         lazy_func = lazy(lambda: 0, int) | ||||||
|  |         self.assertIsInstance( | ||||||
|  |             CharField().get_prep_value(lazy_func()), | ||||||
|  |             six.text_type) | ||||||
|  |  | ||||||
|     def test_CommaSeparatedIntegerField(self): |     def test_CommaSeparatedIntegerField(self): | ||||||
|         lazy_func = lazy(lambda: '1,2', six.text_type) |         lazy_func = lazy(lambda: '1,2', six.text_type) | ||||||
|         self.assertIsInstance( |         self.assertIsInstance( | ||||||
|             CommaSeparatedIntegerField().get_prep_value(lazy_func()), |             CommaSeparatedIntegerField().get_prep_value(lazy_func()), | ||||||
|             six.text_type) |             six.text_type) | ||||||
|  |         lazy_func = lazy(lambda: 0, int) | ||||||
|  |         self.assertIsInstance( | ||||||
|  |             CommaSeparatedIntegerField().get_prep_value(lazy_func()), | ||||||
|  |             six.text_type) | ||||||
|  |  | ||||||
|     def test_DateField(self): |     def test_DateField(self): | ||||||
|         lazy_func = lazy(lambda: datetime.date.today(), datetime.date) |         lazy_func = lazy(lambda: datetime.date.today(), datetime.date) | ||||||
| @@ -709,12 +717,20 @@ class PromiseTest(test.TestCase): | |||||||
|         self.assertIsInstance( |         self.assertIsInstance( | ||||||
|             FileField().get_prep_value(lazy_func()), |             FileField().get_prep_value(lazy_func()), | ||||||
|             six.text_type) |             six.text_type) | ||||||
|  |         lazy_func = lazy(lambda: 0, int) | ||||||
|  |         self.assertIsInstance( | ||||||
|  |             FileField().get_prep_value(lazy_func()), | ||||||
|  |             six.text_type) | ||||||
|  |  | ||||||
|     def test_FilePathField(self): |     def test_FilePathField(self): | ||||||
|         lazy_func = lazy(lambda: 'tests.py', six.text_type) |         lazy_func = lazy(lambda: 'tests.py', six.text_type) | ||||||
|         self.assertIsInstance( |         self.assertIsInstance( | ||||||
|             FilePathField().get_prep_value(lazy_func()), |             FilePathField().get_prep_value(lazy_func()), | ||||||
|             six.text_type) |             six.text_type) | ||||||
|  |         lazy_func = lazy(lambda: 0, int) | ||||||
|  |         self.assertIsInstance( | ||||||
|  |             FilePathField().get_prep_value(lazy_func()), | ||||||
|  |             six.text_type) | ||||||
|  |  | ||||||
|     def test_FloatField(self): |     def test_FloatField(self): | ||||||
|         lazy_func = lazy(lambda: 1.2, float) |         lazy_func = lazy(lambda: 1.2, float) | ||||||
| @@ -735,9 +751,13 @@ class PromiseTest(test.TestCase): | |||||||
|             int) |             int) | ||||||
|  |  | ||||||
|     def test_IPAddressField(self): |     def test_IPAddressField(self): | ||||||
|         lazy_func = lazy(lambda: '127.0.0.1', six.text_type) |  | ||||||
|         with warnings.catch_warnings(record=True): |         with warnings.catch_warnings(record=True): | ||||||
|             warnings.simplefilter("always") |             warnings.simplefilter("always") | ||||||
|  |             lazy_func = lazy(lambda: '127.0.0.1', six.text_type) | ||||||
|  |             self.assertIsInstance( | ||||||
|  |                 IPAddressField().get_prep_value(lazy_func()), | ||||||
|  |                 six.text_type) | ||||||
|  |             lazy_func = lazy(lambda: 0, int) | ||||||
|             self.assertIsInstance( |             self.assertIsInstance( | ||||||
|                 IPAddressField().get_prep_value(lazy_func()), |                 IPAddressField().get_prep_value(lazy_func()), | ||||||
|                 six.text_type) |                 six.text_type) | ||||||
| @@ -747,6 +767,10 @@ class PromiseTest(test.TestCase): | |||||||
|         self.assertIsInstance( |         self.assertIsInstance( | ||||||
|             GenericIPAddressField().get_prep_value(lazy_func()), |             GenericIPAddressField().get_prep_value(lazy_func()), | ||||||
|             six.text_type) |             six.text_type) | ||||||
|  |         lazy_func = lazy(lambda: 0, int) | ||||||
|  |         self.assertIsInstance( | ||||||
|  |             GenericIPAddressField().get_prep_value(lazy_func()), | ||||||
|  |             six.text_type) | ||||||
|  |  | ||||||
|     def test_NullBooleanField(self): |     def test_NullBooleanField(self): | ||||||
|         lazy_func = lazy(lambda: True, bool) |         lazy_func = lazy(lambda: True, bool) | ||||||
| @@ -771,6 +795,10 @@ class PromiseTest(test.TestCase): | |||||||
|         self.assertIsInstance( |         self.assertIsInstance( | ||||||
|             SlugField().get_prep_value(lazy_func()), |             SlugField().get_prep_value(lazy_func()), | ||||||
|             six.text_type) |             six.text_type) | ||||||
|  |         lazy_func = lazy(lambda: 0, int) | ||||||
|  |         self.assertIsInstance( | ||||||
|  |             SlugField().get_prep_value(lazy_func()), | ||||||
|  |             six.text_type) | ||||||
|  |  | ||||||
|     def test_SmallIntegerField(self): |     def test_SmallIntegerField(self): | ||||||
|         lazy_func = lazy(lambda: 1, int) |         lazy_func = lazy(lambda: 1, int) | ||||||
| @@ -783,6 +811,10 @@ class PromiseTest(test.TestCase): | |||||||
|         self.assertIsInstance( |         self.assertIsInstance( | ||||||
|             TextField().get_prep_value(lazy_func()), |             TextField().get_prep_value(lazy_func()), | ||||||
|             six.text_type) |             six.text_type) | ||||||
|  |         lazy_func = lazy(lambda: 0, int) | ||||||
|  |         self.assertIsInstance( | ||||||
|  |             TextField().get_prep_value(lazy_func()), | ||||||
|  |             six.text_type) | ||||||
|  |  | ||||||
|     def test_TimeField(self): |     def test_TimeField(self): | ||||||
|         lazy_func = lazy(lambda: datetime.datetime.now().time(), datetime.time) |         lazy_func = lazy(lambda: datetime.datetime.now().time(), datetime.time) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user