postgres.git / summary / log / commit / refs

commit    b3510700e994a525958dc98a33d320786312d19f
Author:   Jeff Davis <jdavis@postgresql.org>
Date:     Thu Aug 20 20:54:36 2026 +0000
First released in: 16.16, 17.12, 18.7

    hashtext: fix fragile code.
    
    Previously, in the path for non-deterministic collations, the code
    assumed that bsize==rsize. That assumption seems to be true for ICU,
    and all non-deterministic collations are ICU, so it's not known to be
    an actual bug.
    
    The only known place where bsize may not equal rsize is in the libc
    provider, where strxfrm() can return an upper bound of the size needed
    to store the result. That means the initial call to determine the
    buffer size (with dest==NULL, n==0) could return a larger number than
    the actual call with an adequate dest buffer. That's OK, because libc
    locales are always deterministic.
    
    Commit 679c5084cf2 partially fixed the assumption, but missed this
    part. Fix it, and add a more prominent documentation note.
    
    Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
    Discussion: https://postgr.es/m/CABXr29Hb31nkj1g2Jmk+1BhAm=3ecGs_pWy4tU++j8CQBnbMxQ@mail.gmail.com
    Backpatch-through: 16


src/backend/access/hash/hashfunc.c | 4 ++-- src/backend/utils/adt/pg_locale.c | 8 +++++--- src/backend/utils/adt/pg_locale_libc.c | 5 +++++ src/backend/utils/adt/varchar.c | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c index adab96b9f13..ed272e6f01e 100644 --- a/src/backend/access/hash/hashfunc.c +++ b/src/backend/access/hash/hashfunc.c @@ -310,7 +310,7 @@ hashtext(PG_FUNCTION_ARGS) * character in the hash, but it was done before and the behavior must * be preserved. */ - result = hash_any((uint8_t *) buf, bsize + 1); + result = hash_any((uint8_t *) buf, rsize + 1); pfree(buf); } @@ -365,7 +365,7 @@ hashtextextended(PG_FUNCTION_ARGS) * character in the hash, but it was done before and the behavior must * be preserved. */ - result = hash_any_extended((uint8_t *) buf, bsize + 1, + result = hash_any_extended((uint8_t *) buf, rsize + 1, PG_GETARG_INT64(1)); pfree(buf); diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index ebf38647f95..b6f3917c67d 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -1566,9 +1566,11 @@ pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale) * pg_strxfrm_enabled() first, otherwise this function may return wrong * results or an error. * - * Returns the number of bytes needed (or more) to store the transformed - * string, excluding the terminating nul byte. If the value returned is - * 'destsize' or greater, the resulting contents of 'dest' are undefined. + * Returns the number of bytes needed (NB: or more; see comments above + * strnxfrm_libc()) to store the transformed string, excluding the terminating + * nul byte. If the value returned is 'destsize' or greater, the resulting + * contents of 'dest' are undefined, and the caller should use the return + * value to resize the buffer. */ size_t pg_strnxfrm(char *dest, size_t destsize, const char *src, ssize_t srclen, diff --git a/src/backend/utils/adt/pg_locale_libc.c b/src/backend/utils/adt/pg_locale_libc.c index 31274f069d2..13f22b5a5d1 100644 --- a/src/backend/utils/adt/pg_locale_libc.c +++ b/src/backend/utils/adt/pg_locale_libc.c @@ -622,6 +622,11 @@ strncoll_libc(const char *arg1, ssize_t len1, const char *arg2, ssize_t len2, * NUL-terminate src, if necessary, and pass to strxfrm_l(). * * A source length of -1 means that it's already NUL-terminated. + * + * NB: it's possible for this function to return a different size needed for + * two calls with the same input string. If destsize is too small to hold the + * result, strxfrm() may return the upper bound of the size needed rather than + * the exact size needed. */ size_t strnxfrm_libc(char *dest, size_t destsize, const char *src, ssize_t srclen, diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c index 3f40c9da1a0..38620260338 100644 --- a/src/backend/utils/adt/varchar.c +++ b/src/backend/utils/adt/varchar.c @@ -1029,7 +1029,7 @@ hashbpchar(PG_FUNCTION_ARGS) * character in the hash, but it was done before and the behavior must * be preserved. */ - result = hash_any((uint8_t *) buf, bsize + 1); + result = hash_any((uint8_t *) buf, rsize + 1); pfree(buf); } @@ -1086,7 +1086,7 @@ hashbpcharextended(PG_FUNCTION_ARGS) * character in the hash, but it was done before and the behavior must * be preserved. */ - result = hash_any_extended((uint8_t *) buf, bsize + 1, + result = hash_any_extended((uint8_t *) buf, rsize + 1, PG_GETARG_INT64(1)); pfree(buf); [parent: 47c350c522e3]