From e18b77153c740122a0eadde39ebfd5899156143e Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Sat, 23 May 2026 08:10:18 +0900 Subject: [PATCH] Avoid exposing WAL receiver raw conninfo during timeline jumps When reusing an existing WAL receiver after it has reached WALRCV_WAITING for new instructions, RequestXLogStreaming() copied PrimaryConnInfo into WalRcv->conninfo before switching the state to WALRCV_RESTARTING. At that point ready_to_display could still be true, so pg_stat_wal_receiver could expose the raw connection string, including sensitive fields, but it should only show the user-displayable version of the connection string. WALRCV_RESTARTING does not establish a new connection. The waiting WAL receiver reuses its existing connection and only needs a new startpoint and timeline, so there is no need to copy the raw connection string into shared memory again. Let's only copy conninfo when launching a new WAL receiver after WALRCV_STOPPED, not while waiting for instructions. This commit adds coverage for the case fixed by this commit to the timeline-switch test by verifying that the WAL receiver conninfo remains consistent across the jump. Backpatch all the way down, as this issue is possible since pg_stat_wal_receiver has been introduced. Author: Chao Li Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/EF91FF76-1E2B-4F3B-9162-290B4DC517FF@gmail.com Backpatch-through: 14 --- src/backend/replication/walreceiverfuncs.c | 14 +++++++++----- src/test/recovery/t/004_timeline_switch.pl | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c index 6f0acbfdef4..6ef2d7c0d43 100644 --- a/src/backend/replication/walreceiverfuncs.c +++ b/src/backend/replication/walreceiverfuncs.c @@ -265,11 +265,6 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo, Assert(walrcv->walRcvState == WALRCV_STOPPED || walrcv->walRcvState == WALRCV_WAITING); - if (conninfo != NULL) - strlcpy((char *) walrcv->conninfo, conninfo, MAXCONNINFO); - else - walrcv->conninfo[0] = '\0'; - /* * Use configured replication slot if present, and ignore the value of * create_temp_slot as the slot name should be persistent. Otherwise, use @@ -287,10 +282,19 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo, walrcv->is_temp_slot = create_temp_slot; } + /* + * While waiting for instructions, the WAL receiver uses the same + * connection, so do not clobber the user-visible conninfo already saved. + */ if (walrcv->walRcvState == WALRCV_STOPPED) { launch = true; walrcv->walRcvState = WALRCV_STARTING; + + if (conninfo != NULL) + strlcpy((char *) walrcv->conninfo, conninfo, MAXCONNINFO); + else + walrcv->conninfo[0] = '\0'; } else walrcv->walRcvState = WALRCV_RESTARTING; diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl index edfb2bef536..bbff6e97cd3 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -7,7 +7,7 @@ use warnings; use File::Path qw(rmtree); use PostgresNode; use TestLib; -use Test::More tests => 5; +use Test::More tests => 6; $ENV{PGDATABASE} = 'postgres'; @@ -50,11 +50,15 @@ $node_standby_1->psql( stdout => \$psql_out); is($psql_out, 't', "promotion of standby with pg_promote"); -# Switch standby 2 to replay from standby 1 +# Switch standby 2 to replay from standby 1. During the timeline switch, +# the WAL receiver process on standby 2 should not be stopped, and the +# new primary connection string should not be visible +# in pg_stat_wal_receiver. +my $secret = 'dont_show_me'; my $connstr_1 = $node_standby_1->connstr; $node_standby_2->append_conf( 'postgresql.conf', qq( -primary_conninfo='$connstr_1' +primary_conninfo='$connstr_1 password=$secret' )); # Rotate logfile before restarting, for the log checks done below. @@ -97,6 +101,13 @@ my $wr_pid_after_switch = $node_standby_2->safe_psql('postgres', is($wr_pid_before_switch, $wr_pid_after_switch, 'WAL receiver PID matches across timeline jumps'); +my $raw_conninfo_count = $node_standby_2->safe_psql('postgres', + "SELECT count(*) FROM pg_stat_wal_receiver WHERE conninfo LIKE '%$secret%'" +); + +is($raw_conninfo_count, '0', + 'pg_stat_wal_receiver.conninfo not updated across timeline jumps'); + # Ensure that a standby is able to follow a primary on a newer timeline # when WAL archiving is enabled.