From 6bb759dc22839f8ec101cad8551738be71b9ea9d Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Mon, 25 Sep 2023 17:19:23 +1300
Subject: [PATCH] Fix edge-case for xl_tot_len missed by bae868ca.

bae868ca removed a check that was still needed.  If you had a value
in xl_tot_len position that was too small for a record header, but not
big enough to span onto the next page, we'd immediately perform the CRC
check using a very large length, due to modulo arithmetic.  Because of
arbitrary coding differences between the CRC implementations on
different platforms, nothing bad would happen on common modern systems.
On systems using the _sb8.c fallback, we could segfault.

Fix, and add a new test for that case.  Without the code change, the
test would fail because recovery would report a CRC failure on typical
systems (instead of "too small"), but on some systems it would segfault.

diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 58654b746c..0bba56cc0e 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -653,6 +653,15 @@ restart:
 	}
 	else
 	{
+		/* There may be no next page if it's too small. */
+		if (total_len < SizeOfXLogRecord)
+		{
+			report_invalid_record(state,
+								  "invalid record length at %X/%X: expected at least %u, got %u",
+								  LSN_FORMAT_ARGS(RecPtr),
+								  (uint32) SizeOfXLogRecord, total_len);
+			goto err;
+		}
 		/* We'll validate the header once we have the next page. */
 		gotheader = false;
 	}
diff --git a/src/test/recovery/t/039_end_of_wal.pl b/src/test/recovery/t/039_end_of_wal.pl
index 61728bc38b..a523c16af7 100644
--- a/src/test/recovery/t/039_end_of_wal.pl
+++ b/src/test/recovery/t/039_end_of_wal.pl
@@ -281,6 +281,20 @@ ok( $node->log_contains(
 		$log_size),
 	"xl_tot_len short");
 
+# xl_tot_len in final position, not big enough to span into a new page but
+# also not eligible for regular record header validation
+emit_message($node, 0);
+$end_lsn = advance_to_record_splitting_zone($node);
+$node->stop('immediate');
+write_wal($node, $TLI, $end_lsn,
+	build_record_header(1, 0, 0xdeadbeef));
+$log_size = -s $node->logfile;
+$node->start;
+ok( $node->log_contains(
+		"invalid record length at .*: expected at least 24, got 1", $log_size
+	),
+	"xl_tot_len too small at end-of-page");
+
 # Need more pages, but xl_prev check fails first.
 emit_message($node, 0);
 $end_lsn = advance_out_of_record_splitting_zone($node);
-- 
2.39.2

