v11-0002-Add-protections-in-xlog-record-APIs-against-over.patch
application/octet-stream
Filename: v11-0002-Add-protections-in-xlog-record-APIs-against-over.patch
Type: application/octet-stream
Part: 1
Patch
Format: format-patch
Series: patch v11-0002
Subject: Add protections in xlog record APIs against overflows
| File | + | − |
|---|---|---|
| src/backend/access/transam/xloginsert.c | 43 | 6 |
| src/include/access/xlogrecord.h | 11 | 0 |
From ab7f5bd145fe2d21667cb7982db71d3de287b416 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Wed, 5 Apr 2023 16:11:56 +0200
Subject: [PATCH v11 2/2] Add protections in xlog record APIs against overflows
Before this, it was possible for an extension to create malicious WAL
records that were too large to replay; or that would overflow the
xl_tot_len field, causing potential corruption in WAL record IO ops.
Emitting invalid records was also possible through
pg_logical_emit_message(), which allowed you to emit arbitrary amounts
of data up to 2GB, much higher than the replay limit of approximately
1GB.
This patch adds a limit to the size of an XLogRecord (1020MiB), checks
against overflows, and ensures that the reader infrastructure can read
any max-sized XLogRecords, such that the wal-generating backend will
fail when it attempts to insert unreadable records, instead of that
insertion succeeding but breaking any replication streams.
---
src/backend/access/transam/xloginsert.c | 49 ++++++++++++++++++++++---
src/include/access/xlogrecord.h | 11 ++++++
2 files changed, 54 insertions(+), 6 deletions(-)
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 134cf32bb4..6437234994 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -98,7 +98,7 @@ static int max_registered_block_id = 0; /* highest block_id + 1 currently
*/
static XLogRecData *mainrdata_head;
static XLogRecData *mainrdata_last = (XLogRecData *) &mainrdata_head;
-static uint32 mainrdata_len; /* total # of bytes in chain */
+static uint64 mainrdata_len; /* total # of bytes in chain */
/* flags for the in-progress insertion */
static uint8 curinsert_flags = 0;
@@ -368,7 +368,10 @@ XLogRegisterData(char *data, uint32 len)
Assert(begininsert_called);
if (num_rdatas >= max_rdatas)
- elog(ERROR, "too much WAL data");
+ ereport(ERROR,
+ (errmsg_internal("too much WAL data"),
+ errdetail_internal("%u out of %u data segments are already in use",
+ num_rdatas, max_rdatas)));
rdata = &rdatas[num_rdatas++];
rdata->data = data;
@@ -418,9 +421,16 @@ XLogRegisterBufData(uint8 block_id, char *data, uint32 len)
* regbuf->rdata_len does not grow beyond what
* XLogRecordBlockHeader->data_length can hold.
*/
- if (num_rdatas >= max_rdatas ||
- regbuf->rdata_len + len > UINT16_MAX)
- elog(ERROR, "too much WAL data");
+ if (num_rdatas >= max_rdatas)
+ ereport(ERROR,
+ (errmsg_internal("too much WAL data"),
+ errdetail_internal("%u out of %u data segments are already in use",
+ num_rdatas, max_rdatas)));
+ if (regbuf->rdata_len + len > UINT16_MAX || len > UINT16_MAX)
+ ereport(ERROR,
+ (errmsg_internal("too much WAL data"),
+ errdetail_internal("Registering more than max %u bytes total to block %u: current %uB, adding %uB",
+ UINT16_MAX, block_id, regbuf->rdata_len, len)));
rdata = &rdatas[num_rdatas++];
@@ -643,7 +653,7 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecPtr *fpw_lsn, int *num_fpi, bool *topxid_included)
{
XLogRecData *rdt;
- uint32 total_len = 0;
+ uint64 total_len = 0;
int block_id;
pg_crc32c rdata_crc;
registered_buffer *prev_regbuf = NULL;
@@ -994,6 +1004,20 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
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.
+ *
+ * XLogReader machinery is only able to handle records up to a certain
+ * size (ignoring machine resource limitations), so make sure we will
+ * not emit records larger than those sizes we advertise we support.
+ * This cap is based on DecodeXLogRecordRequiredSpace().
+ */
+ if (total_len >= XLogRecordMaxSize)
+ ereport(ERROR,
+ (errmsg_internal("oversized WAL record"),
+ errdetail_internal("WAL record would be %llu bytes (of max %u bytes); rmid %u flags %u",
+ (unsigned long long) total_len, XLogRecordMaxSize, rmid, info)));
+
/*
* 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
@@ -1419,6 +1443,19 @@ log_newpage_range(Relation rel, ForkNumber forknum,
void
InitXLogInsert(void)
{
+#ifdef USE_ASSERT_CHECKING
+
+ /*
+ * Check that any records assembled can be decoded. This is capped
+ * based on what XLogReader would require at its maximum bound.
+ * This code path is called once per backend, more than enough for
+ * this check.
+ */
+ size_t max_required = DecodeXLogRecordRequiredSpace(XLogRecordMaxSize);
+
+ Assert(AllocSizeIsValid(max_required));
+#endif
+
/* Initialize the working areas */
if (xloginsert_cxt == NULL)
{
diff --git a/src/include/access/xlogrecord.h b/src/include/access/xlogrecord.h
index 72bee48c5b..06cca575ea 100644
--- a/src/include/access/xlogrecord.h
+++ b/src/include/access/xlogrecord.h
@@ -65,6 +65,17 @@ typedef struct XLogRecord
*/
#define XLogRecordMaxSize (1020 * 1024 * 1024)
+/*
+ * XLogReader needs to allocate all the data of an xlog record in a single
+ * chunk. This means that a single XLogRecord cannot exceed MaxAllocSize
+ * in length if we ignore any allocation overhead of the XLogReader.
+ *
+ * To accommodate some overhead, this value allows for 4M of allocation
+ * overhead, that should be plenty enough for what
+ * DecodeXLogRecordRequiredSpace() expects as extra.
+ */
+#define XLogRecordMaxSize (1020 * 1024 * 1024)
+
/*
* The high 4 bits in xl_info may be used freely by rmgr. The
* XLR_SPECIAL_REL_UPDATE and XLR_CHECK_CONSISTENCY bits can be passed by
--
2.39.0