diff --git a/CHANGELOG.md b/CHANGELOG.md index 2455acda..b7198910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Add additional validation to help prevent OOMs during WKB parsing. + ## v0.56.0 2025-11-21 diff --git a/geom/wkb_parser.go b/geom/wkb_parser.go index 06e980df..ad757abc 100644 --- a/geom/wkb_parser.go +++ b/geom/wkb_parser.go @@ -225,11 +225,13 @@ func (p *wkbParser) parseLineString(ctype CoordinatesType) (LineString, error) { if err != nil { return LineString{}, err } - floats := make([]float64, int(n)*ctype.Dimension()) - if len(p.body) < 8*len(floats) { + // Ensure there are enough bytes for n points. + bytesNeeded := uint64(n) * uint64(ctype.Dimension()) * 8 + if bytesNeeded > uint64(len(p.body)) { return LineString{}, wkbSyntaxError{"unexpected EOF"} } + floats := make([]float64, int(n)*ctype.Dimension()) var seqData []byte if p.no { @@ -274,7 +276,13 @@ func (p *wkbParser) parsePolygon(ctype CoordinatesType) (Polygon, error) { if n == 0 { return Polygon{}.ForceCoordinatesType(ctype), nil } + + // Each ring needs at least 4 bytes for its point count. + if uint64(n)*4 > uint64(len(p.body)) { + return Polygon{}, wkbSyntaxError{"unexpected EOF"} + } rings := make([]LineString, n) + for i := range rings { rings[i], err = p.parseLineString(ctype) if err != nil { @@ -292,7 +300,13 @@ func (p *wkbParser) parseMultiPoint(ctype CoordinatesType) (MultiPoint, error) { if n == 0 { return MultiPoint{}.ForceCoordinatesType(ctype), nil } + + // Each point WKB needs at least 5 bytes (1 byte order + 4 type). + if uint64(n)*5 > uint64(len(p.body)) { + return MultiPoint{}, wkbSyntaxError{"unexpected EOF"} + } pts := make([]Point, n) + for i := uint32(0); i < n; i++ { geom, err := p.inner() if err != nil { @@ -314,7 +328,13 @@ func (p *wkbParser) parseMultiLineString(ctype CoordinatesType) (MultiLineString if n == 0 { return MultiLineString{}.ForceCoordinatesType(ctype), nil } + + // Each linestring WKB needs at least 9 bytes (1 byte order + 4 type + 4 point count). + if uint64(n)*9 > uint64(len(p.body)) { + return MultiLineString{}, wkbSyntaxError{"unexpected EOF"} + } lss := make([]LineString, n) + for i := uint32(0); i < n; i++ { geom, err := p.inner() if err != nil { @@ -336,7 +356,13 @@ func (p *wkbParser) parseMultiPolygon(ctype CoordinatesType) (MultiPolygon, erro if n == 0 { return MultiPolygon{}.ForceCoordinatesType(ctype), nil } + + // Each polygon WKB needs at least 9 bytes (1 byte order + 4 type + 4 ring count). + if uint64(n)*9 > uint64(len(p.body)) { + return MultiPolygon{}, wkbSyntaxError{"unexpected EOF"} + } polys := make([]Polygon, n) + for i := uint32(0); i < n; i++ { geom, err := p.inner() if err != nil { @@ -358,7 +384,13 @@ func (p *wkbParser) parseGeometryCollection(ctype CoordinatesType) (GeometryColl if n == 0 { return GeometryCollection{}.ForceCoordinatesType(ctype), nil } + + // Each geometry needs at least 5 bytes (1 byte order + 4 type). + if uint64(n)*5 > uint64(len(p.body)) { + return GeometryCollection{}, wkbSyntaxError{"unexpected EOF"} + } geoms := make([]Geometry, n) + for i := uint32(0); i < n; i++ { geoms[i], err = p.inner() if err != nil { diff --git a/geom/wkb_test.go b/geom/wkb_test.go index baa79d04..50b895ee 100644 --- a/geom/wkb_test.go +++ b/geom/wkb_test.go @@ -493,6 +493,48 @@ func TestWKBParserSyntaxError(t *testing.T) { "0000000000000000", // 0 "MultiPolygon contains non-Polygon element", }, + { + "linestring with inflated point count", + "00" + // Big endian + "00000002" + // LineString XY + "0000FFFF", // n=65535 points, but no coordinate data + "unexpected EOF", + }, + { + "polygon with inflated ring count", + "00" + // Big endian + "00000003" + // Polygon XY + "0000FFFF", // n=65535 rings, but no ring data + "unexpected EOF", + }, + { + "multipoint with inflated point count", + "00" + // Big endian + "00000004" + // MultiPoint XY + "0000FFFF", // n=65535 points, but no point data + "unexpected EOF", + }, + { + "multilinestring with inflated linestring count", + "00" + // Big endian + "00000005" + // MultiLineString XY + "0000FFFF", // n=65535 linestrings, but no data + "unexpected EOF", + }, + { + "multipolygon with inflated polygon count", + "00" + // Big endian + "00000006" + // MultiPolygon XY + "0000FFFF", // n=65535 polygons, but no data + "unexpected EOF", + }, + { + "geometrycollection with inflated geometry count", + "00" + // Big endian + "00000007" + // GeometryCollection XY + "0000FFFF", // n=65535 geometries, but no data + "unexpected EOF", + }, } { t.Run(tc.description, func(t *testing.T) { wkb := hexStringToBytes(t, tc.wkbHex)