Opened 47 minutes ago
#37360 new Bug
GEOSGeometry() raises unhandled KeyError on curve geometry WKT/WKB (CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON, MULTICURVE) with GEOS 3.13+
| Reported by: | Harsh Parmar | Owned by: | |
|---|---|---|---|
| Component: | GIS | Version: | dev |
| Severity: | Normal | Keywords: | geos curve-geometry circularstring compoundcurve keyerror |
| Cc: | Harsh Parmar | Triage Stage: | Unreviewed |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
GEOSGeometry.__init__ dispatches a successfully-parsed GEOS geometry
pointer to a Python wrapper class using a hardcoded lookup table
(GEOSGeometryBase._GEOS_CLASSES in django/contrib/gis/geos/geometry.py)
that only covers the seven classic OGC Simple Features types (0-7).
GEOS 3.13+ can parse curve geometries from WKT and WKB -- CIRCULARSTRING
(type id 8), COMPOUNDCURVE (9), CURVEPOLYGON (10), and MULTICURVE (11) --
successfully, returning a valid geometry pointer. Django's dispatch table
has no entry for ids 8-11, so the lookup raises an unhandled KeyError
instead of the GEOSException/ValueError that every calling site is
written to expect and catch.
This is confirmed unhandled at every real entry point I checked, because
none of their except clauses include KeyError:
GeometryField.to_python()(django/contrib/gis/forms/fields.py) -- catches(GEOSException, ValueError, TypeError)BaseGeometryWidget.deserialize()(django/contrib/gis/forms/widgets.py) -- catches(GEOSException, GDALException, ValueError, TypeError)BaseSpatialField.get_prep_value()(django/contrib/gis/db/models/fields.py) -- catches(TypeError, ValueError)and separately `(GEOSException, GDALException)`
So GeometryField.clean(user_input) -- the exact method any Form/ModelForm
calls during normal request validation -- crashes with a raw KeyError
instead of a ValidationError, for input that is completely valid,
standards-compliant WKT.
Reproduction
import django from django.conf import settings settings.configure() django.setup() from django.contrib.gis.geos import GEOSGeometry GEOSGeometry("CIRCULARSTRING(0 0, 1 1, 2 0)")
Traceback:
File "django/contrib/gis/geos/geometry.py", line 65, in __init__
cls = GEOSGeometryBase._GEOS_CLASSES[self.geom_typeid]
KeyError: 8
Same result (KeyError: 8) via the public form-validation API:
from django.contrib.gis.forms.fields import GeometryField GeometryField(required=False).clean("CIRCULARSTRING(0 0, 1 1, 2 0)")
Other curve types reproduce with their own type id: COMPOUNDCURVE -> 9,
CURVEPOLYGON -> 10, MULTICURVE -> 11. Ordinary (non-curve) WKT is
unaffected (POINT(1 1) etc. work as normal).
Independently reproduced via a clean pip install of Django straight from
GitHub main (commit 935edaa9) into a fresh virtualenv, confirming this is
not an artifact of a locally modified checkout:
$ pip install "django @ git+https://github.com/django/django.git@main"
Successfully installed django-6.2.dev20260919162742
$ python3 -c "
from django.conf import settings
import django; settings.configure(); django.setup()
from django.contrib.gis.geos import GEOSGeometry
GEOSGeometry('CIRCULARSTRING(0 0, 1 1, 2 0)')"
Traceback (most recent call last):
...
File ".../site-packages/django/contrib/gis/geos/geometry.py", line 65, in __init__
cls = GEOSGeometryBase._GEOS_CLASSES[self.geom_typeid]
KeyError: 8
Relation to #34406
#34406 added curve-geometry support, but only to the GDAL wrapper
(django/contrib/gis/gdal/geometries.py already has CircularString,
CompoundCurve, CurvePolygon, MultiCurve, MultiSurface classes, merged as
04adff9f). The GEOS wrapper (django/contrib/gis/geos/geometry.py) was
never updated to match, and there are no GEOS-side tests for any curve
type (tests/gis_tests/geos_tests/ has no CIRCULARSTRING references, versus
tests/gis_tests/gdal_tests/test_geom.py and tests/gis_tests/data/geometries.json
which do). This is why it wasn't caught: it's only reachable once GEOS's
*own* WKT/WKB reader gains curve support, which is a recent GEOS feature
(3.13+) that most test/CI environments haven't run against yet.
Suggested fix
Either add the five curve-geometry types to
GEOSGeometryBase._GEOS_CLASSES (mirroring the GDAL side), or, as a
minimal stopgap, catch KeyError in GEOSGeometryBase.__init__ and
re-raise as GEOSException with a clear "unsupported geometry type"
message so it fails the way callers already expect.
AI disclosure
This report was found and drafted with assistance from Claude Code
(Anthropic). Specifically: coverage-guided fuzzing (Google's atheris/
libFuzzer bindings) was used to fuzz GEOSGeometry() against random
WKT/WKB/GML input, which surfaced a crashing input; that crash was then
manually reduced to the minimal repro above and independently re-verified
from scratch (default Django settings, no fuzzing-harness artifacts) by
running the reproduction code shown here and inspecting the relevant
source (the except clauses listed above) directly, plus a fresh `pip
install` from GitHub main into a clean virtualenv. Confirmed this is not
a duplicate of #34406 or any other ticket via Trac's own search before
filing.