#830 closed defect (fixed)
Possible memory leak in WKTReader with partial MULTIPOLYGON
Reported by: | goatbar | Owned by: | |
---|---|---|---|
Priority: | minor | Milestone: | |
Component: | Default | Version: | 3.5.0 |
Severity: | Unassigned | Keywords: | wkt fuzzing |
Cc: |
Description
Seeing this with llvm fuzzing with ASAN and geos 3.5.0.
Am I doing something wrong with the API or is this a bug with WKTReader not cleaning up in the case of an exception?
Given this input string:
MULTIPOLYGON(
And this code:
// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include <stddef.h> #include <stdint.h> #include <string> #include "third_party/geos/include/geos/geom/CoordinateSequence.h" #include "third_party/geos/include/geos/geom/Geometry.h" #include "third_party/geos/include/geos/io/WKTReader.h" typedef std::unique_ptr<geos::geom::Geometry> GeomPtr; typedef std::unique_ptr<geos::geom::CoordinateSequence> CoordSeqPtr; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // Ensure there is a NUL at the end of the data passed to the reader. const std::string s(reinterpret_cast<const char *>(data), size); try { geos::io::WKTReader reader; GeomPtr geom(reader.read(s)); CoordSeqPtr coords(geom->getCoordinates()); coords->getDimension(); } catch (...) { // NOP } return 0; }
Running: leak-1c9d82726612fdd23af7740675fa675af0cebe0f ================================================================= ==23962==ERROR: LeakSanitizer: detected memory leaks Direct leak of 24 byte(s) in 1 object(s) allocated from: #0 0x500942 in operator new(unsigned long) third_party/llvm/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:82:35 #1 0x66cffc in geos::io::WKTReader::readMultiPolygonText(geos::io::StringTokenizer*) third_party/geos/geos-3.5.0/src/io/WKTReader.cpp:422:31 #2 0x668ced in geos::io::WKTReader::readGeometryTaggedText(geos::io::StringTokenizer*) third_party/geos/geos-3.5.0/src/io/WKTReader.cpp:240:10 #3 0x668784 in geos::io::WKTReader::read(std::string const&) third_party/geos/geos-3.5.0/src/io/WKTReader.cpp:67:4 #4 0x5037a3 in LLVMFuzzerTestOneInput third_party/geos/tests/io/wktreader_fuzzer.cc:33:23 SUMMARY: AddressSanitizer: 24 byte(s) leaked in 1 allocation(s).
Change History (7)
comment:1 by , 7 years ago
comment:2 by , 7 years ago
Sadly, no, https://github.com/OSGeo/geos/pull/83 does not fix the issue.
source:trunk/src/io/WKTReader.cpp@4052#L422
`new vector' is leaked if there is a throw before return. I am not sure what shape the fix should be. If this were C++11, putting it in a unique_ptr would make sure it was deleted as the stack unwound. A closer class with a delete in the destructor would work too. Or simplest might be to try / catch the exception. I'm not very experienced with exceptions, so I haven't tried catch the exception yet.
MultiPolygon* WKTReader::readMultiPolygonText(StringTokenizer *tokenizer) { string nextToken=getNextEmptyOrOpener(tokenizer); if (nextToken=="EMPTY") { return geometryFactory->createMultiPolygon(NULL); } vector<Geometry *> *polygons=new vector<Geometry *>(); // <--- This is leaked. Polygon *polygon=readPolygonText(tokenizer); polygons->push_back(polygon); nextToken=getNextCloserOrComma(tokenizer); while(nextToken==",") { Polygon *polygon=readPolygonText(tokenizer); polygons->push_back(polygon); nextToken=getNextCloserOrComma(tokenizer); } MultiPolygon *ret = geometryFactory->createMultiPolygon(polygons); //for (int i=0; i<polygons->size(); i++) delete (*polygons)[i]; //delete polygons; return ret; }
I would guess that every other read method with a new will also leak.
comment:3 by , 7 years ago
Kurt, this is classic lack of RAII in GEOS. Switch to C++11 should help, replacing auto_ptr with unique_ptr and using unique_ptr in as many places as possible.
In this particular case, vector<Geometry *> *polygons=new vector<Geometry *>(
could be replaced with intermediate vector on stack, once fully created, allocate new on heap and move polygons
data into it before passing to createMultiPolygon
.
This however is partial fix: it will avoid leaking polygons = new vector<Geometry *>
but won't avoid leaking heap-allocated items of polygons
.
Long term solution could be switching away from heap-allocated vectors to use of rvalues with move semantic.
comment:4 by , 7 years ago
Working on a proposed simple patch of this form. I'll have it ready in the next day.
Polygon *polygon = NULL; try { polygon=readPolygonText(tokenizer); } catch (ParseException const& e) { delete polygons; throw ParseException(e.what()); }
This happens a number of locations, e.g. multilinestring too.
mloskot: Agreed, the whole move semantics world will make this a lot simple in the long run.
comment:5 by , 7 years ago
My proposed solution is in https://github.com/OSGeo/geos/pull/84
It's similar to other methods in the class. Catching ...
is especially important as not all exceptions are geos::io::ParseException
. e.g. I got a geos::util::IllegalArgumentException
for a malformed LinearRing
inside a GeometryCollection
.
Was this fixed by [f825b41004b37fcb0dd4ea76f22b5b6aa447b2ac/git] ?