From ed08aad4ef065d91560238ffb14c7f18976b7e15 Mon Sep 17 00:00:00 2001 From: Jan Nidzwetzki Date: Fri, 16 Oct 2026 14:24:05 +0200 Subject: [PATCH v2 3/3] Use pg_parse_lsn() for timeline history switchpoints readTimeLineHistory() parsed the switchpoint on each history file line with sscanf("%X/%08X"). Commit f31d6fbc31d converted the frontend copy of this code in pg_rewind, but not the backend one. Convert it in the same way. As in pg_rewind, characters directly after the switchpoint are now rejected rather than ignored. Add a TAP test covering overlong components and trailing characters. --- src/backend/access/transam/timeline.c | 37 +++++++++++++++++----- src/test/recovery/t/004_timeline_switch.pl | 28 ++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c index d80c8ffe0a7..cc27587a07c 100644 --- a/src/backend/access/transam/timeline.c +++ b/src/backend/access/transam/timeline.c @@ -39,6 +39,7 @@ #include "access/xlog_internal.h" #include "access/xlogarchive.h" #include "access/xlogdefs.h" +#include "common/pg_parse_lsn.h" #include "pgstat.h" #include "storage/fd.h" #include "utils/wait_event.h" @@ -128,10 +129,13 @@ readTimeLineHistory(TimeLineID targetTLI) char fline[MAXPGPATH]; char *res; char *ptr; + char *token_end; + char save; TimeLineID tli; - uint32 switchpoint_hi; - uint32 switchpoint_lo; - int nfields; + XLogRecPtr switchpoint; + bool valid_switchpoint = false; + int nchars; + size_t nspaces; pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_READ); res = fgets(fline, sizeof(fline), fd); @@ -155,16 +159,33 @@ readTimeLineHistory(TimeLineID targetTLI) if (*ptr == '\0' || *ptr == '#') continue; - nfields = sscanf(fline, "%u\t%X/%08X", &tli, &switchpoint_hi, &switchpoint_lo); - - if (nfields < 1) + if (sscanf(fline, "%u%n", &tli, &nchars) != 1) { /* expect a numeric timeline ID as first field of line */ ereport(FATAL, (errmsg("syntax error in history file: %s", fline), errhint("Expected a numeric timeline ID."))); } - if (nfields != 3) + + /* the switchpoint location follows, separated by whitespace */ + ptr = fline + nchars; + nspaces = strspn(ptr, " \t\n\r\f\v"); + if (nspaces > 0) + { + ptr += nspaces; + + /* + * isolate the location from the rest of the line before parsing + * it + */ + token_end = ptr + strcspn(ptr, " \t\n\r\f\v"); + save = *token_end; + *token_end = '\0'; + valid_switchpoint = pg_parse_lsn(ptr, &switchpoint); + *token_end = save; + } + + if (!valid_switchpoint) ereport(FATAL, (errmsg("syntax error in history file: %s", fline), errhint("Expected a write-ahead log switchpoint location."))); @@ -179,7 +200,7 @@ readTimeLineHistory(TimeLineID targetTLI) entry = palloc_object(TimeLineHistoryEntry); entry->tli = tli; entry->begin = prevend; - entry->end = ((uint64) (switchpoint_hi)) << 32 | (uint64) switchpoint_lo; + entry->end = switchpoint; prevend = entry->end; /* Build list with newest item first */ diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl index 4085d7d04d1..acb479a3b5c 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -205,4 +205,32 @@ my $temp_slot_name = $node_standby_4->safe_psql('postgres', like($temp_slot_name, qr/^pg_walreceiver_[0-9]+$/, 'pg_stat_wal_receiver.slot_name remains set across timeline jumps'); +# Check that a history file with a bogus switchpoint is rejected. +my $node_bad_history = PostgreSQL::Test::Cluster->new('bad_history'); +$node_bad_history->init(allows_streaming => 1); +# Start and stop the node once before switching it to standby mode. This is +# required so that the control file records the test settings. +$node_bad_history->start; +$node_bad_history->stop; +$node_bad_history->set_standby_mode; +$node_bad_history->append_conf('postgresql.conf', + "recovery_target_timeline = '2'"); + +for my $switchpoint ('123456789/0', '0/123456789', '0/1x') +{ + my $history_file = + $node_bad_history->data_dir . '/pg_wal/00000002.history'; + my $log_offset = -s $node_bad_history->logfile; + + unlink $history_file; + append_to_file($history_file, "1\t$switchpoint\tbogus switchpoint\n"); + + ok(!$node_bad_history->start(fail_ok => 1), + "server fails to start with history switchpoint $switchpoint"); + ok( $node_bad_history->log_contains( + qr/FATAL: .*syntax error in history file: 1\t\Q$switchpoint\E/, + $log_offset), + "history switchpoint $switchpoint is rejected"); +} + done_testing(); -- 2.47.3