Package: release.debian.org Severity: normal Tags: bookworm X-Debbugs-Cc: [email protected] Control: affects -1 + src:pgbouncer User: [email protected] Usertags: pu
[ Reason ] I've backported CVE fixes from upstream according to security tracker info. [ Impact ] security according to CVE description. [ Tests ] The package was built locally (which included running tests) as well as uploaded to debusine.debian.net which included both build tests and autopkgtests, etc: https://debusine.debian.net/debian/developers/work-request/276645/ Previous contact with maintainer has describes this as same level of testing he could do. (Note that new tests has been included or skipped where possible. Sometimes tests where hooked into test infra that did not yet exist in the older package version and thus new test had to be dropped.) [ Risks ] There's always a risk of regression ofcourse, but I don't see any major problems that we could not deal with if so. One of the fixed CVEs has already existed in bullseye (LTS) for a while now with no problem reports so far. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable [ Changes ] The debdiff contains additions to debian/patches where backports of upstream commits has been added. The last part of the patch header has comments about changes that where made vs upstreams commit for backporting, ending in `//ah`. [ Other info ] I have prepared bookworm and trixie updates in git branches currently at: https://salsa.debian.org/ah/pgbouncer/-/commits/bookworm https://salsa.debian.org/ah/pgbouncer/-/commits/trixie I'll tag and push to official debian packaging repo once I have the go ahead to upload. Regards, Andreas Henriksson on behalf of the LTS Security Team.
diff -Nru pgbouncer-1.18.0/debian/changelog pgbouncer-1.18.0/debian/changelog --- pgbouncer-1.18.0/debian/changelog 2023-02-03 16:14:12.000000000 +0100 +++ pgbouncer-1.18.0/debian/changelog 2025-12-20 12:57:12.000000000 +0100 @@ -1,3 +1,18 @@ +pgbouncer (1.18.0-1+deb12u1) bookworm; urgency=medium + + * Non-maintainer upload by the LTS Security Team. + * CVE-2025-2291: expired password can be used. + Password can be used past expiry in PgBouncer due to auth_query not + taking into account Postgres its VALID UNTIL value, which allows an + attacker to log in with an already expired password (Closes: #1103394) + * CVE-2025-12819: execute arbitrary SQL during authentication. + Untrusted search path in auth_query connection handler in PgBouncer + before 1.25.1 allows an unauthenticated attacker to execute arbitrary + SQL during authentication via a malicious search_path parameter in the + StartupMessage. + + -- Andreas Henriksson <[email protected]> Sat, 20 Dec 2025 12:57:12 +0100 + pgbouncer (1.18.0-1) unstable; urgency=medium * Team upload. diff -Nru pgbouncer-1.18.0/debian/patches/CVE-2025-12819.patch pgbouncer-1.18.0/debian/patches/CVE-2025-12819.patch --- pgbouncer-1.18.0/debian/patches/CVE-2025-12819.patch 1970-01-01 01:00:00.000000000 +0100 +++ pgbouncer-1.18.0/debian/patches/CVE-2025-12819.patch 2025-12-20 12:57:12.000000000 +0100 @@ -0,0 +1,76 @@ +From 85acffac5ddf56657706812f600c5f7f477abbab Mon Sep 17 00:00:00 2001 +From: Jelte Fennema-Nio <[email protected]> +Date: Wed, 5 Nov 2025 22:59:06 +0100 +Subject: [PATCH] Harden auth_query connection setup (fixes CVE-2025-12819) + +We were sending `SET` commands based on an unauthenticated +StartupMessage over the connection used to run an `auth_query` on the +Postgres server. In default configurations this doesn't have any clear +security implications, because the only settings that an attacker can +send are the `DateStyle`, `client_encoding`, `TimeZone`, +`standard_conforming_strings`, `application_name` and `IntervalStyle`. +For the default `auth_query` those shouldn't matter. + +For users that configured some special security sensitive GUC in +`track_extra_parameters` like `search_path` this does pose a security +problem though. +--- + src/objects.c | 12 ++++++++-- + test/test_auth.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 68 insertions(+), 2 deletions(-) + +drop test_auth.py as file does not yet exist in 1.18.0 //ah +include sending_auth_query impl from upstream commit 49623c6a6074 //ah + +diff --git a/src/objects.c b/src/objects.c +index 5309eeae53a2..f922f57d5b3f 100644 +--- a/src/objects.c ++++ b/src/objects.c +@@ -676,8 +676,16 @@ bool find_server(PgSocket *client) + } + Assert(!server || server->state == SV_IDLE); + +- /* send var changes */ +- if (server) { ++ /* ++ * Send var changes. However, Don't send SET commands over a connection ++ * that runs the auth_query query. Since the user is not authenticated, ++ * it might have security implications if a susceptible GUC is set in ++ * track_extra_parameters (e.g. search_path). In general, it also just ++ * isn't necessary, to do so for the auth_query. Connections for the ++ * auth_query should be isolated connections from the actual ++ * connections, e.g. they also use a completely different user. ++ */ ++ if (server && !sending_auth_query(client)) { + res = varcache_apply(server, client, &varchange); + if (!res) { + disconnect_server(server, true, "var change failed"); +Index: pgbouncer/include/client.h +=================================================================== +--- pgbouncer.orig/include/client.h ++++ pgbouncer/include/client.h +@@ -19,3 +19,4 @@ + bool client_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *pkt) _MUSTCHECK; + bool set_pool(PgSocket *client, const char *dbname, const char *username, const char *password, bool takeover) _MUSTCHECK; + bool handle_auth_query_response(PgSocket *client, PktHdr *pkt); ++bool sending_auth_query(PgSocket *client); +Index: pgbouncer/src/client.c +=================================================================== +--- pgbouncer.orig/src/client.c ++++ pgbouncer/src/client.c +@@ -115,6 +115,15 @@ static bool send_client_authreq(PgSocket + return res; + } + ++/* ++ * Returns true if the client is currently trying to send an auth query to the ++ * server. ++ */ ++bool sending_auth_query(PgSocket *client) ++{ ++ return client->wait_for_user_conn || client->wait_for_user; ++} ++ + static void start_auth_query(PgSocket *client, const char *username) + { + int res; diff -Nru pgbouncer-1.18.0/debian/patches/CVE-2025-2291.patch pgbouncer-1.18.0/debian/patches/CVE-2025-2291.patch --- pgbouncer-1.18.0/debian/patches/CVE-2025-2291.patch 1970-01-01 01:00:00.000000000 +0100 +++ pgbouncer-1.18.0/debian/patches/CVE-2025-2291.patch 2025-12-20 12:44:32.000000000 +0100 @@ -0,0 +1,126 @@ +From 9912ee7f1af2e1b81d4d624a0da1cb49075ee78a Mon Sep 17 00:00:00 2001 +From: Jelte Fennema-Nio <[email protected]> +Date: Wed, 5 Feb 2025 18:48:45 +0100 +Subject: [PATCH] Account for VALID UNTIL in auth_query (fixes CVE-2025-2291) + +Previously PgBouncer did not take into account the VALID UNTIL of a user +password when querying for password hashes using its auth_query. So if +PgBouncer is used as a transparent proxy in front of Postgres it could +allow passwords that had already expired. + +To solve this issue this changes the default auth_query and the examples +of custom auth_query functions in the documentation to take VALID UNTIL +into account. + +Since this can be considered a security issue in setups where VALID +UNTIL is used to limit exposure of leaked passwords, this is tracked as +CVE-2025-2291. +--- + doc/config.md | 16 +++++++++------- + etc/mkauth.py | 4 +++- + etc/pgbouncer.ini | 2 +- + src/main.c | 2 +- + test/test_auth.py | 12 +++++++++++- + 5 files changed, 25 insertions(+), 11 deletions(-) + +Dropped test_auth.py changes as that file does not yet exist in 1.18.0. //ah +unfuzz patch and update offsets //ah + +diff --git a/doc/config.md b/doc/config.md +index 9656aa491724..70c720fd4cc4 100644 +--- a/doc/config.md ++++ b/doc/config.md +@@ -320,12 +320,12 @@ Default: not set + ### auth_user + + If `auth_user` is set, then any user not specified in `auth_file` will be +-queried through the `auth_query` query from pg_shadow in the database, ++queried through the `auth_query` query from `pg_authid` in the database, + using `auth_user`. The password of `auth_user` will be taken from `auth_file`. + (If the `auth_user` does not require a password then it does not need + to be defined in `auth_file`.) + +-Direct access to pg_shadow requires admin rights. It's preferable to ++Direct access to `pg_authid` requires admin rights. It's preferable to + use a non-superuser that calls a SECURITY DEFINER function instead. + + Default: not set +@@ -334,13 +334,13 @@ Default: not set + + Query to load user's password from database. + +-Direct access to pg_shadow requires admin rights. It's preferable to ++Direct access to `pg_authid` requires admin rights. It's preferable to + use a non-superuser that calls a SECURITY DEFINER function instead. + + Note that the query is run inside the target database. So if a function + is used, it needs to be installed into each database. + +-Default: `SELECT usename, passwd FROM pg_shadow WHERE usename=$1` ++Default: `SELECT rolname, CASE WHEN rolvaliduntil < now() THEN NULL ELSE rolpassword END FROM pg_authid WHERE rolname=$1 AND rolcanlogin` + + + ## Log settings +@@ -1131,7 +1131,7 @@ credentials. + The authentication file can be written by hand, but it's also useful + to generate it from some other list of users and passwords. See + `./etc/mkauth.py` for a sample script to generate the authentication +-file from the `pg_shadow` system table. Alternatively, use ++file from the `pg_authid` system table. Alternatively, use + `auth_query` instead of `auth_file` to avoid having to maintain a + separate authentication file. + +@@ -1189,8 +1189,10 @@ Example of a secure function for `auth_q + CREATE OR REPLACE FUNCTION pgbouncer.user_lookup(in i_username text, out uname text, out phash text) + RETURNS record AS $$ + BEGIN +- SELECT usename, passwd FROM pg_catalog.pg_shadow +- WHERE usename = i_username INTO uname, phash; ++ SELECT rolname, CASE WHEN rolvaliduntil < now() THEN NULL ELSE rolpassword END ++ FROM pg_authid ++ WHERE rolname=i_username AND rolcanlogin ++ INTO uname, phash; + RETURN; + END; + $$ LANGUAGE plpgsql SECURITY DEFINER; +diff --git a/etc/mkauth.py b/etc/mkauth.py +index 85c26120c675..800b580d485e 100755 +--- a/etc/mkauth.py ++++ b/etc/mkauth.py +@@ -19,7 +19,9 @@ except IOError: + # create new file data + db = psycopg2.connect(sys.argv[2]) + curs = db.cursor() +-curs.execute("select usename, passwd from pg_shadow order by 1") ++curs.execute( ++ "SELECT rolname, CASE WHEN rolvaliduntil < pg_catalog.now() THEN NULL ELSE rolpassword END FROM pg_authid WHERE rolcanlogin order by 1" ++) + lines = [] + for user, psw in curs.fetchall(): + user = user.replace('"', '""') +diff --git a/etc/pgbouncer.ini b/etc/pgbouncer.ini +index db360b9d31ec..eee880eefa89 100644 +--- a/etc/pgbouncer.ini ++++ b/etc/pgbouncer.ini +@@ -121,7 +121,7 @@ auth_file = /etc/pgbouncer/userlist.txt + + ;; Query to use to fetch password from database. Result + ;; must have 2 columns - username and password hash. +-;auth_query = SELECT usename, passwd FROM pg_shadow WHERE usename=$1 ++;auth_query = SELECT rolname, CASE WHEN rolvaliduntil < pg_catalog.now() THEN NULL ELSE rolpassword END FROM pg_authid WHERE rolname=$1 AND rolcanlogin + + ;;; + ;;; Users allowed into database 'pgbouncer' +diff --git a/src/main.c b/src/main.c +index 402c73c5bc8d..88b94c84d226 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -230,7 +230,7 @@ static const struct CfKey bouncer_params [] = { + CF_ABS("application_name_add_host", CF_INT, cf_application_name_add_host, 0, "0"), + CF_ABS("auth_file", CF_STR, cf_auth_file, 0, NULL), + CF_ABS("auth_hba_file", CF_STR, cf_auth_hba_file, 0, ""), +-CF_ABS("auth_query", CF_STR, cf_auth_query, 0, "SELECT usename, passwd FROM pg_shadow WHERE usename=$1"), ++CF_ABS("auth_query", CF_STR, cf_auth_query, 0, "SELECT rolname, CASE WHEN rolvaliduntil < now() THEN NULL ELSE rolpassword END FROM pg_authid WHERE rolname=$1 AND rolcanlogin"), + CF_ABS("auth_type", CF_LOOKUP(auth_type_map), cf_auth_type, 0, "md5"), + CF_ABS("auth_user", CF_STR, cf_auth_user, 0, NULL), + CF_ABS("autodb_idle_timeout", CF_TIME_USEC, cf_autodb_idle_timeout, 0, "3600"), diff -Nru pgbouncer-1.18.0/debian/patches/series pgbouncer-1.18.0/debian/patches/series --- pgbouncer-1.18.0/debian/patches/series 2023-02-01 13:48:18.000000000 +0100 +++ pgbouncer-1.18.0/debian/patches/series 2025-12-20 12:47:32.000000000 +0100 @@ -1,2 +1,4 @@ debian-config #04ff38b25e4348b9191c3c7002f0562f0e1f53d2 +CVE-2025-2291.patch +CVE-2025-12819.patch

