fill_seq_fork_with_data() initializes buffer without lock
Hi,
Look at:
static void
fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
{
Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
/* Initialize first page of relation with special magic number */
buf = ReadBufferExtended(rel, forkNum, P_NEW, RBM_NORMAL, NULL);
Assert(BufferGetBlockNumber(buf) == 0);
page = BufferGetPage(buf);
PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic));
sm = (sequence_magic *) PageGetSpecialPointer(page);
sm->magic = SEQ_MAGIC;
/* Now insert sequence tuple */
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
Clearly we are modifying the page (via PageInit()), without holding a buffer
lock, which is only acquired subsequently.
It's clearly unlikely to cause bad consequences - the sequence doesn't yet
really exist, and we haven't seen any reports of a problem - but it doesn't
seem quite impossible that it would cause problems.
As far as I can tell, this goes back to the initial addition of the sequence
code, in e8647c45d66a - I'm too lazy to figure out whether it possibly wasn't
a problem in 1997 for some reason.
Greetings,
Andres Freund
Hi,
On 2023-04-04 11:55:01 -0700, Andres Freund wrote:
Look at:
static void
fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
{
Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;/* Initialize first page of relation with special magic number */
buf = ReadBufferExtended(rel, forkNum, P_NEW, RBM_NORMAL, NULL);
Assert(BufferGetBlockNumber(buf) == 0);page = BufferGetPage(buf);
PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic));
sm = (sequence_magic *) PageGetSpecialPointer(page);
sm->magic = SEQ_MAGIC;/* Now insert sequence tuple */
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
Clearly we are modifying the page (via PageInit()), without holding a buffer
lock, which is only acquired subsequently.It's clearly unlikely to cause bad consequences - the sequence doesn't yet
really exist, and we haven't seen any reports of a problem - but it doesn't
seem quite impossible that it would cause problems.As far as I can tell, this goes back to the initial addition of the sequence
code, in e8647c45d66a - I'm too lazy to figure out whether it possibly wasn't
a problem in 1997 for some reason.
Robert suggested to add an assertion to PageInit() to defend against such
omissions. I quickly hacked one together. The assertion immediately found the
issue here, but no other currently existing ones.
I'm planning to push a fix for this to HEAD. Given that the risk seems low and
the issue is so longstanding, it doesn't seem quite worth backpatching?
FWIW, the assertion I used is:
if (page >= BufferBlocks && page <= BufferBlocks + BLCKSZ * NBuffers)
{
Buffer buffer = (page - BufferBlocks) / BLCKSZ + 1;
BufferDesc *buf = GetBufferDescriptor(buffer - 1);
Assert(LWLockHeldByMeInMode(BufferDescriptorGetContentLock(buf), LW_EXCLUSIVE));
}
If there's interest in having such an assertion permenantly, it clearly can't
live in bufpage.c.
I have a bit of a hard time coming up with a good name. Any suggestions?
Greetings,
Andres Freund