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 <li.evan.chao@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/EF91FF76-1E2B-4F3B-9162-290B4DC517FF@gmail.com
Backpatch-through: 14
This commit is contained in:
Michael Paquier 2026-05-23 08:10:18 +09:00
parent 5552a15a3e
commit e18b77153c
2 changed files with 23 additions and 8 deletions

View file

@ -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;

View file

@ -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.