Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 0e87dfe

Browse files
committed
Harden memory context allocators against bogus chunk pointers.
Before commit c6e0fe1, functions such as AllocSetFree could pretty safely presume that they were given a valid chunk pointer for their own type of context, because the indirect call through a memory context object and method struct would be very unlikely to work otherwise. But now, if pfree() is mistakenly invoked on a pointer to garbage, we have three chances in eight of ending up at one of these functions. That means we need to take extra measures to verify that we are looking at what we're supposed to be looking at, especially in debug builds. Hence, add code to verify that the chunk's back-link to a block header leads to a memory context object that satisfies the right sort of IsA() check. This is still a bit weaker than what we did before, but for the moment assume that an IsA() check is sufficient. As a compromise between speed and safety, implement these checks as Asserts when dealing with small chunks but plain test-and-elogs when dealing with large (external) chunks. The latter case should not be too performance-critical, but the former case probably is. In slab.c, all chunks are small; but nonetheless use a plain test in SlabRealloc, because that is certainly not performance-critical, indeed we should be suspicious that it's being called in error. In aset.c, additionally add some assertions that the "value" field of the chunk header is within the small range allowed for freelist indexes. Without that, we might find ourselves trying to wipe most of memory when CLOBBER_FREED_MEMORY is enabled, or scribbling on a "freelist header" that's far away from the context object. Eventually, field experience might show us that it's smarter for these tests to be active always, but for now we'll try to get away with just having them as assertions. While at it, also be more uniform about asserting that context objects passed as parameters are of the type we expect. Some places missed that altogether, and slab.c was for no very good reason doing it differently from the other allocators. Discussion: https://postgr.es/m/3578387.1665244345@sss.pgh.pa.us
1 parent 235eb4d commit 0e87dfe

File tree

3 files changed

+187
-51
lines changed

3 files changed

+187
-51
lines changed

src/backend/utils/mmgr/aset.c

+84-30
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ typedef struct AllocFreeListLink
132132
#define GetFreeListLink(chkptr) \
133133
(AllocFreeListLink *) ((char *) (chkptr) + ALLOC_CHUNKHDRSZ)
134134

135+
/* Validate a freelist index retrieved from a chunk header */
136+
#define FreeListIdxIsValid(fidx) \
137+
((fidx) >= 0 && (fidx) < ALLOCSET_NUM_FREELISTS)
138+
135139
/* Determine the size of the chunk based on the freelist index */
136140
#define GetChunkSizeFromFreeListIdx(fidx) \
137141
((((Size) 1) << ALLOC_MINBITS) << (fidx))
@@ -202,7 +206,15 @@ typedef struct AllocBlockData
202206
* AllocSetIsValid
203207
* True iff set is valid allocation set.
204208
*/
205-
#define AllocSetIsValid(set) PointerIsValid(set)
209+
#define AllocSetIsValid(set) \
210+
(PointerIsValid(set) && IsA(set, AllocSetContext))
211+
212+
/*
213+
* AllocBlockIsValid
214+
* True iff block is valid block of allocation set.
215+
*/
216+
#define AllocBlockIsValid(block) \
217+
(PointerIsValid(block) && AllocSetIsValid((block)->aset))
206218

207219
/*
208220
* We always store external chunks on a dedicated block. This makes fetching
@@ -530,8 +542,7 @@ AllocSetReset(MemoryContext context)
530542
{
531543
AllocSet set = (AllocSet) context;
532544
AllocBlock block;
533-
Size keepersize PG_USED_FOR_ASSERTS_ONLY
534-
= set->keeper->endptr - ((char *) set);
545+
Size keepersize PG_USED_FOR_ASSERTS_ONLY;
535546

536547
AssertArg(AllocSetIsValid(set));
537548

@@ -540,6 +551,9 @@ AllocSetReset(MemoryContext context)
540551
AllocSetCheck(context);
541552
#endif
542553

554+
/* Remember keeper block size for Assert below */
555+
keepersize = set->keeper->endptr - ((char *) set);
556+
543557
/* Clear chunk freelists */
544558
MemSetAligned(set->freelist, 0, sizeof(set->freelist));
545559

