Re: index prefetching

From: Bilal Kahraman <kahramannbilal(at)gmail(dot)com>
To: Rui Zhao <zhaorui126(at)gmail(dot)com>
Cc: Peter Geoghegan <pg(at)bowt(dot)ie>, Tomas Vondra <tomas(at)vondra(dot)me>, Andres Freund <andres(at)anarazel(dot)de>, Alexandre Felipe <o(dot)alexandre(dot)felipe(at)gmail(dot)com>, Thomas Munro <thomas(dot)munro(at)gmail(dot)com>, Nazir Bilal Yavuz <byavuz81(at)gmail(dot)com>, Robert Haas <robertmhaas(at)gmail(dot)com>, Melanie Plageman <melanieplageman(at)gmail(dot)com>, PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>, Georgios <gkokolatos(at)protonmail(dot)com>, Konstantin Knizhnik <knizhnik(at)garret(dot)ru>, Dilip Kumar <dilipbalaut(at)gmail(dot)com>
Subject: Re: index prefetching
Date: 2026-10-15 21:52:23
Message-ID: CAPkprsycOwGY4tfU_14aX-3LyLS+D4++4a4umGS0QYgjW+hzMg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Tue, Sep 29, 2026 at 3:51 AM Rui Zhao <zhaorui126(at)gmail(dot)com> wrote:
> The old amgettuple path already has the same type mismatch. It also
> reads past the 32-byte BOX, but in my run the BOX still pointed into a
> shared buffer mapping and the read did not fault. Since the query only
> projects the included id, the executor ignored the malformed polygon
> value and returned 7.

Hi,

On released branches this isn't always harmless. We ran into it while
fuzzing an 18-based build, as segfaults in heap_deform_tuple() during
index-only scans on an SP-GiST polygon index with an INCLUDE column.

If the box's first coordinate is a small integer, its first 4 bytes
are zero, so heap_form_tuple() doesn't over-read, it builds a polygon
of length 0 instead. heap_deform_tuple() then reads the INCLUDE
column's bytes as the polygon's length word. Depending on the value
you get a wrong result or a crash.

Repro:

CREATE TABLE t (p polygon, id int4);
INSERT INTO t SELECT polygon(box(point(i, i), point(i + 1, i + 1))),
1898491267 - 2 * i
FROM generate_series(1, 25) i;
CREATE INDEX ON t USING spgist (p) INCLUDE (id);
VACUUM ANALYZE t;
SET enable_seqscan = off;
SET enable_bitmapscan = off;
SELECT id FROM t LIMIT 5;

That should return 1898491265, 1898491263, ... but on 18 I get 2249,
2249, 2249, 2249, -1 with an assert build, and 131072, 131072,
-596975064, 131072, -1 with a release build. Odd ids give a short
length and the read lands in the next reconstructed tuple's header
(2249 is its datum_typeid, -1 its typmod). Even ids give a length of
hundreds of MB and it segfaults (try 1898491268 - i in the INSERT).

v37 fixes this in the new spggetbatch code and removes spggettuple, but
14 through 19 still have the old path in spgLeafTest() and
storeGettuple().

Attached is Rui's approach applied to the old code: only ask
leaf_consistent for the key when canReturnData is true, set the key to
NULL otherwise (like gistFetchTuple() does), and skip the datumCopy()
in spgNewHeapItem() for the same case. I included Rui's test, plus one
with integer coordinates, because Rui's test isn't reliable on the back
branches: unpatched, it returned 7 on 14, 17, 18 and 19 and crashed on
15 and 16 in my tests.

I built and tested it on every branch from 14 through 19 and on master,
with assertions enabled. Without the patch the new test aborts the
server and the repro returns garbage or segfaults; with it the repro
returns the right values and make installcheck-parallel passes.

Do you think this should go into the back branches separately from the
prefetching work?

Thanks,
Bilal

Attachment Content-Type Size
v1-0001-Fix-SP-GiST-index-only-scans-that-return-only-INC.patch application/octet-stream 7.5 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Zsolt Parragi 2026-10-15 22:00:16 Re: Unlogged materialized views
Previous Message Zsolt Parragi 2026-10-15 21:38:11 Re: Reject COPY TO of unlogged relations during recovery