mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Avoid overflow in Levenshtein distance calculations.
levenshtein() and levenshtein_less_equal() let the caller specify the insertion, deletion, and substitution costs, and fuzzystrmatch's corresponding SQL functions accept any 32-bit integer for each. Since the distances are calculated with 32-bit arithmetic, large costs can cause overflows, thereby producing nonsensical results. Certain inputs to levenshtein_less_equal() can even cause out-of-bounds writes. To fix, use 64-bit arithmetic instead, and error whenever the final result won't fit in the returned 32-bit integer. We may want to teach these functions to reject negative costs, too, but that didn't seem appropriate for a security fix, and therefore it is left as a future exercise. Reported-by: Ben Morris in collaboration with Claude and Anthropic Research Author: Nathan Bossart <nathandbossart@gmail.com> Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com> Security: CVE-2026-15742 Backpatch-through: 14
This commit is contained in:
committed by
Noah Misch
parent
64a65ead12
commit
62c31b490d
@@ -41,6 +41,14 @@ SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT levenshtein('GUMBO', 'GAMBOL', 1, 1, 2000000000);
|
||||
levenshtein
|
||||
-------------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT levenshtein('GUMBO', 'GAMBOL', 2000000000, 2000000000, 2000000000);
|
||||
ERROR: levenshtein distance out of range
|
||||
SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
|
||||
levenshtein_less_equal
|
||||
------------------------
|
||||
@@ -53,6 +61,12 @@ SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
|
||||
4
|
||||
(1 row)
|
||||
|
||||
SELECT levenshtein_less_equal('aaa', 'aaaaa', 1073741824, 0, 1073741824, 10);
|
||||
levenshtein_less_equal
|
||||
------------------------
|
||||
11
|
||||
(1 row)
|
||||
|
||||
SELECT metaphone('GUMBO', 4);
|
||||
metaphone
|
||||
-----------
|
||||
|
||||
@@ -11,8 +11,11 @@ SELECT soundex(''), difference('', '');
|
||||
|
||||
SELECT levenshtein('GUMBO', 'GAMBOL');
|
||||
SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1);
|
||||
SELECT levenshtein('GUMBO', 'GAMBOL', 1, 1, 2000000000);
|
||||
SELECT levenshtein('GUMBO', 'GAMBOL', 2000000000, 2000000000, 2000000000);
|
||||
SELECT levenshtein_less_equal('extensive', 'exhaustive', 2);
|
||||
SELECT levenshtein_less_equal('extensive', 'exhaustive', 4);
|
||||
SELECT levenshtein_less_equal('aaa', 'aaaaa', 1073741824, 0, 1073741824, 10);
|
||||
|
||||
|
||||
SELECT metaphone('GUMBO', 4);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* Levenshtein distance with custom costings, and (2) Levenshtein distance with
|
||||
* custom costings and a "max" value above which exact distances are not
|
||||
* interesting. Before the inclusion, we rely on the presence of the inline
|
||||
* function rest_of_char_same().
|
||||
* functions rest_of_char_same() and levenshtein_result().
|
||||
*
|
||||
* Written based on a description of the algorithm by Michael Gilleland found
|
||||
* at http://www.merriampark.com/ld.htm. Also looked at levenshtein.c in the
|
||||
@@ -78,13 +78,16 @@ varstr_levenshtein(const char *source, int slen,
|
||||
{
|
||||
int m,
|
||||
n;
|
||||
int *prev;
|
||||
int *curr;
|
||||
int64 *prev;
|
||||
int64 *curr;
|
||||
int *s_char_len = NULL;
|
||||
int j;
|
||||
const char *y;
|
||||
const char *send = source + slen;
|
||||
const char *tend = target + tlen;
|
||||
int64 ins_c_64 = ins_c;
|
||||
int64 del_c_64 = del_c;
|
||||
int64 sub_c_64 = sub_c;
|
||||
|
||||
/*
|
||||
* For varstr_levenshtein_less_equal, we have real variables called
|
||||
@@ -115,9 +118,9 @@ varstr_levenshtein(const char *source, int slen,
|
||||
* into an empty s with m deletions.
|
||||
*/
|
||||
if (!m)
|
||||
return n * ins_c;
|
||||
return levenshtein_result(n * ins_c_64);
|
||||
if (!n)
|
||||
return m * del_c;
|
||||
return levenshtein_result(m * del_c_64);
|
||||
|
||||
/*
|
||||
* For security concerns, restrict excessive CPU+RAM usage. (This
|
||||
@@ -147,20 +150,20 @@ varstr_levenshtein(const char *source, int slen,
|
||||
*/
|
||||
if (max_d >= 0)
|
||||
{
|
||||
int min_theo_d; /* Theoretical minimum distance. */
|
||||
int max_theo_d; /* Theoretical maximum distance. */
|
||||
int64 min_theo_d; /* Theoretical minimum distance. */
|
||||
int64 max_theo_d; /* Theoretical maximum distance. */
|
||||
int net_inserts = n - m;
|
||||
|
||||
min_theo_d = net_inserts < 0 ?
|
||||
-net_inserts * del_c : net_inserts * ins_c;
|
||||
-net_inserts * del_c_64 : net_inserts * ins_c_64;
|
||||
if (min_theo_d > max_d)
|
||||
return max_d + 1;
|
||||
if (ins_c + del_c < sub_c)
|
||||
sub_c = ins_c + del_c;
|
||||
max_theo_d = min_theo_d + sub_c * Min(m, n);
|
||||
return levenshtein_result((int64) max_d + 1);
|
||||
if (ins_c_64 + del_c_64 < sub_c_64)
|
||||
sub_c_64 = ins_c_64 + del_c_64;
|
||||
max_theo_d = min_theo_d + sub_c_64 * Min(m, n);
|
||||
if (max_d >= max_theo_d)
|
||||
max_d = -1;
|
||||
else if (ins_c + del_c > 0)
|
||||
else if (ins_c_64 + del_c_64 > 0)
|
||||
{
|
||||
/*
|
||||
* Figure out how much of the first row of the notional matrix we
|
||||
@@ -174,12 +177,12 @@ varstr_levenshtein(const char *source, int slen,
|
||||
* column n - m. If we do start further right, the best-case
|
||||
* total cost increases by ins_c + del_c for each move right.
|
||||
*/
|
||||
int slack_d = max_d - min_theo_d;
|
||||
int64 slack_d = max_d - min_theo_d;
|
||||
int best_column = net_inserts < 0 ? -net_inserts : 0;
|
||||
int64 tmp;
|
||||
|
||||
stop_column = best_column + (slack_d / (ins_c + del_c)) + 1;
|
||||
if (stop_column > m)
|
||||
stop_column = m + 1;
|
||||
tmp = best_column + (slack_d / (ins_c_64 + del_c_64)) + 1;
|
||||
stop_column = Min(tmp, m + 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -211,7 +214,7 @@ varstr_levenshtein(const char *source, int slen,
|
||||
++n;
|
||||
|
||||
/* Previous and current rows of notional array. */
|
||||
prev = (int *) palloc(2 * m * sizeof(int));
|
||||
prev = (int64 *) palloc(2 * m * sizeof(int64));
|
||||
curr = prev + m;
|
||||
|
||||
/*
|
||||
@@ -219,12 +222,12 @@ varstr_levenshtein(const char *source, int slen,
|
||||
* t, we must perform i deletions.
|
||||
*/
|
||||
for (int i = START_COLUMN; i < STOP_COLUMN; i++)
|
||||
prev[i] = i * del_c;
|
||||
prev[i] = i * del_c_64;
|
||||
|
||||
/* Loop through rows of the notional array */
|
||||
for (y = target, j = 1; j < n; j++)
|
||||
{
|
||||
int *temp;
|
||||
int64 *temp;
|
||||
const char *x = source;
|
||||
int y_char_len = n != tlen + 1 ? pg_mblen_range(y, tend) : 1;
|
||||
int i;
|
||||
@@ -239,7 +242,7 @@ varstr_levenshtein(const char *source, int slen,
|
||||
*/
|
||||
if (stop_column < m)
|
||||
{
|
||||
prev[stop_column] = max_d + 1;
|
||||
prev[stop_column] = (int64) max_d + 1;
|
||||
++stop_column;
|
||||
}
|
||||
|
||||
@@ -251,13 +254,13 @@ varstr_levenshtein(const char *source, int slen,
|
||||
*/
|
||||
if (start_column == 0)
|
||||
{
|
||||
curr[0] = j * ins_c;
|
||||
curr[0] = j * ins_c_64;
|
||||
i = 1;
|
||||
}
|
||||
else
|
||||
i = start_column;
|
||||
#else
|
||||
curr[0] = j * ins_c;
|
||||
curr[0] = j * ins_c_64;
|
||||
i = 1;
|
||||
#endif
|
||||
|
||||
@@ -272,9 +275,9 @@ varstr_levenshtein(const char *source, int slen,
|
||||
{
|
||||
for (; i < STOP_COLUMN; i++)
|
||||
{
|
||||
int ins;
|
||||
int del;
|
||||
int sub;
|
||||
int64 ins;
|
||||
int64 del;
|
||||
int64 sub;
|
||||
int x_char_len = s_char_len[i - 1];
|
||||
|
||||
/*
|
||||
@@ -286,14 +289,14 @@ varstr_levenshtein(const char *source, int slen,
|
||||
* get past that test, then we compare the lengths and the
|
||||
* remaining bytes.
|
||||
*/
|
||||
ins = prev[i] + ins_c;
|
||||
del = curr[i - 1] + del_c;
|
||||
ins = prev[i] + ins_c_64;
|
||||
del = curr[i - 1] + del_c_64;
|
||||
if (x[x_char_len - 1] == y[y_char_len - 1]
|
||||
&& x_char_len == y_char_len &&
|
||||
(x_char_len == 1 || rest_of_char_same(x, y, x_char_len)))
|
||||
sub = prev[i - 1];
|
||||
else
|
||||
sub = prev[i - 1] + sub_c;
|
||||
sub = prev[i - 1] + sub_c_64;
|
||||
|
||||
/* Take the one with minimum cost. */
|
||||
curr[i] = Min(ins, del);
|
||||
@@ -307,14 +310,14 @@ varstr_levenshtein(const char *source, int slen,
|
||||
{
|
||||
for (; i < STOP_COLUMN; i++)
|
||||
{
|
||||
int ins;
|
||||
int del;
|
||||
int sub;
|
||||
int64 ins;
|
||||
int64 del;
|
||||
int64 sub;
|
||||
|
||||
/* Calculate costs for insertion, deletion, and substitution. */
|
||||
ins = prev[i] + ins_c;
|
||||
del = curr[i - 1] + del_c;
|
||||
sub = prev[i - 1] + ((*x == *y) ? 0 : sub_c);
|
||||
ins = prev[i] + ins_c_64;
|
||||
del = curr[i - 1] + del_c_64;
|
||||
sub = prev[i - 1] + ((*x == *y) ? 0 : sub_c_64);
|
||||
|
||||
/* Take the one with minimum cost. */
|
||||
curr[i] = Min(ins, del);
|
||||
@@ -360,8 +363,8 @@ varstr_levenshtein(const char *source, int slen,
|
||||
int ii = stop_column - 1;
|
||||
int net_inserts = ii - zp;
|
||||
|
||||
if (prev[ii] + (net_inserts > 0 ? net_inserts * ins_c :
|
||||
-net_inserts * del_c) <= max_d)
|
||||
if (prev[ii] + (net_inserts > 0 ? net_inserts * ins_c_64 :
|
||||
-net_inserts * del_c_64) <= max_d)
|
||||
break;
|
||||
stop_column--;
|
||||
}
|
||||
@@ -372,8 +375,8 @@ varstr_levenshtein(const char *source, int slen,
|
||||
int net_inserts = start_column - zp;
|
||||
|
||||
if (prev[start_column] +
|
||||
(net_inserts > 0 ? net_inserts * ins_c :
|
||||
-net_inserts * del_c) <= max_d)
|
||||
(net_inserts > 0 ? net_inserts * ins_c_64 :
|
||||
-net_inserts * del_c_64) <= max_d)
|
||||
break;
|
||||
|
||||
/*
|
||||
@@ -381,8 +384,8 @@ varstr_levenshtein(const char *source, int slen,
|
||||
* there's nothing here that could confuse any future
|
||||
* iteration of the outer loop.
|
||||
*/
|
||||
prev[start_column] = max_d + 1;
|
||||
curr[start_column] = max_d + 1;
|
||||
prev[start_column] = (int64) max_d + 1;
|
||||
curr[start_column] = (int64) max_d + 1;
|
||||
if (start_column != 0)
|
||||
source += (s_char_len != NULL) ? s_char_len[start_column - 1] : 1;
|
||||
start_column++;
|
||||
@@ -390,7 +393,7 @@ varstr_levenshtein(const char *source, int slen,
|
||||
|
||||
/* If they cross, we're going to exceed the bound. */
|
||||
if (start_column >= stop_column)
|
||||
return max_d + 1;
|
||||
return levenshtein_result((int64) max_d + 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -399,5 +402,5 @@ varstr_levenshtein(const char *source, int slen,
|
||||
* Because the final value was swapped from the previous row to the
|
||||
* current row, that's where we'll find it.
|
||||
*/
|
||||
return prev[m - 1];
|
||||
return levenshtein_result(prev[m - 1]);
|
||||
}
|
||||
|
||||
@@ -5306,6 +5306,20 @@ rest_of_char_same(const char *s1, const char *s2, int len)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for checking return value of Levenshtein distance functions.
|
||||
* We calculate it as an int64, but the distance functions return an int32.
|
||||
*/
|
||||
static inline int
|
||||
levenshtein_result(int64 res)
|
||||
{
|
||||
if (unlikely(res < PG_INT32_MIN || res > PG_INT32_MAX))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("levenshtein distance out of range")));
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Expand each Levenshtein distance variant */
|
||||
#include "levenshtein.c"
|
||||
#define LEVENSHTEIN_LESS_EQUAL
|
||||
|
||||
Reference in New Issue
Block a user