Skip to content

Commit 6b18b3f

Browse files
committed
Fail hard on out-of-memory failures in xlogreader.c
This commit changes the WAL reader routines so as a FATAL for the backend or exit(FAILURE) for the frontend is triggered if an allocation for a WAL record decode fails in walreader.c, rather than treating this case as bogus data, which would be equivalent to the end of WAL. The key is to avoid palloc_extended(MCXT_ALLOC_NO_OOM) in walreader.c, relying on plain palloc() calls. The previous behavior could make WAL replay finish too early than it should. For example, crash recovery finishing earlier may corrupt clusters because not all the WAL available locally was replayed to ensure a consistent state. Out-of-memory failures would show up randomly depending on the memory pressure on the host, but one simple case would be to generate a large record, then replay this record after downsizing a host, as Ethan Mertz originally reported. This relies on bae868c, as the WAL reader routines now do the memory allocation required for a record only once its header has been fully read and validated, making xl_tot_len trustable. Making the WAL reader react differently on out-of-memory or bogus record data would require ABI changes, so this is the safest choice for stable branches. Also, it is worth noting that 3f1ce97 has been using a plain palloc() in this code for some time now. Thanks to Noah Misch and Thomas Munro for the discussion. Like the other commit, backpatch down to 12, leaving out v11 that will be EOL'd soon. The behavior of considering a failed allocation as bogus data comes originally from 0ffe11a, where the record length retrieved from its header was not entirely trustable. Reported-by: Ethan Mertz Discussion: https://postgr.es/m/ZRKKdI5-RRlta3aF@paquier.xyz Backpatch-through: 12
1 parent 6c77bb4 commit 6b18b3f

File tree

1 file changed

+8
-39
lines changed

1 file changed

+8
-39
lines changed

src/backend/access/transam/xlogreader.c

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
static void report_invalid_record(XLogReaderState *state, const char *fmt,...)
4545
pg_attribute_printf(2, 3);
46-
static bool allocate_recordbuf(XLogReaderState *state, uint32 reclength);
46+
static void allocate_recordbuf(XLogReaderState *state, uint32 reclength);
4747
static int ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr,
4848
int reqLen);
4949
static void XLogReaderInvalReadState(XLogReaderState *state);
@@ -155,14 +155,7 @@ XLogReaderAllocate(int wal_segment_size, const char *waldir,
155155
* Allocate an initial readRecordBuf of minimal size, which can later be
156156
* enlarged if necessary.
157157
*/
158-
if (!allocate_recordbuf(state, 0))
159-
{
160-
pfree(state->errormsg_buf);
161-
pfree(state->readBuf);
162-
pfree(state);
163-
return NULL;
164-
}
165-
158+
allocate_recordbuf(state, 0);
166159
return state;
167160
}
168161

@@ -184,7 +177,6 @@ XLogReaderFree(XLogReaderState *state)
184177

185178
/*
186179
* Allocate readRecordBuf to fit a record of at least the given length.
187-
* Returns true if successful, false if out of memory.
188180
*
189181
* readRecordBufSize is set to the new buffer size.
190182
*
@@ -196,7 +188,7 @@ XLogReaderFree(XLogReaderState *state)
196188
* Note: This routine should *never* be called for xl_tot_len until the header
197189
* of the record has been fully validated.
198190
*/
199-
static bool
191+
static void
200192
allocate_recordbuf(XLogReaderState *state, uint32 reclength)
201193
{
202194
uint32 newSize = reclength;
@@ -206,15 +198,8 @@ allocate_recordbuf(XLogReaderState *state, uint32 reclength)
206198

207199
if (state->readRecordBuf)
208200
pfree(state->readRecordBuf);
209-
state->readRecordBuf =
210-
(char *) palloc_extended(newSize, MCXT_ALLOC_NO_OOM);
211-
if (state->readRecordBuf == NULL)
212-
{
213-
state->readRecordBufSize = 0;
214-
return false;
215-
}
201+
state->readRecordBuf = (char *) palloc(newSize);
216202
state->readRecordBufSize = newSize;
217-
return true;
218203
}
219204

220205
/*
@@ -505,9 +490,7 @@ XLogReadRecordAlloc(XLogReaderState *state, size_t xl_tot_len, bool allow_oversi
505490
/* Not enough space in the decode buffer. Are we allowed to allocate? */
506491
if (allow_oversized)
507492
{
508-
decoded = palloc_extended(required_space, MCXT_ALLOC_NO_OOM);
509-
if (decoded == NULL)
510-
return NULL;
493+
decoded = palloc(required_space);
511494
decoded->oversized = true;
512495
return decoded;
513496
}
@@ -815,13 +798,7 @@ XLogDecodeNextRecord(XLogReaderState *state, bool nonblocking)
815798
Assert(gotlen <= lengthof(save_copy));
816799
Assert(gotlen <= state->readRecordBufSize);
817800
memcpy(save_copy, state->readRecordBuf, gotlen);
818-
if (!allocate_recordbuf(state, total_len))
819-
{
820-
/* We treat this as a "bogus data" condition */
821-
report_invalid_record(state, "record length %u at %X/%X too long",
822-
total_len, LSN_FORMAT_ARGS(RecPtr));
823-
goto err;
824-
}
801+
allocate_recordbuf(state, total_len);
825802
memcpy(state->readRecordBuf, save_copy, gotlen);
826803
buffer = state->readRecordBuf + gotlen;
827804
}
@@ -877,16 +854,8 @@ XLogDecodeNextRecord(XLogReaderState *state, bool nonblocking)
877854
decoded = XLogReadRecordAlloc(state,
878855
total_len,
879856
true /* allow_oversized */ );
880-
if (decoded == NULL)
881-
{
882-
/*
883-
* We failed to allocate memory for an oversized record. As
884-
* above, we currently treat this as a "bogus data" condition.
885-
*/
886-
report_invalid_record(state,
887-
"out of memory while trying to decode a record of length %u", total_len);
888-
goto err;
889-
}
857+
/* allocation should always happen under allow_oversized */
858+
Assert(decoded != NULL);
890859
}
891860

892861
if (DecodeXLogRecord(state, decoded, record, RecPtr, &errormsg))

0 commit comments

Comments
 (0)