Skip to content

Commit 6d2d5ab

Browse files
committed
Fix inadequate buffer locking in FSM and VM page re-initialization.
When reading an existing FSM or VM page that was found to be corrupt by the buffer manager, the code applied PageInit() to reinitialize the page, but did so without any locking. There is thus a hazard that two backends might concurrently do PageInit, which in itself would still be OK, but the slower one might then zero over subsequent data changes applied by the faster one. Even that is unlikely to be fatal; but it's not desirable, so add locking to prevent it. This does not add any locking overhead in the normal code path where the page is OK. It's not immediately obvious that that's safe, but I believe it is, for reasons explained in the added comments. Problem noted by R P Asim. It's been like this for a long time, so back-patch to all supported branches. Discussion: https://postgr.es/m/CANXE4Te4G0TGq6cr0-TvwP0H4BNiK_-hB5gHe8mF+nz0mcYfMQ@mail.gmail.com
1 parent a6bbf1c commit 6d2d5ab

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

src/backend/access/heap/visibilitymap.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,11 +575,30 @@ vm_readbuf(Relation rel, BlockNumber blkno, bool extend)
575575
* Use ZERO_ON_ERROR mode, and initialize the page if necessary. It's
576576
* always safe to clear bits, so it's better to clear corrupt pages than
577577
* error out.
578+
*
579+
* The initialize-the-page part is trickier than it looks, because of the
580+
* possibility of multiple backends doing this concurrently, and our
581+
* desire to not uselessly take the buffer lock in the normal path where
582+
* the page is OK. We must take the lock to initialize the page, so
583+
* recheck page newness after we have the lock, in case someone else
584+
* already did it. Also, because we initially check PageIsNew with no
585+
* lock, it's possible to fall through and return the buffer while someone
586+
* else is still initializing the page (i.e., we might see pd_upper as set
587+
* but other page header fields are still zeroes). This is harmless for
588+
* callers that will take a buffer lock themselves, but some callers
589+
* inspect the page without any lock at all. The latter is OK only so
590+
* long as it doesn't depend on the page header having correct contents.
591+
* Current usage is safe because PageGetContents() does not require that.
578592
*/
579593
buf = ReadBufferExtended(rel, VISIBILITYMAP_FORKNUM, blkno,
580594
RBM_ZERO_ON_ERROR, NULL);
581595
if (PageIsNew(BufferGetPage(buf)))
582-
PageInit(BufferGetPage(buf), BLCKSZ, 0);
596+
{
597+
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
598+
if (PageIsNew(BufferGetPage(buf)))
599+
PageInit(BufferGetPage(buf), BLCKSZ, 0);
600+
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
601+
}
583602
return buf;
584603
}
585604

src/backend/storage/freespace/freespace.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,10 +553,29 @@ fsm_readbuf(Relation rel, FSMAddress addr, bool extend)
553553
* pages than error out. Since the FSM changes are not WAL-logged, the
554554
* so-called torn page problem on crash can lead to pages with corrupt
555555
* headers, for example.
556+
*
557+
* The initialize-the-page part is trickier than it looks, because of the
558+
* possibility of multiple backends doing this concurrently, and our
559+
* desire to not uselessly take the buffer lock in the normal path where
560+
* the page is OK. We must take the lock to initialize the page, so
561+
* recheck page newness after we have the lock, in case someone else
562+
* already did it. Also, because we initially check PageIsNew with no
563+
* lock, it's possible to fall through and return the buffer while someone
564+
* else is still initializing the page (i.e., we might see pd_upper as set
565+
* but other page header fields are still zeroes). This is harmless for
566+
* callers that will take a buffer lock themselves, but some callers
567+
* inspect the page without any lock at all. The latter is OK only so
568+
* long as it doesn't depend on the page header having correct contents.
569+
* Current usage is safe because PageGetContents() does not require that.
556570
*/
557571
buf = ReadBufferExtended(rel, FSM_FORKNUM, blkno, RBM_ZERO_ON_ERROR, NULL);
558572
if (PageIsNew(BufferGetPage(buf)))
559-
PageInit(BufferGetPage(buf), BLCKSZ, 0);
573+
{
574+
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
575+
if (PageIsNew(BufferGetPage(buf)))
576+
PageInit(BufferGetPage(buf), BLCKSZ, 0);
577+
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
578+
}
560579
return buf;
561580
}
562581

0 commit comments

Comments
 (0)