Skip to content

Commit 8f98352

Browse files
committed
Harden pmsignal.c against clobbered shared memory.
The postmaster is not supposed to do anything that depends fundamentally on shared memory contents, because that creates the risk that a backend crash that trashes shared memory will take the postmaster down with it, preventing automatic recovery. In commit 969d7cd I lost sight of this principle and coded AssignPostmasterChildSlot() in such a way that it could fail or even crash if the shared PMSignalState structure became corrupted. Remarkably, we've not seen field reports of such crashes; but I managed to induce one while testing the recent changes around palloc chunk headers. To fix, make a semi-duplicative state array inside the postmaster so that we need consult only local state while choosing a "child slot" for a new backend. Ensure that other postmaster-executed routines in pmsignal.c don't have critical dependencies on the shared state, either. Corruption of PMSignalState might now lead ReleasePostmasterChildSlot() to conclude that backend X failed, when actually backend Y was the one that trashed things. But that doesn't matter, because we'll force a cluster-wide reset regardless. Back-patch to all supported branches, since this is an old bug. Discussion: https://postgr.es/m/3436789.1665187055@sss.pgh.pa.us
1 parent abc510f commit 8f98352

File tree

1 file changed

+44
-12
lines changed

1 file changed

+44
-12
lines changed

src/backend/storage/ipc/pmsignal.c

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "replication/walsender.h"
2727
#include "storage/pmsignal.h"
2828
#include "storage/shmem.h"
29+
#include "utils/memutils.h"
2930

3031

3132
/*
@@ -69,12 +70,21 @@ struct PMSignalData
6970
sig_atomic_t PMSignalFlags[NUM_PMSIGNALS];
7071
/* per-child-process flags */
7172
int num_child_flags; /* # of entries in PMChildFlags[] */
72-
int next_child_flag; /* next slot to try to assign */
7373
sig_atomic_t PMChildFlags[FLEXIBLE_ARRAY_MEMBER];
7474
};
7575

76+
/* PMSignalState pointer is valid in both postmaster and child processes */
7677
NON_EXEC_STATIC volatile PMSignalData *PMSignalState = NULL;
7778

79+
/*
80+
* These static variables are valid only in the postmaster. We keep a
81+
* duplicative private array so that we can trust its state even if some
82+
* failing child has clobbered the PMSignalData struct in shared memory.
83+
*/
84+
static int num_child_inuse; /* # of entries in PMChildInUse[] */
85+
static int next_child_inuse; /* next slot to try to assign */
86+
static bool *PMChildInUse; /* true if i'th flag slot is assigned */
87+
7888
/*
7989
* Signal handler to be notified if postmaster dies.
8090
*/
@@ -135,7 +145,25 @@ PMSignalShmemInit(void)
135145
if (!found)
136146
{
137147
MemSet(unvolatize(PMSignalData *, PMSignalState), 0, PMSignalShmemSize());
138-
PMSignalState->num_child_flags = MaxLivePostmasterChildren();
148+
num_child_inuse = MaxLivePostmasterChildren();
149+
PMSignalState->num_child_flags = num_child_inuse;
150+
151+
/*
152+
* Also allocate postmaster's private PMChildInUse[] array. We
153+
* might've already done that in a previous shared-memory creation
154+
* cycle, in which case free the old array to avoid a leak. (Do it
155+
* like this to support the possibility that MaxLivePostmasterChildren
156+
* changed.) In a standalone backend, we do not need this.
157+
*/
158+
if (PostmasterContext != NULL)
159+
{
160+
if (PMChildInUse)
161+
pfree(PMChildInUse);
162+
PMChildInUse = (bool *)
163+
MemoryContextAllocZero(PostmasterContext,
164+
num_child_inuse * sizeof(bool));
165+
}
166+
next_child_inuse = 0;
139167
}
140168
}
141169

@@ -183,21 +211,24 @@ CheckPostmasterSignal(PMSignalReason reason)
183211
int
184212
AssignPostmasterChildSlot(void)
185213
{
186-
int slot = PMSignalState->next_child_flag;
214+
int slot = next_child_inuse;
187215
int n;
188216

189217
/*
190-
* Scan for a free slot. We track the last slot assigned so as not to
191-
* waste time repeatedly rescanning low-numbered slots.
218+
* Scan for a free slot. Notice that we trust nothing about the contents
219+
* of PMSignalState, but use only postmaster-local data for this decision.
220+
* We track the last slot assigned so as not to waste time repeatedly
221+
* rescanning low-numbered slots.
192222
*/
193-
for (n = PMSignalState->num_child_flags; n > 0; n--)
223+
for (n = num_child_inuse; n > 0; n--)
194224
{
195225
if (--slot < 0)
196-
slot = PMSignalState->num_child_flags - 1;
197-
if (PMSignalState->PMChildFlags[slot] == PM_CHILD_UNUSED)
226+
slot = num_child_inuse - 1;
227+
if (!PMChildInUse[slot])
198228
{
229+
PMChildInUse[slot] = true;
199230
PMSignalState->PMChildFlags[slot] = PM_CHILD_ASSIGNED;
200-
PMSignalState->next_child_flag = slot;
231+
next_child_inuse = slot;
201232
return slot + 1;
202233
}
203234
}
@@ -219,7 +250,7 @@ ReleasePostmasterChildSlot(int slot)
219250
{
220251
bool result;
221252

222-
Assert(slot > 0 && slot <= PMSignalState->num_child_flags);
253+
Assert(slot > 0 && slot <= num_child_inuse);
223254
slot--;
224255

225256
/*
@@ -229,17 +260,18 @@ ReleasePostmasterChildSlot(int slot)
229260
*/
230261
result = (PMSignalState->PMChildFlags[slot] == PM_CHILD_ASSIGNED);
231262
PMSignalState->PMChildFlags[slot] = PM_CHILD_UNUSED;
263+
PMChildInUse[slot] = false;
232264
return result;
233265
}
234266

235267
/*
236268
* IsPostmasterChildWalSender - check if given slot is in use by a
237-
* walsender process.
269+
* walsender process. This is called only by the postmaster.
238270
*/
239271
bool
240272
IsPostmasterChildWalSender(int slot)
241273
{
242-
Assert(slot > 0 && slot <= PMSignalState->num_child_flags);
274+
Assert(slot > 0 && slot <= num_child_inuse);
243275
slot--;
244276

245277
if (PMSignalState->PMChildFlags[slot] == PM_CHILD_WALSENDER)

0 commit comments

Comments
 (0)