Fix pfree-of-already-freed-tuple when rescanning a GiST index-only scan.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 May 2017 17:59:13 +0000 (13:59 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 May 2017 17:59:39 +0000 (13:59 -0400)
GiST's getNextNearest() function attempts to pfree the previously-returned
tuple if any (that is, scan->xs_hitup in HEAD, or scan->xs_itup in older
branches).  However, if we are rescanning a plan node after ending a
previous scan early, those tuple pointers could be pointing to garbage,
because they would be pointing into the scan's pageDataCxt or queueCxt
which has been reset.  In a debug build this reliably results in a crash,
although I think it might sometimes accidentally fail to fail in
production builds.

To fix, clear the pointer field anyplace we reset a context it might
be pointing into.  This may be overkill --- I think probably only the
queueCxt case is involved in this bug, so that resetting in gistrescan()
would be sufficient --- but dangling pointers are generally bad news,
so let's avoid them.

Another plausible answer might be to just not bother with the pfree in
getNextNearest().  The reconstructed tuples would go away anyway in the
context resets, and I'm far from convinced that freeing them a bit earlier
really saves anything meaningful.  I'll stick with the original logic in
this patch, but if we find more problems in the same area we should
consider that approach.

Per bug #14641 from Denis Smirnov.  Back-patch to 9.5 where this
logic was introduced.

Discussion: https://postgr.es/m/20170504072034.24366.57688@wrigleys.postgresql.org

src/backend/access/gist/gistget.c
src/backend/access/gist/gistscan.c
src/test/regress/expected/gist.out
src/test/regress/sql/gist.sql

index 122dc38db565ca643eaa71cc6ab622d9a751e6db..5a4dea89ac53c3bbfad1b16ec0b80c5b88fcc730 100644 (file)
@@ -375,6 +375,7 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
        }
 
        so->nPageData = so->curPageData = 0;
+       scan->xs_hitup = NULL;          /* might point into pageDataCxt */
        if (so->pageDataCxt)
                MemoryContextReset(so->pageDataCxt);
 
@@ -642,6 +643,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir)
 
                so->firstCall = false;
                so->curPageData = so->nPageData = 0;
+               scan->xs_hitup = NULL;
                if (so->pageDataCxt)
                        MemoryContextReset(so->pageDataCxt);
 
@@ -766,6 +768,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
        /* Begin the scan by processing the root page */
        so->curPageData = so->nPageData = 0;
+       scan->xs_hitup = NULL;
        if (so->pageDataCxt)
                MemoryContextReset(so->pageDataCxt);
 
index 81ff8fc8b6e1e3095c88ff44a3824413eccfa60d..058544e2ae24345f33545dfd82f76c2dd06083b7 100644 (file)
@@ -313,6 +313,9 @@ gistrescan(IndexScanDesc scan, ScanKey key, int nkeys,
                if (!first_time)
                        pfree(fn_extras);
        }
+
+       /* any previous xs_hitup will have been pfree'd in context resets above */
+       scan->xs_hitup = NULL;
 }
 
 void
index c7181b0397ec4688a416b40eb0d7233f8632c552..91f999814090423bbbd64b855cc462d3a752e33e 100644 (file)
@@ -114,6 +114,40 @@ order by point(0.101, 0.101) <-> p;
  (0.5,0.5)
 (11 rows)
 
+-- Check case with multiple rescans (bug #14641)
+explain (costs off)
+select p from
+  (values (box(point(0,0), point(0.5,0.5))),
+          (box(point(0.5,0.5), point(0.75,0.75))),
+          (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+  (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Nested Loop
+   ->  Values Scan on "*VALUES*"
+   ->  Limit
+         ->  Index Only Scan using gist_tbl_point_index on gist_tbl
+               Index Cond: (p <@ "*VALUES*".column1)
+               Order By: (p <-> ("*VALUES*".column1)[0])
+(6 rows)
+
+select p from
+  (values (box(point(0,0), point(0.5,0.5))),
+          (box(point(0.5,0.5), point(0.75,0.75))),
+          (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+  (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+      p      
+-------------
+ (0.5,0.5)
+ (0.45,0.45)
+ (0.75,0.75)
+ (0.7,0.7)
+ (1,1)
+ (0.95,0.95)
+(6 rows)
+
 drop index gist_tbl_point_index;
 -- Test index-only scan with box opclass
 create index gist_tbl_box_index on gist_tbl using gist (b);
index 9d4ff1e97e970c7b2a2558014c7765dbfaa47d94..49126fd466df64400a92fc59245b3df0ec848494 100644 (file)
@@ -69,6 +69,22 @@ order by point(0.101, 0.101) <-> p;
 select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5))
 order by point(0.101, 0.101) <-> p;
 
+-- Check case with multiple rescans (bug #14641)
+explain (costs off)
+select p from
+  (values (box(point(0,0), point(0.5,0.5))),
+          (box(point(0.5,0.5), point(0.75,0.75))),
+          (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+  (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+
+select p from
+  (values (box(point(0,0), point(0.5,0.5))),
+          (box(point(0.5,0.5), point(0.75,0.75))),
+          (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb)
+cross join lateral
+  (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss;
+
 drop index gist_tbl_point_index;
 
 -- Test index-only scan with box opclass