From 19281d8708facd17cd65a9ab07dd7b775496a5c9 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 12 May 2026 12:24:52 +0900 Subject: [PATCH] pg_stat_statements: Fix potential use-after-free of PlannedStmt Blahblahblah. Author: Chao Li Co-authored-by: Michael Paquier Discussion: https://postgr.es/m/ --- .../pg_stat_statements/expected/plancache.out | 38 +++++++++++++++++++ .../pg_stat_statements/pg_stat_statements.c | 3 +- contrib/pg_stat_statements/sql/plancache.sql | 19 ++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/contrib/pg_stat_statements/expected/plancache.out b/contrib/pg_stat_statements/expected/plancache.out index 32bf913b2861..d0796d5693c0 100644 --- a/contrib/pg_stat_statements/expected/plancache.out +++ b/contrib/pg_stat_statements/expected/plancache.out @@ -216,6 +216,44 @@ SELECT calls, generic_plan_calls, custom_plan_calls, toplevel, query FROM pg_sta RESET pg_stat_statements.track; -- +-- Procedure with internal ROLLBACK and the extended query protocol. +-- The PlannedStmt used in pgss_ProcessUtility() is freed by the internal +-- ROLLBACK. +-- +CREATE OR REPLACE PROCEDURE rollback_proc(a INOUT int) AS $$ +BEGIN + ROLLBACK; +END; +$$ LANGUAGE plpgsql; +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + t +--- + t +(1 row) + +CALL rollback_proc($1) \parse stmt_rollback +\bind_named stmt_rollback 1 \g + a +--- + 1 +(1 row) + +\bind_named stmt_rollback 2 \g + a +--- + 2 +(1 row) + +SELECT calls, query FROM pg_stat_statements + WHERE query LIKE '%rollback_proc%' + ORDER BY query COLLATE "C"; + calls | query +-------+------------------------ + 2 | CALL rollback_proc($1) +(1 row) + +DROP PROCEDURE rollback_proc; +-- -- Cleanup -- DROP FUNCTION select_one_func(int); diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 95a5411a39d9..a2d3ab770cc6 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -1099,6 +1099,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, int64 saved_queryId = pstmt->queryId; int saved_stmt_location = pstmt->stmt_location; int saved_stmt_len = pstmt->stmt_len; + PlannedStmtOrigin saved_planOrigin = pstmt->planOrigin; bool enabled = pgss_track_utility && pgss_enabled(nesting_level); /* @@ -1210,7 +1211,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, NULL, 0, 0, - pstmt->planOrigin); + saved_planOrigin); } else { diff --git a/contrib/pg_stat_statements/sql/plancache.sql b/contrib/pg_stat_statements/sql/plancache.sql index 160ced7add36..948d3e985180 100644 --- a/contrib/pg_stat_statements/sql/plancache.sql +++ b/contrib/pg_stat_statements/sql/plancache.sql @@ -87,6 +87,25 @@ SELECT calls, generic_plan_calls, custom_plan_calls, toplevel, query FROM pg_sta RESET pg_stat_statements.track; +-- +-- Procedure with internal ROLLBACK and the extended query protocol. +-- The PlannedStmt used in pgss_ProcessUtility() is freed by the internal +-- ROLLBACK. +-- +CREATE OR REPLACE PROCEDURE rollback_proc(a INOUT int) AS $$ +BEGIN + ROLLBACK; +END; +$$ LANGUAGE plpgsql; +SELECT pg_stat_statements_reset() IS NOT NULL AS t; +CALL rollback_proc($1) \parse stmt_rollback +\bind_named stmt_rollback 1 \g +\bind_named stmt_rollback 2 \g +SELECT calls, query FROM pg_stat_statements + WHERE query LIKE '%rollback_proc%' + ORDER BY query COLLATE "C"; +DROP PROCEDURE rollback_proc; + -- -- Cleanup -- -- 2.54.0