Skip to content

Commit 38763d6

Browse files
committed
Fix segment_bins corruption in dsa.c.
If a segment has been freed by dsa.c because it is entirely empty, other backends must make sure to unmap it before following links to new segments that might happen to have the same index number, or they could finish up looking at a defunct segment and then corrupt the segment_bins lists. The correct protocol requires checking freed_segment_counter after acquiring the area lock and before resolving any index number to a segment. Add the missing checks and an assertion. Back-patch to 10, where dsa.c first arrived. Author: Thomas Munro Reported-by: Tomas Vondra Discussion: https://postgr.es/m/CAEepm%3D0thg%2Bja5zGVa7jBy-uqyHrTqTm8HGhEOtMmigGrAqTbw%40mail.gmail.com
1 parent 6c3c9d4 commit 38763d6

File tree

1 file changed

+45
-5
lines changed
  • src/backend/utils/mmgr

1 file changed

+45
-5
lines changed

src/backend/utils/mmgr/dsa.c

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ static dsa_area *create_internal(void *place, size_t size,
405405
static dsa_area *attach_internal(void *place, dsm_segment *segment,
406406
dsa_handle handle);
407407
static void check_for_freed_segments(dsa_area *area);
408+
static void check_for_freed_segments_locked(dsa_area *area);
408409

409410
/*
410411
* Create a new shared area in a new DSM segment. Further DSM segments will
@@ -1065,6 +1066,7 @@ dsa_dump(dsa_area *area)
10651066
*/
10661067

10671068
LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE);
1069+
check_for_freed_segments_locked(area);
10681070
fprintf(stderr, "dsa_area handle %x:\n", area->control->handle);
10691071
fprintf(stderr, " max_total_segment_size: %zu\n",
10701072
area->control->max_total_segment_size);
@@ -1762,6 +1764,23 @@ get_segment_by_index(dsa_area *area, dsa_segment_index index)
17621764
(DSA_SEGMENT_HEADER_MAGIC ^ area->control->handle ^ index));
17631765
}
17641766

1767+
/*
1768+
* Callers of dsa_get_address() and dsa_free() don't hold the area lock,
1769+
* but it's a bug in the calling code and undefined behavior if the
1770+
* address is not live (ie if the segment might possibly have been freed,
1771+
* they're trying to use a dangling pointer).
1772+
*
1773+
* For dsa.c code that holds the area lock to manipulate segment_bins
1774+
* lists, it would be a bug if we ever reach a freed segment here. After
1775+
* it's marked as freed, the only thing any backend should do with it is
1776+
* unmap it, and it should always have done that in
1777+
* check_for_freed_segments_locked() before arriving here to resolve an
1778+
* index to a segment_map.
1779+
*
1780+
* Either way we can assert that we aren't returning a freed segment.
1781+
*/
1782+
Assert(!area->segment_maps[index].header->freed);
1783+
17651784
return &area->segment_maps[index];
17661785
}
17671786

@@ -1778,8 +1797,6 @@ destroy_superblock(dsa_area *area, dsa_pointer span_pointer)
17781797
int size_class = span->size_class;
17791798
dsa_segment_map *segment_map;
17801799

1781-
segment_map =
1782-
get_segment_by_index(area, DSA_EXTRACT_SEGMENT_NUMBER(span->start));
17831800

17841801
/* Remove it from its fullness class list. */
17851802
unlink_span(area, span);
@@ -1790,6 +1807,9 @@ destroy_superblock(dsa_area *area, dsa_pointer span_pointer)
17901807
* could deadlock.
17911808
*/
17921809
LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE);
1810+
check_for_freed_segments_locked(area);
1811+
segment_map =
1812+
get_segment_by_index(area, DSA_EXTRACT_SEGMENT_NUMBER(span->start));
17931813
FreePageManagerPut(segment_map->fpm,
17941814
DSA_EXTRACT_OFFSET(span->start) / FPM_PAGE_SIZE,
17951815
span->npages);
@@ -1944,6 +1964,7 @@ get_best_segment(dsa_area *area, Size npages)
19441964
Size bin;
19451965

19461966
Assert(LWLockHeldByMe(DSA_AREA_LOCK(area)));
1967+
check_for_freed_segments_locked(area);
19471968

19481969
/*
19491970
* Start searching from the first bin that *might* have enough contiguous
@@ -2220,10 +2241,30 @@ check_for_freed_segments(dsa_area *area)
22202241
freed_segment_counter = area->control->freed_segment_counter;
22212242
if (unlikely(area->freed_segment_counter != freed_segment_counter))
22222243
{
2223-
int i;
2224-
22252244
/* Check all currently mapped segments to find what's been freed. */
22262245
LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE);
2246+
check_for_freed_segments_locked(area);
2247+
LWLockRelease(DSA_AREA_LOCK(area));
2248+
}
2249+
}
2250+
2251+
/*
2252+
* Workhorse for check_for_free_segments(), and also used directly in path
2253+
* where the area lock is already held. This should be called after acquiring
2254+
* the lock but before looking up any segment by index number, to make sure we
2255+
* unmap any stale segments that might have previously had the same index as a
2256+
* current segment.
2257+
*/
2258+
static void
2259+
check_for_freed_segments_locked(dsa_area *area)
2260+
{
2261+
Size freed_segment_counter;
2262+
int i;
2263+
2264+
Assert(LWLockHeldByMe(DSA_AREA_LOCK(area)));
2265+
freed_segment_counter = area->control->freed_segment_counter;
2266+
if (unlikely(area->freed_segment_counter != freed_segment_counter))
2267+
{
22272268
for (i = 0; i <= area->high_segment_index; ++i)
22282269
{
22292270
if (area->segment_maps[i].header != NULL &&
@@ -2235,7 +2276,6 @@ check_for_freed_segments(dsa_area *area)
22352276
area->segment_maps[i].mapped_address = NULL;
22362277
}
22372278
}
2238-
LWLockRelease(DSA_AREA_LOCK(area));
22392279
area->freed_segment_counter = freed_segment_counter;
22402280
}
22412281
}

0 commit comments

Comments
 (0)