Re: PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows - Mailing list pgsql-bugs
| From | Andrey Rachitskiy |
|---|---|
| Subject | Re: PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows |
| Date | |
| Msg-id | CAB8bMiv=f8U4L=JtzLe+H4ygYDnHD43QOq9DCR+U5VzSdGafag@mail.gmail.com Whole thread |
| In response to | PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows (Vladimir Savin <vladimir@encord.com>) |
| Responses |
Re: PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows
|
| List | pgsql-bugs |
чт, 1 окт. 2026 г. в 19:28, Vladimir Savin <vladimir@encord.com>:
Hi,
A backend process is terminated by SIGSEGV in run-time partition pruning
when a statement contains a FOR UPDATE CTE that has to recheck a concurrently
updated row (EvalPlanQual) and a separate CTE that updates a partitioned table
through an Append with run-time pruning. It reproduces on REL_18_6 and on
master.
== Steps to reproduce
Attached epq-prune-repro.sql, run against a freshly initdb'd cluster:
psql -X -d postgres -f epq-prune-repro.sql
It needs contrib/dblink, which it uses to open the second session.
The enable_* settings only force the nested loop plan on these small tables;
we first hit this with a plan the planner picked on its own (Update -> Nested
Loop -> CTE Scan / Append with run-time pruning).
== Output I got (REL_18_6, built with --enable-cassert; master gives the same)
psql:
COMMIT
psql:epq-prune-repro.sql:44: ERROR: 08006: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
invalid socket
CONTEXT: while executing query on dblink connection named "s2"
LOCATION: dblink_res_error, dblink.c:2863
Server log (log_error_verbosity = verbose):
2026-10-01 14:07:00.975 UTC [13783] LOG: 00000: client backend (PID 13824) was terminated by signal 11: Segmentation fault
2026-10-01 14:07:00.975 UTC [13783] DETAIL: Failed process was running:
WITH inputs AS MATERIALIZED (
... (the session 2 query above)
Backtrace of the backend core (REL_18_6):
#0 ExecEvalExprSwitchContext (state=0x7f7f7f7f7f7f7f7f, econtext=0xaaaaf14182f0, isNull=0xffffe735e23f) at ../../../src/include/executor/executor.h:440
#1 partkey_datum_from_expr (context=0xaaaaf1418568, expr=0xaaaaf13fb440, stateidx=0, value=0xffffe735e240, isnull=0xffffe735e23f) at partprune.c:3823
#2 perform_pruning_base_step (context=0xaaaaf1418568, opstep=0xaaaaf13fb3f0) at partprune.c:3503
#3 get_matching_partitions (context=0xaaaaf1418568, pruning_steps=0xaaaaf13fb4c0) at partprune.c:881
#4 find_matching_subplans_recurse (prunedata=0xaaaaf14184d0, pprune=0xaaaaf14184d8, initial_prune=false, validsubplans=0xffffe735e488, validsubplan_rtis=0x0)
#5 ExecFindMatchingSubPlans (prunestate=0xaaaaf1418480, initial_prune=false, validsubplan_rtis=0x0) at execPartition.c:2535
#6 choose_next_subplan_locally (node=0xaaaaf143fe58) at nodeAppend.c:584
#7 ExecAppend (pstate=0xaaaaf143fe58) at nodeAppend.c:330
#8 ExecProcNode (node=0xaaaaf143fe58) at ../../../src/include/executor/executor.h:315
#9 ExecNestLoop (pstate=0xaaaaf142d410) at nodeNestloop.c:159
#10 ExecProcNode (node=0xaaaaf142d410) at ../../../src/include/executor/executor.h:315
#11 ExecModifyTable (pstate=0xaaaaf142c260) at nodeModifyTable.c:4280
#12 ExecProcNode (node=0xaaaaf142c260) at ../../../src/include/executor/executor.h:315
...
On master the frames are the same (partprune.c:3841, execPartition.c:2642,
nodeModifyTable.c:4497), also with state=0x7f7f7f7f7f7f7f7f.
== Output I expected
The session 2 statement completes and dblink_get_result returns updated = 8.
That is what the patched build below returns.
== Builds and settings
- REL_18_6, commit 724edf9bde9d356724ad384a2e196edc3c9f80f7:
SELECT version() = PostgreSQL 18.6 on aarch64-unknown-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
- master, commit 42e96cf2fe095c822f76bc94669ea875cb1e3351:
SELECT version() = PostgreSQL 20devel on aarch64-unknown-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
- configure: '--prefix=/pg' '--enable-cassert' '--enable-debug' '--without-icu' 'CFLAGS=-O0 -g'
plus `make -C contrib/dblink install`. No other deviations from the installation instructions.
- Cluster: `initdb -A trust`, started with pg_ctl. The only postgresql.conf change from the
initdb defaults is log_error_verbosity = verbose. No environment variables set.
Platform: Linux 7.0.12-linuxkit #1 SMP PREEMPT aarch64 (Docker Desktop on macOS,
Apple silicon), Debian GNU/Linux 13 (trixie), glibc 2.41-12+deb13u4,
gcc 14.2.0-19, 14 CPUs, 23 GiB RAM.
Release builds: with these exact steps the stock postgres:18.6 Docker image
(Debian 18.6-1.pgdg13+2, aarch64) returns 8 and does not crash (3 of 3 runs).Release builds do crash on the same query shape under concurrent load from theHatchet workflow engine, whose UpdateDurableEventLogEntriesSatisfied query has
this structure:
- postgres:18.6 (18.6-1.pgdg13+2) under a synthetic load test: a segfault
every few minutes. A core taken with the dbgsym package installed has the
same frames from ExecModifyTable (called from CteScanNext) up to
ExecEvalExprSwitchContext, with frame #0 at 0x000004a00000000c.
- Google Cloud SQL for PostgreSQL 18, production: "terminated by signal 11"
in that query roughly once a day.
- postgres:17.11 under the same synthetic load: no crashes in 9 runs.
Thanks,
Vladimir SavinThis email and any files transmitted with it are confidential and intended solely for the use of the individual or entity to whom they are addressed. If you have received this email in error, please notify the sender immediately by replying to this email and delete it from your system. Please do not copy, distribute, or take action based on the contents of this email if you are not the intended recipient. Any unauthorized use or disclosure of this email's contents is strictly prohibited.
Hi, Vladimir!
Thanks for the report.
I can reproduce it on the master branch.
EvalPlanQualStart() shares the parent's PartitionPruneState so that EPQ
initializes the same Append and MergeAppend subplans (8741e48e5dd).
ExecInit of those nodes called InitExecPartitionPruneContexts() again.
That replaced exec prune ExprStates with ones allocated in the EPQ
memory context. EvalPlanQualEnd() frees that context.
A later CTE that updates a partitioned table through Nested Loop plus
Append with run-time pruning then used the dangling ExprStates and
could SIGSEGV in partkey_datum_from_expr().
Skip InitExecPartitionPruneContexts() when es_epq_active is set. A
case is added to eval-plan-qual.
initializes the same Append and MergeAppend subplans (8741e48e5dd).
ExecInit of those nodes called InitExecPartitionPruneContexts() again.
That replaced exec prune ExprStates with ones allocated in the EPQ
memory context. EvalPlanQualEnd() frees that context.
A later CTE that updates a partitioned table through Nested Loop plus
Append with run-time pruning then used the dangling ExprStates and
could SIGSEGV in partkey_datum_from_expr().
Skip InitExecPartitionPruneContexts() when es_epq_active is set. A
case is added to eval-plan-qual.
Regards,
Rachitskiy Andrey
Rachitskiy Andrey
Attachment
pgsql-bugs by date: