Skip to content

Commit 36522d6

Browse files
committed
Fix a low-probability crash in our qsort implementation.
It's standard for quicksort implementations, after having partitioned the input into two subgroups, to recurse to process the smaller partition and then handle the larger partition by iterating. This method guarantees that no more than log2(N) levels of recursion can be needed. However, Bentley and McIlroy argued that checking to see which partition is smaller isn't worth the cycles, and so their code doesn't do that but just always recurses on the left partition. In most cases that's fine; but with worst-case input we might need O(N) levels of recursion, and that means that qsort could be driven to stack overflow. Such an overflow seems to be the only explanation for today's report from Yiqing Jin of a SIGSEGV in med3_tuple while creating an index of a couple billion entries with a very large maintenance_work_mem setting. Therefore, let's spend the few additional cycles and lines of code needed to choose the smaller partition for recursion. Also, fix up the qsort code so that it properly uses size_t not int for some intermediate values representing numbers of items. This would only be a live risk when sorting more than INT_MAX bytes (in qsort/qsort_arg) or tuples (in qsort_tuple), which I believe would never happen with any caller in the current core code --- but perhaps it could happen with call sites in third-party modules? In any case, this is trouble waiting to happen, and the corrected code is probably if anything shorter and faster than before, since it removes sign-extension steps that had to happen when converting between int and size_t. In passing, move a couple of CHECK_FOR_INTERRUPTS() calls so that it's not necessary to preserve the value of "r" across them, and prettify the output of gen_qsort_tuple.pl a little. Back-patch to all supported branches. The odds of hitting this issue are probably higher in 9.4 and up than before, due to the new ability to allocate sort workspaces exceeding 1GB, but there's no good reason to believe that it's impossible to crash older branches this way.
1 parent 7803d57 commit 36522d6

File tree

2 files changed

+94
-32
lines changed

2 files changed

+94
-32
lines changed

src/port/qsort.c

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Remove __inline, _DIAGASSERTs, __P
77
* Remove ill-considered "swap_cnt" switch to insertion sort,
88
* in favor of a simple check for presorted input.
9+
* Take care to recurse on the smaller partition, to bound stack usage.
910
*
1011
* CAUTION: if you change this file, see also qsort_arg.c
1112
*
@@ -54,9 +55,18 @@ static void swapfunc(char *, char *, size_t, int);
5455
* Qsort routine based on J. L. Bentley and M. D. McIlroy,
5556
* "Engineering a sort function",
5657
* Software--Practice and Experience 23 (1993) 1249-1265.
58+
*
5759
* We have modified their original by adding a check for already-sorted input,
5860
* which seems to be a win per discussions on pgsql-hackers around 2006-03-21.
61+
*
62+
* Also, we recurse on the smaller partition and iterate on the larger one,
63+
* which ensures we cannot recurse more than log(N) levels (since the
64+
* partition recursed to is surely no more than half of the input). Bentley
65+
* and McIlroy explicitly rejected doing this on the grounds that it's "not
66+
* worth the effort", but we have seen crashes in the field due to stack
67+
* overrun, so that judgment seems wrong.
5968
*/
69+
6070
#define swapcode(TYPE, parmi, parmj, n) \
6171
do { \
6272
size_t i = (n) / sizeof (TYPE); \
@@ -89,7 +99,7 @@ swapfunc(char *a, char *b, size_t n, int swaptype)
8999
} else \
90100
swapfunc(a, b, es, swaptype)
91101

92-
#define vecswap(a, b, n) if ((n) > 0) swapfunc((a), (b), (size_t)(n), swaptype)
102+
#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype)
93103

94104
static char *
95105
med3(char *a, char *b, char *c, int (*cmp) (const void *, const void *))
@@ -109,8 +119,9 @@ pg_qsort(void *a, size_t n, size_t es, int (*cmp) (const void *, const void *))
109119
*pl,
110120
*pm,
111121
*pn;
112-
int d,
113-
r,
122+
size_t d1,
123+
d2;
124+
int r,
114125
swaptype,
115126
presorted;
116127

@@ -141,7 +152,8 @@ loop:SWAPINIT(a, es);
141152
pn = (char *) a + (n - 1) * es;
142153
if (n > 40)
143154
{
144-
d = (n / 8) * es;
155+
size_t d = (n / 8) * es;
156+
145157
pl = med3(pl, pl + d, pl + 2 * d, cmp);
146158
pm = med3(pm - d, pm, pm + d, cmp);
147159
pn = med3(pn - 2 * d, pn - d, pn, cmp);
@@ -178,18 +190,37 @@ loop:SWAPINIT(a, es);
178190
pc -= es;
179191
}
180192
pn = (char *) a + n * es;
181-
r = Min(pa - (char *) a, pb - pa);
182-
vecswap(a, pb - r, r);
183-
r = Min(pd - pc, pn - pd - es);
184-
vecswap(pb, pn - r, r);
185-
if ((r = pb - pa) > es)
186-
qsort(a, r / es, es, cmp);
187-
if ((r = pd - pc) > es)
193+
d1 = Min(pa - (char *) a, pb - pa);
194+
vecswap(a, pb - d1, d1);
195+
d1 = Min(pd - pc, pn - pd - es);
196+
vecswap(pb, pn - d1, d1);
197+
d1 = pb - pa;
198+
d2 = pd - pc;
199+
if (d1 <= d2)
188200
{
189-
/* Iterate rather than recurse to save stack space */
190-
a = pn - r;
191-
n = r / es;
192-
goto loop;
201+
/* Recurse on left partition, then iterate on right partition */
202+
if (d1 > es)
203+
pg_qsort(a, d1 / es, es, cmp);
204+
if (d2 > es)
205+
{
206+
/* Iterate rather than recurse to save stack space */
207+
/* pg_qsort(pn - d2, d2 / es, es, cmp); */
208+
a = pn - d2;
209+
n = d2 / es;
210+
goto loop;
211+
}
212+
}
213+
else
214+
{
215+
/* Recurse on right partition, then iterate on left partition */
216+
if (d2 > es)
217+
pg_qsort(pn - d2, d2 / es, es, cmp);
218+
if (d1 > es)
219+
{
220+
/* Iterate rather than recurse to save stack space */
221+
/* pg_qsort(a, d1 / es, es, cmp); */
222+
n = d1 / es;
223+
goto loop;
224+
}
193225
}
194-
/* qsort(pn - r, r / es, es, cmp);*/
195226
}

