File CVE-2026-1207.patch of Package python-Django.19338

From 891f01ea595df4bef7415454d2cf57b265bbd573 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Mon, 19 Jan 2026 15:42:33 -0500
Subject: [PATCH 3/7] [4.2.x] Fixed CVE-2026-1207 -- Prevented SQL injections
 in RasterField lookups via band index.

Thanks Tarek Nakkouch for the report, and Simon Charette for the initial
triage and review.

Backport of e8fb40508f4ca8c836caf985afc60d286cafec48 from main.
---
 .../gis/db/backends/postgis/operations.py     |  6 +++
 docs/releases/4.2.28.txt                      | 12 +++++
 tests/gis_tests/rasterapp/test_rasterfield.py | 47 ++++++++++++++++++-
 3 files changed, 64 insertions(+), 1 deletion(-)

Index: Django-2.2.28/django/contrib/gis/db/backends/postgis/operations.py
===================================================================
--- Django-2.2.28.orig/django/contrib/gis/db/backends/postgis/operations.py
+++ Django-2.2.28/django/contrib/gis/db/backends/postgis/operations.py
@@ -54,11 +54,17 @@ class PostGISOperator(SpatialOperator):
 
         # Look for band indices and inject them if provided.
         if lookup.band_lhs is not None and lhs_is_raster:
+            if not isinstance(lookup.band_lhs, int):
+                name = lookup.band_lhs.__class__.__name__
+                raise TypeError(f"Band index must be an integer, but got {name!r}.")
             if not self.func:
                 raise ValueError('Band indices are not allowed for this operator, it works on bbox only.')
             template_params['lhs'] = '%s, %s' % (template_params['lhs'], lookup.band_lhs)
 
         if lookup.band_rhs is not None and rhs_is_raster:
+            if not isinstance(lookup.band_rhs, int):
+                name = lookup.band_rhs.__class__.__name__
+                raise TypeError(f"Band index must be an integer, but got {name!r}.")
             if not self.func:
                 raise ValueError('Band indices are not allowed for this operator, it works on bbox only.')
             template_params['rhs'] = '%s, %s' % (template_params['rhs'], lookup.band_rhs)
Index: Django-2.2.28/tests/gis_tests/rasterapp/test_rasterfield.py
===================================================================
--- Django-2.2.28.orig/tests/gis_tests/rasterapp/test_rasterfield.py
+++ Django-2.2.28/tests/gis_tests/rasterapp/test_rasterfield.py
@@ -2,7 +2,11 @@ import json
 
 from django.contrib.gis.db.models.fields import BaseSpatialField
 from django.contrib.gis.db.models.functions import Distance
-from django.contrib.gis.db.models.lookups import DistanceLookupBase, GISLookup
+from django.contrib.gis.db.models.lookups import (
+    DistanceLookupBase,
+    GISLookup,
+    RasterBandTransform,
+)
 from django.contrib.gis.gdal import GDALRaster
 from django.contrib.gis.geos import GEOSGeometry
 from django.contrib.gis.measure import D
@@ -282,6 +286,47 @@ class RasterFieldTest(TransactionTestCas
         with self.assertRaisesMessage(ValueError, msg):
             qs.count()
 
+    def test_lookup_invalid_band_rhs(self):
+        rast = GDALRaster(json.loads(JSON_RASTER))
+        qs = RasterModel.objects.filter(rast__contains=(rast, "evil"))
+        msg = "Band index must be an integer, but got 'str'."
+        with self.assertRaisesMessage(TypeError, msg):
+            qs.count()
+
+    def test_lookup_invalid_band_lhs(self):
+        """
+        Typical left-hand side usage is protected against non-integers, but for
+        defense-in-depth purposes, construct custom lookups that evade the
+        `int()` and `+ 1` checks in the lookups shipped by django.contrib.gis.
+        """
+
+        # Evade the int() call in RasterField.get_transform().
+        class MyRasterBandTransform(RasterBandTransform):
+            band_index = "evil"
+
+            def process_band_indices(self, *args, **kwargs):
+                self.band_lhs = self.lhs.band_index
+                self.band_rhs, *self.rhs_params = self.rhs_params
+
+        # Evade the `+ 1` call in BaseSpatialField.process_band_indices().
+        ContainsLookup = RasterModel._meta.get_field("rast").get_lookup("contains")
+
+        class MyContainsLookup(ContainsLookup):
+            def process_band_indices(self, *args, **kwargs):
+                self.band_lhs = self.lhs.band_index
+                self.band_rhs, *self.rhs_params = self.rhs_params
+
+        RasterField = RasterModel._meta.get_field("rast")
+        RasterField.register_lookup(MyContainsLookup, "contains")
+        self.addCleanup(RasterField.register_lookup, ContainsLookup, "contains")
+
+        qs = RasterModel.objects.annotate(
+            transformed=MyRasterBandTransform("rast")
+        ).filter(transformed__contains=(F("transformed"), 1))
+        msg = "Band index must be an integer, but got 'str'."
+        with self.assertRaisesMessage(TypeError, msg):
+            list(qs)
+
     def test_isvalid_lookup_with_raster_error(self):
         qs = RasterModel.objects.filter(rast__isvalid=True)
         msg = 'IsValid function requires a GeometryField in position 1, got RasterField.'
openSUSE Build Service is sponsored by