From 93d81224a3c19d7b6874e99fa59ff7346f95e58c Mon Sep 17 00:00:00 2001
From: Zhong ShiHao <zhong950419@gmail.com>
Date: Mon, 7 Sep 2026 20:22:13 -0400
Subject: [PATCH] Make pg_stat_get_backend_subxact() respect statistics
 permissions

Every other per-backend statistics function that reports the details of a
session first calls HAS_PGSTAT_PERMISSIONS(), so it returns NULL to a
caller that is neither a superuser, nor a member of pg_read_all_stats,
nor a member of the role that owns the session.
pg_stat_get_backend_subxact() had no such check and reported the
subtransaction count and overflow flag to any caller.  Add the check the
sibling functions use.

The rule is stated for the dynamic statistics views but not for these
functions, so document it above the per-backend function table, and
correct the name of the subxact_overflowed output column while at it.
---
 doc/src/sgml/monitoring.sgml        | 12 +++++++++++-
 src/backend/utils/adt/pgstatfuncs.c |  7 ++++++-
 src/test/regress/expected/stats.out | 27 +++++++++++++++++++++++++++
 src/test/regress/sql/stats.sql      | 18 ++++++++++++++++++
 4 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index b403fb990a7..1958c7c7636 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -6193,6 +6193,16 @@ FROM pg_stat_get_backend_idset() AS backendid;
 </programlisting>
   </para>
 
+  <para>
+   These functions are security restricted in the same way as
+   <structname>pg_stat_activity</structname>.  The existence of a session and
+   its general properties, such as its session user and database, are visible
+   to all users, but the functions that report the details of a session's
+   activity return NULL unless the caller is a superuser, has privileges of the
+   <link linkend="predefined-role-pg-monitor"><literal>pg_read_all_stats</literal></link>
+   role, or is a member of the role that owns the session.
+  </para>
+
    <table id="monitoring-stats-backend-funcs-table">
     <title>Per-Backend Statistics Functions</title>
     <tgroup cols="1">
@@ -6325,7 +6335,7 @@ FROM pg_stat_get_backend_idset() AS backendid;
         backend with the specified ID.
         The fields returned are <parameter>subxact_count</parameter>, which
         is the number of subtransactions in the backend's subtransaction cache,
-        and <parameter>subxact_overflow</parameter>, which indicates whether
+        and <parameter>subxact_overflowed</parameter>, which indicates whether
         the backend's subtransaction cache is overflowed or not.
        </para></entry>
       </row>
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0d47d745c18..f91f9b39614 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -838,7 +838,12 @@ pg_stat_get_backend_subxact(PG_FUNCTION_ARGS)
 	TupleDescFinalize(tupdesc);
 	BlessTupleDesc(tupdesc);
 
-	if ((local_beentry = pgstat_get_local_beentry_by_proc_number(procNumber)) != NULL)
+	/*
+	 * Like the other per-backend statistics functions, report the details of
+	 * a session only to a caller that is allowed to see them.
+	 */
+	if ((local_beentry = pgstat_get_local_beentry_by_proc_number(procNumber)) != NULL &&
+		HAS_PGSTAT_PERMISSIONS(local_beentry->backendStatus.st_userid))
 	{
 		/* Fill values and NULLs */
 		values[0] = Int32GetDatum(local_beentry->backend_subxact_count);
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 8b15471248b..54af4cb3032 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1141,6 +1141,33 @@ WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid();
  t
 (1 row)
 
+-- pg_stat_get_backend_subxact() reports the details of a session, so like the
+-- other per-backend functions it is only meant to answer callers that are
+-- allowed to see them.
+SELECT beid FROM pg_stat_get_backend_idset() beid
+WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid() \gset
+-- the role that owns this backend sees the values
+SELECT subxact_count IS NOT NULL AS count_visible,
+       subxact_overflowed IS NOT NULL AS overflow_visible
+FROM pg_stat_get_backend_subxact(:beid);
+ count_visible | overflow_visible 
+---------------+------------------
+ t             | t
+(1 row)
+
+CREATE ROLE regress_stat_subxact_role;
+SET ROLE regress_stat_subxact_role;
+-- an unrelated role gets NULLs instead
+SELECT subxact_count IS NULL AS count_hidden,
+       subxact_overflowed IS NULL AS overflow_hidden
+FROM pg_stat_get_backend_subxact(:beid);
+ count_hidden | overflow_hidden 
+--------------+-----------------
+ t            | t
+(1 row)
+
+RESET ROLE;
+DROP ROLE regress_stat_subxact_role;
 -----
 -- Test that resetting stats works for reset timestamp
 -----
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 674637e172b..0ccf3c9b839 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -535,6 +535,24 @@ SELECT (current_schemas(true))[1] = ('pg_temp_' || beid::text) AS match
 FROM pg_stat_get_backend_idset() beid
 WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid();
 
+-- pg_stat_get_backend_subxact() reports the details of a session, so like the
+-- other per-backend functions it is only meant to answer callers that are
+-- allowed to see them.
+SELECT beid FROM pg_stat_get_backend_idset() beid
+WHERE pg_stat_get_backend_pid(beid) = pg_backend_pid() \gset
+-- the role that owns this backend sees the values
+SELECT subxact_count IS NOT NULL AS count_visible,
+       subxact_overflowed IS NOT NULL AS overflow_visible
+FROM pg_stat_get_backend_subxact(:beid);
+CREATE ROLE regress_stat_subxact_role;
+SET ROLE regress_stat_subxact_role;
+-- an unrelated role gets NULLs instead
+SELECT subxact_count IS NULL AS count_hidden,
+       subxact_overflowed IS NULL AS overflow_hidden
+FROM pg_stat_get_backend_subxact(:beid);
+RESET ROLE;
+DROP ROLE regress_stat_subxact_role;
+
 -----
 -- Test that resetting stats works for reset timestamp
 -----
-- 
2.37.1 (Apple Git-137.1)

