Skip to content

Commit 90decdb

Browse files
committed
Fix actual and potential double-frees around tuplesort usage.
tuplesort_gettupleslot() passed back tuples allocated in the tuplesort's own memory context, even when the caller was responsible to free them. This created a double-free hazard, because some callers might destroy the tuplesort object (via tuplesort_end) before trying to clean up the last returned tuple. To avoid this, change the API to specify that the tuple is allocated in the caller's memory context. v10 and HEAD already did things that way, but in 9.5 and 9.6 this is a live bug that can demonstrably cause crashes with some grouping-set usages. In 9.5 and 9.6, this requires doing an extra tuple copy in some cases, which is unfortunate. But the amount of refactoring needed to avoid it seems excessive for a back-patched change, especially since the cases where an extra copy happens are less performance-critical. Likewise change tuplesort_getdatum() to return pass-by-reference Datums in the caller's context not the tuplesort's context. There seem to be no live bugs among its callers, but clearly the same sort of situation could happen in future. For other tuplesort fetch routines, continue to allocate the memory in the tuplesort's context. This is a little inconsistent with what we now do for tuplesort_gettupleslot() and tuplesort_getdatum(), but that's preferable to adding new copy overhead in the back branches where it's clearly unnecessary. These other fetch routines provide the weakest possible guarantees about tuple memory lifespan from v10 on, anyway, so this actually seems more consistent overall. Adjust relevant comments to reflect these API redefinitions. Arguably, we should change the pre-9.5 branches as well, but since there are no known failure cases there, it seems not worth the risk. Peter Geoghegan, per report from Bernd Helmle. Reviewed by Kyotaro Horiguchi; thanks also to Andreas Seltenreich for extracting a self-contained test case. Discussion: https://postgr.es/m/1512661638.9720.34.camel@oopsware.de
1 parent 43cd7ab commit 90decdb

File tree

2 files changed

+60
-31
lines changed

2 files changed

+60
-31
lines changed

src/backend/utils/adt/orderedsetaggs.c

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -457,10 +457,9 @@ percentile_disc_final(PG_FUNCTION_ARGS)
457457
elog(ERROR, "missing row in percentile_disc");
458458

459459
/*
460-
* Note: we *cannot* clean up the tuplesort object here, because the value
461-
* to be returned is allocated inside its sortcontext. We could use
462-
* datumCopy to copy it out of there, but it doesn't seem worth the
463-
* trouble, since the cleanup callback will clear the tuplesort later.
460+
* Note: we could clean up the tuplesort object here, but it doesn't seem
461+
* worth the trouble, since the cleanup callback will clear the tuplesort
462+
* later.
464463
*/
465464

466465
/* We shouldn't have stored any nulls, but do the right thing anyway */
@@ -575,10 +574,9 @@ percentile_cont_final_common(FunctionCallInfo fcinfo,
575574
}
576575

577576
/*
578-
* Note: we *cannot* clean up the tuplesort object here, because the value
579-
* to be returned may be allocated inside its sortcontext. We could use
580-
* datumCopy to copy it out of there, but it doesn't seem worth the
581-
* trouble, since the cleanup callback will clear the tuplesort later.
577+
* Note: we could clean up the tuplesort object here, but it doesn't seem
578+
* worth the trouble, since the cleanup callback will clear the tuplesort
579+
* later.
582580
*/
583581

584582
PG_RETURN_DATUM(val);
@@ -1097,10 +1095,9 @@ mode_final(PG_FUNCTION_ARGS)
10971095
pfree(DatumGetPointer(last_val));
10981096

10991097
/*
1100-
* Note: we *cannot* clean up the tuplesort object here, because the value
1101-
* to be returned is allocated inside its sortcontext. We could use
1102-
* datumCopy to copy it out of there, but it doesn't seem worth the
1103-
* trouble, since the cleanup callback will clear the tuplesort later.
1098+
* Note: we could clean up the tuplesort object here, but it doesn't seem
1099+
* worth the trouble, since the cleanup callback will clear the tuplesort
1100+
* later.
11041101
*/
11051102

11061103
if (mode_freq)

src/backend/utils/sort/tuplesort.c

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,11 @@ tuplesort_performsort(Tuplesortstate *state)
18241824
* direction into *stup. Returns FALSE if no more tuples.
18251825
* If *should_free is set, the caller must pfree stup.tuple when done with it.
18261826
* Otherwise, caller should not use tuple following next call here.
1827+
*
1828+
* Note: Public tuplesort fetch routine callers cannot rely on tuple being
1829+
* allocated in their own memory context when should_free is TRUE. It may be
1830+
* necessary to create a new copy of the tuple to meet the requirements of
1831+
* public fetch routine callers.
18271832
*/
18281833
static bool
18291834
tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
@@ -2046,9 +2051,10 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
20462051
* NULL value in leading attribute will set abbreviated value to zeroed
20472052
* representation, which caller may rely on in abbreviated inequality check.
20482053
*
2049-
* The slot receives a copied tuple (sometimes allocated in caller memory
2050-
* context) that will stay valid regardless of future manipulations of the
2051-
* tuplesort's state.
2054+
* The slot receives a tuple that's been copied into the caller's memory
2055+
* context, so that it will stay valid regardless of future manipulations of
2056+
* the tuplesort's state (up to and including deleting the tuplesort).
2057+
* This differs from similar routines for other types of tuplesorts.
20522058
*/
20532059
bool
20542060
tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
@@ -2069,12 +2075,24 @@ tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
20692075
if (state->sortKeys->abbrev_converter && abbrev)
20702076
*abbrev = stup.datum1;
20712077

