[PATCH] Corruption Issue: Fix missing tts_tid in ExecForceStoreHeapTuple

From: Virender Singla <virender(dot)cse(at)gmail(dot)com>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: [PATCH] Corruption Issue: Fix missing tts_tid in ExecForceStoreHeapTuple
Date: 2026-09-01 06:18:43
Message-ID: CAM6Zo8wZOLnCWRO_tuuXVX9J4N4JN6GsEnk8WJtT0=_0zy-1dw@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi hackers,

We encountered an issue where queries using the index scan reorder queue
(e.g., KNN GiST scans with lossy distance recheck) lose the tuple's
physical ItemPointer (ctid) in the returned slot.

When combined with row locking (e.g., SELECT ... FOR UPDATE), the invalid
TID (4294967295, 0) is passed to heap_lock_tuple(). Because InvalidBlockNumber
equals P_NEW, ReadBuffer() extends the relation on disk before aborting with
"attempted to lock invisible tuple", leaving an orphaned uninitialized block
that fails subsequent sequential scans with "ERROR: invalid page in block N".

This affects all supported versions (PG 14 through master).

=== Cause ===

When tuples are popped from node->iss_ReorderQueue, nodeIndexscan.c calls:
ExecForceStoreHeapTuple(tuple, slot, true);

In src/backend/executor/execTuples.c (TTS_IS_BUFFERTUPLE branch),
ExecClearTuple() resets slot->tts_tid to InvalidItemPointer, but
slot->tts_tid is never updated with tuple->t_self.

=== Reproducer ===

CREATE TABLE t (id int PRIMARY KEY, v circle);
INSERT INTO t SELECT i, circle(point(i, i), 0.5) FROM generate_series(1, 100) i;
CREATE INDEX ON t USING gist (v circle_ops);

-- 1. Emits invalid ctid (4294967295, 0):
SELECT ctid, id FROM t ORDER BY v <-> point(50, 50) LIMIT 1;

-- 2. Fails with "attempted to lock invisible tuple" and extends table on disk:
BEGIN;
SELECT id FROM t ORDER BY v <-> point(50, 50) LIMIT 1 FOR UPDATE;
ROLLBACK;

=== Fix ===

Assign `slot->tts_tid = tuple->t_self;` in the TTS_IS_BUFFERTUPLE branch of
ExecForceStoreHeapTuple(), matching the behavior of
tts_buffer_heap_store_tuple().

Attached is a patch against master including regression test coverage
in gist.sql.

Thanks,
Virender

Attachment Content-Type Size
fix_exec_force_store_heap_tuple_tid.patch application/x-patch 3.8 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Ewan Young 2026-09-01 06:26:38 Re: Use pg_neg_s*_overflow() for open-coded negation overflow checks
Previous Message vignesh C 2026-09-01 06:06:04 Re: Logical replication row filter loses unchanged toasted columns