public inbox for [email protected]  
help / color / mirror / Atom feed
From: Emond Papegaaij <[email protected]>
To: [email protected]
Subject: Use-after-free crash
Date: Thu, 11 Jun 2026 09:59:38 +0200
Message-ID: <CAGXsc+aFfv2_PUrEdCt8TousYP9dPmty3i+0mkJpWwVg=fVN+Q@mail.gmail.com> (raw)

Hi,

Yesterday one of our tests detected a segmentation fault in pgpool,
caused by a use-after-free. It is triggered during the shutdown of the
process, so it probably isn't too harmful, but still I think it should
be fixed. I've worked with Claude Code to build a patch (against 4.7)
and a reproducer of the problem. The patch also contains a detailed
explanation of how this crashes in the commit message. To run the
reproducer:
tar xzf pgpool-uaf-repro.tar.gz
cd pgpool-uaf-repro
PGPOOL=/path/to/their/pgpool ./reproduce.sh

Best regards,
Emond


Attachments:

  [text/x-patch] 0001-Fix-use-after-free-of-query-context-after-a-backend-.patch (4.4K, ../CAGXsc+aFfv2_PUrEdCt8TousYP9dPmty3i+0mkJpWwVg=fVN+Q@mail.gmail.com/2-0001-Fix-use-after-free-of-query-context-after-a-backend-.patch)
  download | inline diff:
From 9b4f7d5f4f3a8ebadcabda6f1993790441a75b80 Mon Sep 17 00:00:00 2001
From: Emond Papegaaij <[email protected]>
Date: Thu, 11 Jun 2026 09:37:44 +0200
Subject: [PATCH] Fix use-after-free of query context after a backend node
 shutdown

A POOL_QUERY_CONTEXT is referenced from the session scoped sent message
list (session_context->message_list) and pending message list, which
live in session_context->memory_context and survive for the whole
client session.  However pool_init_query_context() allocated the query
context in the global QueryContext memory context.

do_child() resets QueryContext (MemoryContextResetAndDeleteChildren)
at the top of every iteration of its query processing loop.  In normal
operation pool_process_query() runs the whole session in a single
iteration, so QueryContext is effectively session lived and this is
harmless.  But when pool_process_query() returns POOL_CONTINUE in the
middle of a session, the loop iterates and QueryContext is reset while
the sent/pending message lists still reference query contexts that were
allocated in it.  The freed query contexts are later dereferenced (for
example from pool_clear_sent_message_list() in reset_connection(), or
from pool_remove_sent_message() while binding a reused statement name),
causing a use-after-free and a SIGSEGV.

This is reachable when a backend node is shut down by an administrative
command (e.g. "fast" shutdown of PostgreSQL) while a client session that
has named prepared statements is open.  read_packets_and_process()
detects the admin shutdown, sets was_error and returns POOL_CONTINUE
(cont = false), so pool_process_query() returns to do_child() mid
session and the next loop iteration frees the still referenced query
contexts.

Fix this by allocating the query context under the session memory
context rather than the per-iteration QueryContext, so its lifetime
matches the session scoped lists that reference it.  When there is no
session context (internal queries issued before a session exists) fall
back to QueryContext, preserving the previous behaviour.  Query contexts
are still freed explicitly by pool_query_context_destroy(), and at the
latest when the session memory context is destroyed, so this does not
change memory reclamation for the normal single-iteration case (in which
QueryContext was likewise only freed at session end).
---
 src/context/pool_query_context.c | 34 +++++++++++++++++++++++++-------
 1 file changed, 27 insertions(+), 7 deletions(-)

diff --git a/src/context/pool_query_context.c b/src/context/pool_query_context.c
index a056ac596..7eef39367 100644
--- a/src/context/pool_query_context.c
+++ b/src/context/pool_query_context.c
@@ -78,15 +78,35 @@ static char *get_associated_object_from_dml_adaptive_relations
 POOL_QUERY_CONTEXT *
 pool_init_query_context(void)
 {
-	MemoryContext memory_context = AllocSetContextCreate(QueryContext,
-														 "QueryContextMemoryContext",
-														 ALLOCSET_SMALL_MINSIZE,
-														 ALLOCSET_SMALL_INITSIZE,
-														 ALLOCSET_SMALL_MAXSIZE);
-
-	MemoryContext oldcontext = MemoryContextSwitchTo(memory_context);
+	POOL_SESSION_CONTEXT *session_context;
+	MemoryContext parent;
+	MemoryContext memory_context;
+	MemoryContext oldcontext;
 	POOL_QUERY_CONTEXT *qc;
 
+	/*
+	 * Parent the query context under the session memory context rather than
+	 * the per-iteration QueryContext.  A query context is referenced from the
+	 * session scoped sent message list and pending message list.  These lists
+	 * outlive the QueryContext, which do_child() resets between the iterations
+	 * of its query processing loop (for instance when pool_process_query()
+	 * returns after a backend node was shut down).  If the query context lived
+	 * in QueryContext, such a reset would free it while the message lists
+	 * still reference it, resulting in a use-after-free.  When there is no
+	 * session context (e.g. internal queries issued before a session exists),
+	 * fall back to QueryContext.
+	 */
+	session_context = pool_get_session_context(true);
+	parent = session_context ? session_context->memory_context : QueryContext;
+
+	memory_context = AllocSetContextCreate(parent,
+										   "QueryContextMemoryContext",
+										   ALLOCSET_SMALL_MINSIZE,
+										   ALLOCSET_SMALL_INITSIZE,
+										   ALLOCSET_SMALL_MAXSIZE);
+
+	oldcontext = MemoryContextSwitchTo(memory_context);
+
 	qc = palloc0(sizeof(*qc));
 	qc->memory_context = memory_context;
 	MemoryContextSwitchTo(oldcontext);
-- 
2.53.0



  [application/gzip] pgpool-uaf-repro.tar.gz (9.6K, ../CAGXsc+aFfv2_PUrEdCt8TousYP9dPmty3i+0mkJpWwVg=fVN+Q@mail.gmail.com/3-pgpool-uaf-repro.tar.gz)
  download

view thread (5+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected]
  Subject: Re: Use-after-free crash
  In-Reply-To: <CAGXsc+aFfv2_PUrEdCt8TousYP9dPmty3i+0mkJpWwVg=fVN+Q@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox