From c97cdbe57b54d86707f9580a6dc52e25688fedd6 Mon Sep 17 00:00:00 2001
From: Diego <mrstephenamell@gmail.com>
Date: Mon, 14 Sep 2026 11:53:22 -0300
Subject: [PATCH v3 1/2] libpq: Do not leave password residue in
 passwordFromFile()'s result

passwordFromFile() copied the remainder of the matching password file
line with strdup() and then de-escaped the copy in place.  The copy
held everything up to the end of the line, and every escape sequence
shrinks the string by one byte, so the tail of the escaped password,
and any further fields on the line, remained in the allocation past
the terminating zero byte.  Callers that clear the password before
freeing it, such as pqReleaseConnHosts() with explicit_bzero(p,
strlen(p)), could not reach those bytes.

De-escape the password within the line buffer instead, which is
cleared with explicit_bzero() before it is freed, and copy only the
de-escaped password.  The returned string is unchanged, and the
function now writes nothing past its terminating zero byte.

The de-escaping dates from 8d15e3ec4fc; the explicit_bzero() clearing
added later by 74a308cf522 did not account for it.

Reported-by: Denis Smirnov <darthunix@gmail.com>
Discussion: https://postgr.es/m/D35D0ABB-755E-4EDE-B535-5C7578B3D3DC@gmail.com
---
 src/interfaces/libpq/fe-connect.c | 30 +++++++++++++++++++++---------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2ce128da157..a86564e192a 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8000,6 +8000,10 @@ pwdfMatchesString(char *buf, const char *token)
 /*
  * Get a password from the password file. Return value is malloc'd.
  *
+ * The returned string holds nothing but the de-escaped password and its
+ * terminating zero byte, so callers can clear it with
+ * explicit_bzero(ret, strlen(ret)); pqReleaseConnHosts() relies on this.
+ *
  * On failure, *errmsg is set to an error to be returned.  It is
  * left NULL on success, or if no password could be found.
  */
@@ -8116,6 +8120,23 @@ passwordFromFile(const char *hostname, const char *port,
 						   *p1,
 						   *p2;
 
+				/*
+				 * De-escape the password in place, within the line buffer,
+				 * before copying it out.  Copying first and de-escaping the
+				 * copy would leave the tail of the escaped password, and
+				 * anything following it on the line, in the result past the
+				 * terminating zero byte, where a caller that clears
+				 * strlen(ret) bytes (as pqReleaseConnHosts() does) cannot
+				 * reach it.  The line buffer itself is cleared below.
+				 */
+				for (p1 = p2 = t; *p1 != ':' && *p1 != '\0'; ++p1, ++p2)
+				{
+					if (*p1 == '\\' && p1[1] != '\0')
+						++p1;
+					*p2 = *p1;
+				}
+				*p2 = '\0';
+
 				ret = strdup(t);
 
 				fclose(fp);
@@ -8128,15 +8149,6 @@ passwordFromFile(const char *hostname, const char *port,
 					return NULL;
 				}
 
-				/* De-escape password. */
-				for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2)
-				{
-					if (*p1 == '\\' && p1[1] != '\0')
-						++p1;
-					*p2 = *p1;
-				}
-				*p2 = '\0';
-
 				return ret;
 			}
 		}

base-commit: bd1244343332419df6b34be38883ee0af3227030
-- 
2.43.0