@@ -598,8 +612,7 @@ AllocSetDelete(MemoryContext context)
598612
{
599613
AllocSet set = (AllocSet) context;
600614
AllocBlock block = set->blocks;
601-
Size keepersize PG_USED_FOR_ASSERTS_ONLY
602-
= set->keeper->endptr - ((char *) set);
615+
Size keepersize PG_USED_FOR_ASSERTS_ONLY;
603616

604617
AssertArg(AllocSetIsValid(set));
605618

@@ -608,6 +621,9 @@ AllocSetDelete(MemoryContext context)
608621
AllocSetCheck(context);
609622
#endif
610623

624+
/* Remember keeper block size for Assert below */
625+
keepersize = set->keeper->endptr - ((char *) set);
626+
611627
/*
612628
* If the context is a candidate for a freelist, put it into that freelist
613629
* instead of destroying it.
@@ -994,9 +1010,16 @@ AllocSetFree(void *pointer)
9941010

9951011
if (MemoryChunkIsExternal(chunk))
9961012
{
997-
1013+
/* Release single-chunk block. */
9981014
AllocBlock block = ExternalChunkGetBlock(chunk);
9991015

1016+
/*
1017+
* Try to verify that we have a sane block pointer: the block header
1018+
* should reference an aset and the freeptr should match the endptr.
1019+
*/
1020+
if (!AllocBlockIsValid(block) || block->freeptr != block->endptr)
1021+
elog(ERROR, "could not find block containing chunk %p", chunk);
1022+
10001023
set = block->aset;
10011024

10021025
#ifdef MEMORY_CONTEXT_CHECKING
@@ -1011,14 +1034,6 @@ AllocSetFree(void *pointer)
10111034
}
10121035
#endif
10131036

1014-
1015-
/*
1016-
* Try to verify that we have a sane block pointer, the freeptr should
1017-
* match the endptr.
1018-
*/
1019-
if (block->freeptr != block->endptr)
1020-
elog(ERROR, "could not find block containing chunk %p", chunk);
1021-
10221037
/* OK, remove block from aset's list and free it */
10231038
if (block->prev)
10241039
block->prev->next = block->next;
@@ -1036,12 +1051,23 @@ AllocSetFree(void *pointer)
10361051
}
10371052
else
10381053
{
1039-
int fidx = MemoryChunkGetValue(chunk);
10401054
AllocBlock block = MemoryChunkGetBlock(chunk);
1041-
AllocFreeListLink *link = GetFreeListLink(chunk);
1055+
int fidx;
1056+
AllocFreeListLink *link;
10421057

1058+
/*
1059+
* In this path, for speed reasons we just Assert that the referenced
1060+
* block is good. We can also Assert that the value field is sane.
1061+
* Future field experience may show that these Asserts had better
1062+
* become regular runtime test-and-elog checks.
1063+
*/
1064+
AssertArg(AllocBlockIsValid(block));
10431065
set = block->aset;
10441066

1067+
fidx = MemoryChunkGetValue(chunk);
1068+
Assert(FreeListIdxIsValid(fidx));
1069+
link = GetFreeListLink(chunk);
1070+
10451071
#ifdef MEMORY_CONTEXT_CHECKING
10461072
/* Test for someone scribbling on unused space in chunk */
10471073
if (chunk->requested_size < GetChunkSizeFromFreeListIdx(fidx))
@@ -1089,6 +1115,7 @@ AllocSetRealloc(void *pointer, Size size)
10891115
AllocSet set;
10901116
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
10911117
Size oldsize;
1118+
int fidx;
10921119

10931120
/* Allow access to private part of chunk header. */
10941121
VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOCCHUNK_PRIVATE_LEN);
@@ -1105,9 +1132,18 @@ AllocSetRealloc(void *pointer, Size size)
11051132
Size oldblksize;
11061133

11071134
block = ExternalChunkGetBlock(chunk);
1108-
oldsize = block->endptr - (char *) pointer;
1135+
1136+
/*
1137+
* Try to verify that we have a sane block pointer: the block header
1138+
* should reference an aset and the freeptr should match the endptr.
1139+
*/
1140+
if (!AllocBlockIsValid(block) || block->freeptr != block->endptr)
1141+
elog(ERROR, "could not find block containing chunk %p", chunk);
1142+
11091143
set = block->aset;
11101144

1145+
oldsize = block->endptr - (char *) pointer;
1146+
11111147
#ifdef MEMORY_CONTEXT_CHECKING
11121148
/* Test for someone scribbling on unused space in chunk */
11131149
Assert(chunk->requested_size < oldsize);
@@ -1116,13 +1152,6 @@ AllocSetRealloc(void *pointer, Size size)
11161152
set->header.name, chunk);
11171153
#endif
11181154

1119-
/*
1120-
* Try to verify that we have a sane block pointer, the freeptr should
1121-
* match the endptr.
1122-
*/
1123-
if (block->freeptr != block->endptr)
1124-
elog(ERROR, "could not find block containing chunk %p", chunk);
1125-
11261155
#ifdef MEMORY_CONTEXT_CHECKING
11271156
/* ensure there's always space for the sentinel byte */
11281157
chksize = MAXALIGN(size + 1);
@@ -1201,9 +1230,20 @@ AllocSetRealloc(void *pointer, Size size)
12011230
}
12021231