2072-
if (!should_free)
2073-
{
2074-
stup.tuple = heap_copy_minimal_tuple((MinimalTuple) stup.tuple);
2075-
should_free = true;
2076-
}
2077-
ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free);
2078+
/*
2079+
* Callers rely on tuple being in their own memory context, which is
2080+
* not guaranteed by tuplesort_gettuple_common(), even when should_free
2081+
* is set to TRUE. We must always copy here, since our interface does
2082+
* not allow callers to opt into arrangement where tuple memory can go
2083+
* away on the next call here, or after tuplesort_end() is called.
2084+
*/
2085+
ExecStoreMinimalTuple(heap_copy_minimal_tuple((MinimalTuple) stup.tuple),
2086+
slot, true);
2087+
2088+
/*
2089+
* Free local copy if needed. It would be very invasive to get
2090+
* tuplesort_gettuple_common() to allocate tuple in caller's context
2091+
* for us, so we just do this instead.
2092+
*/
2093+
if (should_free)
2094+
pfree(stup.tuple);
2095+
20782096
return true;
20792097
}
20802098
else
@@ -2089,7 +2107,7 @@ tuplesort_gettupleslot(Tuplesortstate *state, bool forward,
20892107
* Returns NULL if no more tuples. If *should_free is set, the
20902108
* caller must pfree the returned tuple when done with it.
20912109
* If it is not set, caller should not use tuple following next
2092-
* call here.
2110+
* call here. It's never okay to use it after tuplesort_end().
20932111
*/
20942112
HeapTuple
20952113
tuplesort_getheaptuple(Tuplesortstate *state, bool forward, bool *should_free)
@@ -2110,7 +2128,7 @@ tuplesort_getheaptuple(Tuplesortstate *state, bool forward, bool *should_free)
21102128
* Returns NULL if no more tuples. If *should_free is set, the
21112129
* caller must pfree the returned tuple when done with it.
21122130
* If it is not set, caller should not use tuple following next
2113-
* call here.
2131+
* call here. It's never okay to use it after tuplesort_end().
21142132
*/
21152133
IndexTuple
21162134
tuplesort_getindextuple(Tuplesortstate *state, bool forward,
@@ -2132,7 +2150,8 @@ tuplesort_getindextuple(Tuplesortstate *state, bool forward,
21322150
* Returns FALSE if no more datums.
21332151
*
21342152
* If the Datum is pass-by-ref type, the returned value is freshly palloc'd
2135-
* and is now owned by the caller.
2153+
* in caller's context, and is now owned by the caller (this differs from
2154+
* similar routines for other types of tuplesorts).
21362155
*
21372156
* Caller may optionally be passed back abbreviated value (on TRUE return
21382157
* value) when abbreviation was used, which can be used to cheaply avoid
@@ -2155,6 +2174,9 @@ tuplesort_getdatum(Tuplesortstate *state, bool forward,
21552174
return false;
21562175
}
21572176

2177+
/* Ensure we copy into caller's memory context */
2178+
MemoryContextSwitchTo(oldcontext);
2179+
21582180
/* Record abbreviated key for caller */
21592181
if (state->sortKeys->abbrev_converter && abbrev)
21602182
*abbrev = stup.datum1;
@@ -2166,17 +2188,27 @@ tuplesort_getdatum(Tuplesortstate *state, bool forward,
21662188
}
21672189
else
21682190
{
2169-
/* use stup.tuple because stup.datum1 may be an abbreviation */
2191+
/*
2192+
* Callers rely on datum being in their own memory context, which is
2193+
* not guaranteed by tuplesort_gettuple_common(), even when should_free
2194+
* is set to TRUE. We must always copy here, since our interface does
2195+
* not allow callers to opt into arrangement where tuple memory can go
2196+
* away on the next call here, or after tuplesort_end() is called.
2197+
*
2198+
* Use stup.tuple because stup.datum1 may be an abbreviation.
2199+
*/
2200+
*val = datumCopy(PointerGetDatum(stup.tuple), false, state->datumTypeLen);
2201+
*isNull = false;
21702202

2203+
/*
2204+
* Free local copy if needed. It would be very invasive to get
2205+
* tuplesort_gettuple_common() to allocate tuple in caller's context
2206+
* for us, so we just do this instead.
2207+
*/
21712208
if (should_free)
2172-
*val = PointerGetDatum(stup.tuple);
2173-
else
2174-
*val = datumCopy(PointerGetDatum(stup.tuple), false, state->datumTypeLen);
2175-
*isNull = false;
2209+
pfree(stup.tuple);
21762210
}
21772211

2178-
MemoryContextSwitchTo(oldcontext);
2179-
21802212
return true;
21812213
}
21822214

0 commit comments

Comments
 (0)