mirror of
https://github.com/postgres/postgres.git
synced 2026-06-21 06:29:06 -04:00
Fix int32 overflow in ltree_compare()
The expression (len_diff * 10 * (an + 1)) used as the return value of ltree_compare() is computed at int32 width. With LTREE_MAX_LEVELS = 65535, the product can exceed INT32_MAX once an ltree has more than ~14,653 levels, which causes the result to wrap and invert its sign. That corrupts btree ordering as well as the "magnitude" consumed by ltree_penalty() for GiST page splits. To fix, split ltree_compare() into two functions. The new ltree_compare_distance() function returns a float, which won't overflow. It's used by the ltree_penalty() caller. All the other callers only care about the sign of the return value, i.e. which of the arguments is greater, so change ltree_compare() to not multiply the result with (10 * (an + 1)), which avoids the overflow for those callers. Existing btree or GiST indexes on ltree columns containing values with more than ~14,653 levels may be corrupt and should be REINDEXed. Add a regression test based on the reporter's PoC. Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com> Reported-by: 王跃林 <violin0613@tju.edu.cn> Discussion: https://www.postgresql.org/message-id/AI6AnABgKW93Qbx1jVzi84r9.8.1781322625756.Hmail.3020001251%40tju.edu.cn Backpatch-through: 14
This commit is contained in:
parent
54ffa74c99
commit
c3e36a9a5f
5 changed files with 63 additions and 9 deletions
|
|
@ -8224,3 +8224,13 @@ DETAIL: Total size of level exceeds the maximum allowed (65535 bytes).
|
|||
SELECT (repeat('a|', 65535) || 'a')::lquery;
|
||||
ERROR: lquery level has too many variants
|
||||
DETAIL: Number of variants exceeds the maximum allowed (65535).
|
||||
-- Test that ltree_compare() does not overflow with very deep paths.
|
||||
WITH s AS (SELECT 'a'::ltree AS v),
|
||||
l AS (SELECT (repeat('a.', 14999) || 'a')::ltree AS v)
|
||||
SELECT (l.v > s.v) AS gt_ok, (l.v < s.v) AS lt_ok, (l.v = s.v) AS eq_ok
|
||||
FROM s, l;
|
||||
gt_ok | lt_ok | eq_ok
|
||||
-------+-------+-------
|
||||
t | f | f
|
||||
(1 row)
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ bool ltree_execute(ITEM *curitem, void *checkval,
|
|||
bool calcnot, bool (*chkcond) (void *checkval, ITEM *val));
|
||||
|
||||
int ltree_compare(const ltree *a, const ltree *b);
|
||||
float ltree_compare_distance(const ltree *a, const ltree *b);
|
||||
bool inner_isparent(const ltree *c, const ltree *p);
|
||||
bool compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
|
||||
ltree *lca_inner(ltree **a, int len);
|
||||
|
|
|
|||
|
|
@ -264,11 +264,11 @@ ltree_penalty(PG_FUNCTION_ARGS)
|
|||
ltree_gist *newval = (ltree_gist *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
|
||||
float *penalty = (float *) PG_GETARG_POINTER(2);
|
||||
int siglen = LTREE_GET_SIGLEN();
|
||||
int32 cmpr,
|
||||
float cmpr,
|
||||
cmpl;
|
||||
|
||||
cmpl = ltree_compare(LTG_GETLNODE(origval, siglen), LTG_GETLNODE(newval, siglen));
|
||||
cmpr = ltree_compare(LTG_GETRNODE(newval, siglen), LTG_GETRNODE(origval, siglen));
|
||||
cmpl = ltree_compare_distance(LTG_GETLNODE(origval, siglen), LTG_GETLNODE(newval, siglen));
|
||||
cmpr = ltree_compare_distance(LTG_GETRNODE(newval, siglen), LTG_GETRNODE(origval, siglen));
|
||||
|
||||
*penalty = Max(cmpl, 0) + Max(cmpr, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ PG_FUNCTION_INFO_V1(ltree2text);
|
|||
PG_FUNCTION_INFO_V1(text2ltree);
|
||||
PG_FUNCTION_INFO_V1(ltreeparentsel);
|
||||
|
||||
/*
|
||||
* btree-comparison function.
|
||||
*/
|
||||
int
|
||||
ltree_compare(const ltree *a, const ltree *b)
|
||||
{
|
||||
|
|
@ -54,18 +57,52 @@ ltree_compare(const ltree *a, const ltree *b)
|
|||
{
|
||||
int res;
|
||||
|
||||
if ((res = memcmp(al->name, bl->name, Min(al->len, bl->len))) == 0)
|
||||
res = memcmp(al->name, bl->name, Min(al->len, bl->len));
|
||||
if (res == 0)
|
||||
{
|
||||
if (al->len != bl->len)
|
||||
return (al->len - bl->len) * 10 * (an + 1);
|
||||
return (int) al->len - (int) bl->len;
|
||||
}
|
||||
else
|
||||
return res;
|
||||
|
||||
an--;
|
||||
bn--;
|
||||
al = LEVEL_NEXT(al);
|
||||
bl = LEVEL_NEXT(bl);
|
||||
}
|
||||
|
||||
return a->numlevel - b->numlevel;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a "distance" between a and b. If a < b, the distance is negative,
|
||||
* consistent with the ltree_compare() ordering.
|
||||
*/
|
||||
float
|
||||
ltree_compare_distance(const ltree *a, const ltree *b)
|
||||
{
|
||||
ltree_level *al = LTREE_FIRST(a);
|
||||
ltree_level *bl = LTREE_FIRST(b);
|
||||
int an = a->numlevel;
|
||||
int bn = b->numlevel;
|
||||
|
||||
while (an > 0 && bn > 0)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = memcmp(al->name, bl->name, Min(al->len, bl->len));
|
||||
if (res == 0)
|
||||
{
|
||||
if (al->len != bl->len)
|
||||
return (float) (al->len - bl->len) * 10.0 * (an + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res < 0)
|
||||
res = -1;
|
||||
return -1.0 * 10.0 * (an + 1);
|
||||
else
|
||||
res = 1;
|
||||
return res * 10 * (an + 1);
|
||||
return 1.0 * 10.0 * (an + 1);
|
||||
}
|
||||
|
||||
an--;
|
||||
|
|
@ -74,7 +111,7 @@ ltree_compare(const ltree *a, const ltree *b)
|
|||
bl = LEVEL_NEXT(bl);
|
||||
}
|
||||
|
||||
return (a->numlevel - b->numlevel) * 10 * (an + 1);
|
||||
return ((float) (a->numlevel - b->numlevel)) * 10.0 * (an + 1);
|
||||
}
|
||||
|
||||
#define RUNCMP \
|
||||
|
|
|
|||
|
|
@ -476,3 +476,9 @@ SELECT (repeat('x', 255) || repeat('|' || repeat('x', 255), 256))::lquery;
|
|||
--- Test for overflow of lquery_level.numvar, with a set of single-char
|
||||
--- variants in one level.
|
||||
SELECT (repeat('a|', 65535) || 'a')::lquery;
|
||||
|
||||
-- Test that ltree_compare() does not overflow with very deep paths.
|
||||
WITH s AS (SELECT 'a'::ltree AS v),
|
||||
l AS (SELECT (repeat('a.', 14999) || 'a')::ltree AS v)
|
||||
SELECT (l.v > s.v) AS gt_ok, (l.v < s.v) AS lt_ok, (l.v = s.v) AS eq_ok
|
||||
FROM s, l;
|
||||
|
|
|
|||
Loading…
Reference in a new issue