12031232
block = MemoryChunkGetBlock(chunk);
1204-
oldsize = GetChunkSizeFromFreeListIdx(MemoryChunkGetValue(chunk));
1233+
1234+
/*
1235+
* In this path, for speed reasons we just Assert that the referenced
1236+
* block is good. We can also Assert that the value field is sane. Future
1237+
* field experience may show that these Asserts had better become regular
1238+
* runtime test-and-elog checks.
1239+
*/
1240+
AssertArg(AllocBlockIsValid(block));
12051241
set = block->aset;
12061242

1243+
fidx = MemoryChunkGetValue(chunk);
1244+
Assert(FreeListIdxIsValid(fidx));
1245+
oldsize = GetChunkSizeFromFreeListIdx(fidx);
1246+
12071247
#ifdef MEMORY_CONTEXT_CHECKING
12081248
/* Test for someone scribbling on unused space in chunk */
12091249
if (chunk->requested_size < oldsize)
@@ -1328,6 +1368,7 @@ AllocSetGetChunkContext(void *pointer)
13281368
else
13291369
block = (AllocBlock) MemoryChunkGetBlock(chunk);
13301370

1371+
AssertArg(AllocBlockIsValid(block));
13311372
set = block->aset;
13321373

13331374
return &set->header;
@@ -1342,16 +1383,19 @@ Size
13421383
AllocSetGetChunkSpace(void *pointer)
13431384
{
13441385
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
1386+
int fidx;
13451387

13461388
if (MemoryChunkIsExternal(chunk))
13471389
{
13481390
AllocBlock block = ExternalChunkGetBlock(chunk);
13491391

1392+
AssertArg(AllocBlockIsValid(block));
13501393
return block->endptr - (char *) chunk;
13511394
}
13521395

1353-
return GetChunkSizeFromFreeListIdx(MemoryChunkGetValue(chunk)) +
1354-
ALLOC_CHUNKHDRSZ;
1396+
fidx = MemoryChunkGetValue(chunk);
1397+
Assert(FreeListIdxIsValid(fidx));
1398+
return GetChunkSizeFromFreeListIdx(fidx) + ALLOC_CHUNKHDRSZ;
13551399
}
13561400

13571401
/*
@@ -1361,6 +1405,8 @@ AllocSetGetChunkSpace(void *pointer)
13611405
bool
13621406
AllocSetIsEmpty(MemoryContext context)
13631407
{
1408+
AssertArg(AllocSetIsValid(context));
1409+
13641410
/*
13651411
* For now, we say "empty" only if the context is new or just reset. We
13661412
* could examine the freelists to determine if all space has been freed,
@@ -1394,6 +1440,8 @@ AllocSetStats(MemoryContext context,
13941440
AllocBlock block;
13951441
int fidx;
13961442

1443+
AssertArg(AllocSetIsValid(set));
1444+
13971445
/* Include context header in totalspace */
13981446
totalspace = MAXALIGN(sizeof(AllocSetContext));
13991447

