Re: PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows - Mailing list pgsql-bugs

From Zsolt Parragi
Subject Re: PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows
Date
Msg-id CAN4CZFPZ6qKnv6FPmq1RQZ49cHALCwLBu_NJdqUrkZNg0z81JQ@mail.gmail.com
Whole thread
In response to Re: PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows  (Andrew Dunstan <andrew@dunslane.net>)
List pgsql-bugs
Hello

An automated claude review pointed out a leftover issue after the fix.

> per buildfarm, this is an ABI break on release 18, apparently needs an
> update to .abi-compliance-history

The attached patch fixing that removes the initialized field again,
which should also take care of this.

EPQ EState has another problem, which the initialized flag doesn't
address: the ExprContext of the PartitionPruneState is created by
CreatePartitionPruneState() for the parent EState, so exec pruning in
the EPQ reads the parent's es_param_exec_vals. If the pruning depends
on a value from the re-fetched row, the EPQ prunes with the value from
the old row version, and can remove the partition it actually needs.

This isn't caused by 1b5dd3a24. Before it the EPQ re-initialized the
exec contexts, but with the same parent ExprContext, so this goes back
to 8741e48e5.

Repro:

CREATE TABLE p (a int, b int) PARTITION BY LIST (a);
CREATE TABLE p1 PARTITION OF p FOR VALUES IN (1);
CREATE TABLE p2 PARTITION OF p FOR VALUES IN (2);
INSERT INTO p VALUES (1, 100), (2, 200);
CREATE TABLE t (id int PRIMARY KEY, k int, v int, done bool DEFAULT false);
INSERT INTO t VALUES (1, 1, 100);

-- s1
BEGIN;
UPDATE t SET k = 2, v = 200 WHERE id = 1;
-- s2, blocks
UPDATE t SET done = true WHERE t.v = (SELECT p.b FROM p WHERE p.a =
t.k) RETURNING *;
-- s1
COMMIT;

s2 returns UPDATE 0. The recheck sees k = 2, the SubPlan sets $0 = 2
in the EPQ EState, but pruning still sees $0 = 1 from the parent, so
only p1 is kept, the scan finds nothing, and the subquery returns
NULL.
With enable_partition_pruning = off the row is updated as expected.

As David said upthread, before bb3ec16e1 the EPQ had its own
PartitionPruneStates, the sharing only came with 8741e48e5, which
fixed the missing states after that commit. The attached patch goes
back to separate states: it gives the EPQ EState its own
PartitionPruneStates, created from the parent's PartitionPruneInfos,
and keeps borrowing es_part_prune_results so the same subplans are
initialized, which was the reason for the sharing. As nothing is
shared anymore, I removed the initialized flag again. This also covers
the use-after-free, nothing in the parent state is touched by the EPQ
anymore, the existing test still passes, and so does Vladimir's
epq-prune-repro.sql.

The patch also adds the case above to the
eval-plan-qual-partition-prune isolation test.

Attachment

pgsql-bugs by date:

Previous
From: Tom Lane
Date:
Subject: Re: GIN disagrees with seq scan when NOT and AND operators are combined
Next
From: Chinmay Kanchi
Date:
Subject: GIN (JSONPath) checks equality only on the first node of each operand