It seems to me reasonable to move size check above CRC computation. However, it
seems suspicious to me to run a test that allocates 1Gb in `make check`. Maybe,
there are places that are not exercised too often. Perhaps recovery tests or
something like that.
Hi Andrey,
I share your concern about memory consumption and also agree recovery
tests look like the right place for this test.
So I split the patch into two - the change proper and the converted TAP
test, guarded by PG_TEST_EXTRA. Attaching both patches.
---
Sergey
From c3818555611ab4c9c39dee90e9e56e69c2fcc1ed Mon Sep 17 00:00:00 2001
From: Sergey Fukanchik <s.fukanc...@postgrespro.ru>
Date: Sun, 7 Sep 2025 12:06:03 +0300
Subject: [PATCH v2 1/2] Perform check for oversized WAL record before
calculating record CRC
---
src/backend/access/transam/xloginsert.c | 26 ++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index c7571429e8e..e8c55924c12 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -904,19 +904,6 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
hdr_rdt.len = (scratch - hdr_scratch);
total_len += hdr_rdt.len;
- /*
- * Calculate CRC of the data
- *
- * Note that the record header isn't added into the CRC initially since we
- * don't know the prev-link yet. Thus, the CRC will represent the CRC of
- * the whole record in the order: rdata, then backup blocks, then record
- * header.
- */
- INIT_CRC32C(rdata_crc);
- COMP_CRC32C(rdata_crc, hdr_scratch + SizeOfXLogRecord, hdr_rdt.len - SizeOfXLogRecord);
- for (rdt = hdr_rdt.next; rdt != NULL; rdt = rdt->next)
- COMP_CRC32C(rdata_crc, rdt->data, rdt->len);
-
/*
* Ensure that the XLogRecord is not too large.
*
@@ -930,6 +917,19 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
errdetail_internal("WAL record would be %" PRIu64 " bytes (of maximum %u bytes); rmid %u flags %u.",
total_len, XLogRecordMaxSize, rmid, info)));
+ /*
+ * Calculate CRC of the data
+ *
+ * Note that the record header isn't added into the CRC initially since we
+ * don't know the prev-link yet. Thus, the CRC will represent the CRC of
+ * the whole record in the order: rdata, then backup blocks, then record
+ * header.
+ */
+ INIT_CRC32C(rdata_crc);
+ COMP_CRC32C(rdata_crc, hdr_scratch + SizeOfXLogRecord, hdr_rdt.len - SizeOfXLogRecord);
+ for (rdt = hdr_rdt.next; rdt != NULL; rdt = rdt->next)
+ COMP_CRC32C(rdata_crc, rdt->data, rdt->len);
+
/*
* Fill in the fields in the record header. Prev-link is filled in later,
* once we know where in the WAL the record will be inserted. The CRC does
--
2.34.1
From 23172b798c82b039ec5be178a3740362ae65fc37 Mon Sep 17 00:00:00 2001
From: Sergey Fukanchik <s.fukanc...@postgrespro.ru>
Date: Sun, 7 Sep 2025 12:06:27 +0300
Subject: [PATCH v2 2/2] Add a TAP test for oversized WAL records
---
.../recovery/t/049_oversized_wal_record.pl | 50 +++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 src/test/recovery/t/049_oversized_wal_record.pl
diff --git a/src/test/recovery/t/049_oversized_wal_record.pl b/src/test/recovery/t/049_oversized_wal_record.pl
new file mode 100644
index 00000000000..8604ef53d48
--- /dev/null
+++ b/src/test/recovery/t/049_oversized_wal_record.pl
@@ -0,0 +1,50 @@
+
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Try to create a WAL record which is larger than the limit XLogRecordMaxSize.
+# That should raise an error.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+# Disable the test unless requested explicitly because it requires 1Gb of memory
+if (exists $ENV{PG_TEST_EXTRA}
+ && $ENV{PG_TEST_EXTRA} =~ m/\boversized_wal_record\b/)
+{
+ plan tests => 4;
+}
+else
+{
+ plan skip_all =>
+ 'Skipping oversized_wal_record test as it requires a lot of memory';
+}
+
+note "Check handing of oversized WAL records";
+
+# Initialize and start basic node
+my $node = PostgreSQL::Test::Cluster->new('node');
+
+$node->init;
+
+$node->start;
+
+# Insert oversized record
+my ($ret, $stdout, $stderr) = $node->psql(
+ 'postgres',
+ "select pg_logical_emit_message(false, 'a', repeat('00000000', (1020 * 1024 * 1024)/8+1), true)",
+ on_error_die => 0
+ );
+
+# Verify we have error
+ok($ret != 0, 'exit code is non-zero');
+
+ok($stderr =~ qr/ERROR: oversized WAL record/, 'expect error');
+
+ok($stderr =~ qr/DETAIL: WAL record would be [0-9]+ bytes \(of maximum [0-9]+ bytes\);/,
+ 'error details');
+
+$node->stop;
+
+done_testing();
--
2.34.1