@@ -1405,14 +1453,14 @@ AllocSetStats(MemoryContext context,
14051453
}
14061454
for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++)
14071455
{
1456+
Size chksz = GetChunkSizeFromFreeListIdx(fidx);
14081457
MemoryChunk *chunk = set->freelist[fidx];
14091458

14101459
while (chunk != NULL)
14111460
{
1412-
Size chksz = GetChunkSizeFromFreeListIdx(MemoryChunkGetValue(chunk));
14131461
AllocFreeListLink *link = GetFreeListLink(chunk);
14141462

1415-
Assert(GetChunkSizeFromFreeListIdx(fidx) == chksz);
1463+
Assert(MemoryChunkGetValue(chunk) == fidx);
14161464

14171465
freechunks++;
14181466
freespace += chksz + ALLOC_CHUNKHDRSZ;
@@ -1522,7 +1570,13 @@ AllocSetCheck(MemoryContext context)
15221570
}
15231571
else
15241572
{
1525-
chsize = GetChunkSizeFromFreeListIdx(MemoryChunkGetValue(chunk)); /* aligned chunk size */
1573+
int fidx = MemoryChunkGetValue(chunk);
1574+
1575+
if (!FreeListIdxIsValid(fidx))
1576+
elog(WARNING, "problem in alloc set %s: bad chunk size for chunk %p in block %p",
1577+
name, chunk, block);
1578+
1579+
chsize = GetChunkSizeFromFreeListIdx(fidx); /* aligned chunk size */
15261580

15271581
/*
15281582
* Check the stored block offset correctly references this

src/backend/utils/mmgr/generation.c

+51-2
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,20 @@ struct GenerationBlock
105105
* simplicity.
106106
*/
107107
#define GENERATIONCHUNK_PRIVATE_LEN offsetof(MemoryChunk, hdrmask)
108+
108109
/*
109110
* GenerationIsValid
110-
* True iff set is valid allocation set.
111+
* True iff set is valid generation set.
112+
*/
113+
#define GenerationIsValid(set) \
114+
(PointerIsValid(set) && IsA(set, GenerationContext))
115+
116+
/*
117+
* GenerationBlockIsValid
118+
* True iff block is valid block of generation set.
111119
*/
112-
#define GenerationIsValid(set) PointerIsValid(set)
120+
#define GenerationBlockIsValid(block) \
121+
(PointerIsValid(block) && GenerationIsValid((block)->context))
113122

114123
/*
115124
* We always store external chunks on a dedicated block. This makes fetching
@@ -345,6 +354,8 @@ GenerationAlloc(MemoryContext context, Size size)
345354
Size chunk_size;
346355
Size required_size;
347356

357+
AssertArg(GenerationIsValid(set));
358+
348359
#ifdef MEMORY_CONTEXT_CHECKING
349360
/* ensure there's always space for the sentinel byte */
350361
chunk_size = MAXALIGN(size + 1);
@@ -625,13 +636,29 @@ GenerationFree(void *pointer)
625636
if (MemoryChunkIsExternal(chunk))
626637
{
627638
block = ExternalChunkGetBlock(chunk);
639+
640+
/*
641+
* Try to verify that we have a sane block pointer: the block header
642+
* should reference a generation context.
643+
*/
644+
if (!GenerationBlockIsValid(block))
645+
elog(ERROR, "could not find block containing chunk %p", chunk);
646+
628647
#if defined(MEMORY_CONTEXT_CHECKING) || defined(CLOBBER_FREED_MEMORY)
629648
chunksize = block->endptr - (char *) pointer;
630649
#endif
631650
}
632651
else
633652
{
634653
block = MemoryChunkGetBlock(chunk);
654+
655+
/*
656+
* In this path, for speed reasons we just Assert that the referenced
657+
* block is good. Future field experience may show that this Assert
658+
* had better become a regular runtime test-and-elog check.
659+
*/
660+
AssertArg(GenerationBlockIsValid(block));
661+
635662
#if defined(MEMORY_CONTEXT_CHECKING) || defined(CLOBBER_FREED_MEMORY)
636663
chunksize = MemoryChunkGetValue(chunk);
637664
#endif
@@ -723,11 +750,27 @@ GenerationRealloc(void *pointer, Size size)
723750
if (MemoryChunkIsExternal(chunk))
724751
{
725752
block = ExternalChunkGetBlock(chunk);
753+
754+
/*
755+
* Try to verify that we have a sane block pointer: the block header
756+
* should reference a generation context.
757+
*/
758+
if (!GenerationBlockIsValid(block))
759+
elog(ERROR, "could not find block containing chunk %p", chunk);
760+
726761
oldsize = block->endptr - (char *) pointer;
727762
}
728763
else
729764
{
730765
block = MemoryChunkGetBlock(chunk);
766+
767+
/*
768+
* In this path, for speed reasons we just Assert that the referenced
769+
* block is good. Future field experience may show that this Assert
770+
* had better become a regular runtime test-and-elog check.
771+
*/
772+
AssertArg(GenerationBlockIsValid(block));
773+
731774
oldsize = MemoryChunkGetValue(chunk);
732775
}
733776

@@ -845,6 +888,7 @@ GenerationGetChunkContext(void *pointer)
845888
else
846889
block = (GenerationBlock *) MemoryChunkGetBlock(chunk);
847890

891+
AssertArg(GenerationBlockIsValid(block));
848892
return &block->context->header;
849893
}
850894

@@ -863,6 +907,7 @@ GenerationGetChunkSpace(void *pointer)
863907
{
864908
GenerationBlock *block = ExternalChunkGetBlock(chunk);
865909

910+
AssertArg(GenerationBlockIsValid(block));
866911
chunksize = block->endptr - (char *) pointer;
867912
}
868913
else
@@ -881,6 +926,8 @@ GenerationIsEmpty(MemoryContext context)
881926
GenerationContext *set = (GenerationContext *) context;
882927
dlist_iter iter;
883928

929+
AssertArg(GenerationIsValid(set));
930+
884931
dlist_foreach(iter, &set->blocks)
885932
{
886933
GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
@@ -917,6 +964,8 @@ GenerationStats(MemoryContext context,
917964
Size freespace = 0;
918965
dlist_iter iter;
919966

967+
AssertArg(GenerationIsValid(set));
968+
920969
/* Include context header in totalspace */
921970
totalspace = MAXALIGN(sizeof(GenerationContext));
922971

0 commit comments

Comments
 (0)