From e7509d1ec9f0d75e5539340981abf0d607d1fac4 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 30 Oct 2026 12:47:51 -0400
Subject: [PATCH v1] libpq: handle GSS credentials more sanely.

pg_GSS_have_cred_cache() always acquires new GSS credentials.
If we already had some, they were just leaked due to overwriting
conn->gcred, and that's a nontrivial amount of memory (around 16K
according to reporter).  Unfortunately, pg_GSS_continue() did this
each time through, and it is typically called more than once in a
GSSAPI connection cycle.  So repeated connections using GSS
authentication could leak substantial amounts of memory.

IMO the source of this problem is really the poorly-designed API
of pg_GSS_have_cred_cache(): it's unclear that it always gets new
credentials.  Let's rename it and remove the unnecessary pointer
parameter to make the behavior clearer.

To add insult to injury, pg_GSS_continue() shouldn't even be
trying to acquire credentials unless GSS delegation is enabled,
which it is not by default; so the whole effort is often wasted.

Problem evidently introduced by 6633cfb21, so back-patch to v16.

Bug: #19411
Reported-by: Matt Carter <Matt.Carter@twosigma.com>
Diagnosed-by: Matt Carter <Matt.Carter@twosigma.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/19411-0440c8897a04b638@postgresql.org
Backpatch-through: 16
---
 src/interfaces/libpq/fe-auth.c          | 10 ++++++----
 src/interfaces/libpq/fe-connect.c       | 16 +++++++++++-----
 src/interfaces/libpq/fe-gssapi-common.c | 15 ++++++---------
 src/interfaces/libpq/fe-gssapi-common.h |  2 +-
 src/interfaces/libpq/fe-secure-gssapi.c |  4 ++--
 5 files changed, 26 insertions(+), 21 deletions(-)

diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index e3bddf91203..4a55964875a 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -102,11 +102,13 @@ pg_GSS_continue(PGconn *conn, int payloadlen)
 		pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false);
 
 	/* Only try to acquire credentials if GSS delegation isn't disabled. */
-	if (!pg_GSS_have_cred_cache(&conn->gcred))
-		conn->gcred = GSS_C_NO_CREDENTIAL;
-
 	if (conn->gssdelegation && conn->gssdelegation[0] == '1')
-		gss_flags |= GSS_C_DELEG_FLAG;
+	{
+		if (conn->gcred == GSS_C_NO_CREDENTIAL)
+			conn->gcred = pg_GSS_get_cred_cache();
+		if (conn->gcred != GSS_C_NO_CREDENTIAL)
+			gss_flags |= GSS_C_DELEG_FLAG;
+	}
 
 	maj_stat = gss_init_sec_context(&min_stat,
 									conn->gcred,
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 811cb0f2a50..9e17a8f1c6f 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -3281,7 +3281,8 @@ keep_going:						/* We will come back to here until there is
 						}
 						if (conn->gcred == GSS_C_NO_CREDENTIAL)
 						{
-							if (!pg_GSS_have_cred_cache(&conn->gcred))
+							conn->gcred = pg_GSS_get_cred_cache();
+							if (conn->gcred == GSS_C_NO_CREDENTIAL)
 							{
 								libpq_append_conn_error(conn,
 														"GSSAPI encryption required but no credential cache");
@@ -4847,15 +4848,16 @@ select_next_encryption_method(PGconn *conn, bool have_valid_connection)
 	if ((remaining_methods & ENC_GSSAPI) != 0)
 	{
 		/*
-		 * If GSSAPI encryption is enabled, then call pg_GSS_have_cred_cache()
-		 * which will return true if we can acquire credentials (and give us a
-		 * handle to use in conn->gcred), and then send a packet to the server
+		 * If GSSAPI encryption is enabled, then call pg_GSS_get_cred_cache()
+		 * to try to acquire credentials, and then send a packet to the server
 		 * asking for GSSAPI Encryption (and skip past SSL negotiation and
 		 * regular startup below).
 		 */
 		if (!conn->gctx)
 		{
-			if (!pg_GSS_have_cred_cache(&conn->gcred))
+			if (conn->gcred == GSS_C_NO_CREDENTIAL)
+				conn->gcred = pg_GSS_get_cred_cache();
+			if (conn->gcred == GSS_C_NO_CREDENTIAL)
 			{
 				conn->allowed_enc_methods &= ~ENC_GSSAPI;
 				remaining_methods &= ~ENC_GSSAPI;
@@ -5020,6 +5022,10 @@ pqMakeEmptyPGconn(void)
 	conn->altsock = PGINVALID_SOCKET;
 	conn->Pfdebug = NULL;
 
+#ifdef ENABLE_GSS
+	conn->gcred = GSS_C_NO_CREDENTIAL;	/* let's not assume this is 0 */
+#endif
+
 	/*
 	 * We try to send at least 8K at a time, which is the usual size of pipe
 	 * buffers on Unix systems.  That way, when we are sending a large amount
diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c
index e7776fe76e7..c23819a219c 100644
--- a/src/interfaces/libpq/fe-gssapi-common.c
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -55,10 +55,11 @@ pg_GSS_error(const char *mprefix, PGconn *conn,
 }
 
 /*
- * Check if we can acquire credentials at all (and yield them if so).
+ * Attempt to acquire credentials, and return the token if successful.
+ * Return GSS_C_NO_CREDENTIAL if not.
  */
-bool
-pg_GSS_have_cred_cache(gss_cred_id_t *cred_out)
+gss_cred_id_t
+pg_GSS_get_cred_cache(void)
 {
 	OM_uint32	major,
 				minor;
@@ -67,12 +68,8 @@ pg_GSS_have_cred_cache(gss_cred_id_t *cred_out)
 	major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET,
 							 GSS_C_INITIATE, &cred, NULL, NULL);
 	if (major != GSS_S_COMPLETE)
-	{
-		*cred_out = NULL;
-		return false;
-	}
-	*cred_out = cred;
-	return true;
+		return GSS_C_NO_CREDENTIAL;
+	return cred;
 }
 
 /*
diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h
index f89d9e071e8..769e9f5ae91 100644
--- a/src/interfaces/libpq/fe-gssapi-common.h
+++ b/src/interfaces/libpq/fe-gssapi-common.h
@@ -20,7 +20,7 @@
 
 void		pg_GSS_error(const char *mprefix, PGconn *conn,
 						 OM_uint32 maj_stat, OM_uint32 min_stat);
-bool		pg_GSS_have_cred_cache(gss_cred_id_t *cred_out);
+gss_cred_id_t pg_GSS_get_cred_cache(void);
 int			pg_GSS_load_servicename(PGconn *conn);
 
 #endif
diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c
index 5ae10af5678..c43ab0294df 100644
--- a/src/interfaces/libpq/fe-secure-gssapi.c
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -640,9 +640,9 @@ pqsecure_open_gss(PGconn *conn)
 
 	if (conn->gssdelegation && conn->gssdelegation[0] == '1')
 	{
-		/* Acquire credentials if possible */
+		/* Attempt to acquire credentials, if we didn't already */
 		if (conn->gcred == GSS_C_NO_CREDENTIAL)
-			(void) pg_GSS_have_cred_cache(&conn->gcred);
+			conn->gcred = pg_GSS_get_cred_cache();
 
 		/*
 		 * We have credentials and gssdelegation is enabled, so request
-- 
2.52.0

