ICU: check for U_STRING_NOT_TERMINATED_WARNING.
authorJeff Davis <jdavis@postgresql.org>
Wed, 17 May 2023 20:43:41 +0000 (13:43 -0700)
committerJeff Davis <jdavis@postgresql.org>
Wed, 17 May 2023 21:18:45 +0000 (14:18 -0700)
Fixes memory error in cases where the length of the language name
returned by uloc_getLanguage() is exactly ULOC_LANG_CAPACITY, in which
case the status is set to U_STRING_NOT_TERMINATED_WARNING.

Also check in call sites for other ICU functions that are expected to
return a C string to be safe (no bug is known at these other call
sites).

Reported-by: Alexander Lakhin
Discussion: https://postgr.es/m/2098874d-c111-41e4-9063-30bcf135226b@gmail.com

src/backend/utils/adt/pg_locale.c
src/bin/initdb/initdb.c

index de678da6033693cd6dea37998feb83bb84a2304a..eea1d1ae0ff8ad94db67c1087e7f9004cc63af15 100644 (file)
@@ -2468,7 +2468,7 @@ pg_ucol_open(const char *loc_str)
 
        status = U_ZERO_ERROR;
        uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status);
-       if (U_FAILURE(status))
+       if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
        {
            ereport(ERROR,
                    (errmsg("could not get language from locale \"%s\": %s",
@@ -2504,7 +2504,7 @@ pg_ucol_open(const char *loc_str)
         * Pretend the error came from ucol_open(), for consistent error
         * message across ICU versions.
         */
-       if (U_FAILURE(status))
+       if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
        {
            ucol_close(collator);
            ereport(ERROR,
@@ -2639,7 +2639,8 @@ icu_from_uchar(char **result, const UChar *buff_uchar, int32_t len_uchar)
    status = U_ZERO_ERROR;
    len_result = ucnv_fromUChars(icu_converter, *result, len_result + 1,
                                 buff_uchar, len_uchar, &status);
-   if (U_FAILURE(status))
+   if (U_FAILURE(status) ||
+       status == U_STRING_NOT_TERMINATED_WARNING)
        ereport(ERROR,
                (errmsg("%s failed: %s", "ucnv_fromUChars",
                        u_errorName(status))));
@@ -2681,7 +2682,7 @@ icu_set_collation_attributes(UCollator *collator, const char *loc,
    icu_locale_id = palloc(len + 1);
    *status = U_ZERO_ERROR;
    len = uloc_canonicalize(loc, icu_locale_id, len + 1, status);
-   if (U_FAILURE(*status))
+   if (U_FAILURE(*status) || *status == U_STRING_NOT_TERMINATED_WARNING)
        return;
 
    lower_str = asc_tolower(icu_locale_id, strlen(icu_locale_id));
@@ -2765,7 +2766,6 @@ icu_set_collation_attributes(UCollator *collator, const char *loc,
 
    pfree(lower_str);
 }
-
 #endif
 
 /*
@@ -2789,7 +2789,7 @@ icu_language_tag(const char *loc_str, int elevel)
 
    status = U_ZERO_ERROR;
    uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status);
-   if (U_FAILURE(status))
+   if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
    {
        if (elevel > 0)
            ereport(elevel,
@@ -2811,19 +2811,12 @@ icu_language_tag(const char *loc_str, int elevel)
    langtag = palloc(buflen);
    while (true)
    {
-       int32_t     len;
-
        status = U_ZERO_ERROR;
-       len = uloc_toLanguageTag(loc_str, langtag, buflen, strict, &status);
+       uloc_toLanguageTag(loc_str, langtag, buflen, strict, &status);
 
-       /*
-        * If the result fits in the buffer exactly (len == buflen),
-        * uloc_toLanguageTag() will return success without nul-terminating
-        * the result. Check for either U_BUFFER_OVERFLOW_ERROR or len >=
-        * buflen and try again.
-        */
+       /* try again if the buffer is not large enough */
        if ((status == U_BUFFER_OVERFLOW_ERROR ||
-            (U_SUCCESS(status) && len >= buflen)) &&
+            status == U_STRING_NOT_TERMINATED_WARNING) &&
            buflen < MaxAllocSize)
        {
            buflen = Min(buflen * 2, MaxAllocSize);
@@ -2878,7 +2871,7 @@ icu_validate_locale(const char *loc_str)
    /* validate that we can extract the language */
    status = U_ZERO_ERROR;
    uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status);
-   if (U_FAILURE(status))
+   if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
    {
        ereport(elevel,
                (errmsg("could not get language from ICU locale \"%s\": %s",
@@ -2901,7 +2894,7 @@ icu_validate_locale(const char *loc_str)
 
        status = U_ZERO_ERROR;
        uloc_getLanguage(otherloc, otherlang, ULOC_LANG_CAPACITY, &status);
-       if (U_FAILURE(status))
+       if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
            continue;
 
        if (strcmp(lang, otherlang) == 0)
index e03d498b1e073e4447b7108b4ee28c5b526ab8df..30b576932fd049e8f05aee667cef8eda8b51cd72 100644 (file)
@@ -2252,7 +2252,7 @@ icu_language_tag(const char *loc_str)
 
    status = U_ZERO_ERROR;
    uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status);
-   if (U_FAILURE(status))
+   if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING)
    {
        pg_fatal("could not get language from locale \"%s\": %s",
                 loc_str, u_errorName(status));
@@ -2272,19 +2272,12 @@ icu_language_tag(const char *loc_str)
    langtag = pg_malloc(buflen);
    while (true)
    {
-       int32_t     len;
-
        status = U_ZERO_ERROR;
-       len = uloc_toLanguageTag(loc_str, langtag, buflen, strict, &status);
+       uloc_toLanguageTag(loc_str, langtag, buflen, strict, &status);
 
-       /*
-        * If the result fits in the buffer exactly (len == buflen),
-        * uloc_toLanguageTag() will return success without nul-terminating
-        * the result. Check for either U_BUFFER_OVERFLOW_ERROR or len >=
-        * buflen and try again.
-        */
+       /* try again if the buffer is not large enough */
        if (status == U_BUFFER_OVERFLOW_ERROR ||
-           (U_SUCCESS(status) && len >= buflen))
+           status == U_STRING_NOT_TERMINATED_WARNING)
        {
            buflen = buflen * 2;
            langtag = pg_realloc(langtag, buflen);