src/port/qsort_arg.c

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Remove __inline, _DIAGASSERTs, __P
77
* Remove ill-considered "swap_cnt" switch to insertion sort,
88
* in favor of a simple check for presorted input.
9+
* Take care to recurse on the smaller partition, to bound stack usage.
910
*
1011
* CAUTION: if you change this file, see also qsort.c
1112
*
@@ -54,9 +55,18 @@ static void swapfunc(char *, char *, size_t, int);
5455
* Qsort routine based on J. L. Bentley and M. D. McIlroy,
5556
* "Engineering a sort function",
5657
* Software--Practice and Experience 23 (1993) 1249-1265.
58+
*
5759
* We have modified their original by adding a check for already-sorted input,
5860
* which seems to be a win per discussions on pgsql-hackers around 2006-03-21.
61+
*
62+
* Also, we recurse on the smaller partition and iterate on the larger one,
63+
* which ensures we cannot recurse more than log(N) levels (since the
64+
* partition recursed to is surely no more than half of the input). Bentley
65+
* and McIlroy explicitly rejected doing this on the grounds that it's "not
66+
* worth the effort", but we have seen crashes in the field due to stack
67+
* overrun, so that judgment seems wrong.
5968
*/
69+
6070
#define swapcode(TYPE, parmi, parmj, n) \
6171
do { \
6272
size_t i = (n) / sizeof (TYPE); \
@@ -89,7 +99,7 @@ swapfunc(char *a, char *b, size_t n, int swaptype)
8999
} else \
90100
swapfunc(a, b, es, swaptype)
91101

92-
#define vecswap(a, b, n) if ((n) > 0) swapfunc((a), (b), (size_t)(n), swaptype)
102+
#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype)
93103

94104
static char *
95105
med3(char *a, char *b, char *c, qsort_arg_comparator cmp, void *arg)
@@ -109,8 +119,9 @@ qsort_arg(void *a, size_t n, size_t es, qsort_arg_comparator cmp, void *arg)
109119
*pl,
110120
*pm,
111121
*pn;
112-
int d,
113-
r,
122+
size_t d1,
123+
d2;
124+
int r,
114125
swaptype,
115126
presorted;
116127

@@ -141,7 +152,8 @@ loop:SWAPINIT(a, es);
141152
pn = (char *) a + (n - 1) * es;
142153
if (n > 40)
143154
{
144-
d = (n / 8) * es;
155+
size_t d = (n / 8) * es;
156+
145157
pl = med3(pl, pl + d, pl + 2 * d, cmp, arg);
146158
pm = med3(pm - d, pm, pm + d, cmp, arg);
147159
pn = med3(pn - 2 * d, pn - d, pn, cmp, arg);
@@ -178,18 +190,37 @@ loop:SWAPINIT(a, es);
178190
pc -= es;
179191
}
180192
pn = (char *) a + n * es;
181-
r = Min(pa - (char *) a, pb - pa);
182-
vecswap(a, pb - r, r);
183-
r = Min(pd - pc, pn - pd - es);
184-
vecswap(pb, pn - r, r);
185-
if ((r = pb - pa) > es)
186-
qsort_arg(a, r / es, es, cmp, arg);
187-
if ((r = pd - pc) > es)
193+
d1 = Min(pa - (char *) a, pb - pa);
194+
vecswap(a, pb - d1, d1);
195+
d1 = Min(pd - pc, pn - pd - es);
196+
vecswap(pb, pn - d1, d1);
197+
d1 = pb - pa;
198+
d2 = pd - pc;
199+
if (d1 <= d2)
188200
{
189-
/* Iterate rather than recurse to save stack space */
190-
a = pn - r;
191-
n = r / es;
192-
goto loop;
201+
/* Recurse on left partition, then iterate on right partition */
202+
if (d1 > es)
203+
qsort_arg(a, d1 / es, es, cmp, arg);
204+
if (d2 > es)
205+
{
206+
/* Iterate rather than recurse to save stack space */
207+
/* qsort_arg(pn - d2, d2 / es, es, cmp, arg); */
208+
a = pn - d2;
209+
n = d2 / es;
210+
goto loop;
211+
}
212+
}
213+
else
214+
{
215+
/* Recurse on right partition, then iterate on left partition */
216+
if (d2 > es)
217+
qsort_arg(pn - d2, d2 / es, es, cmp, arg);
218+
if (d1 > es)
219+
{
220+
/* Iterate rather than recurse to save stack space */
221+
/* qsort_arg(a, d1 / es, es, cmp, arg); */
222+
n = d1 / es;
223+
goto loop;
224+
}
193225
}
194-
/* qsort_arg(pn - r, r / es, es, cmp, arg);*/
195226
}

0 commit comments

Comments
 (0)