Tighten up make_libc_collator() and make_icu_collator().
authorJeff Davis <jdavis@postgresql.org>
Tue, 24 Sep 2024 19:01:45 +0000 (12:01 -0700)
committerJeff Davis <jdavis@postgresql.org>
Tue, 24 Sep 2024 19:01:45 +0000 (12:01 -0700)
Ensure that error paths within these functions do not leak a collator,
and return the result rather than using an out parameter. (Error paths
in the caller may still result in a leaked collator, which will be
addressed separately.)

In make_libc_collator(), if the first newlocale() succeeds and the
second one fails, close the first locale_t object.

The function make_icu_collator() doesn't have any external callers, so
change it to be static.

Discussion: https://postgr.es/m/54d20e812bd6c3e44c10eddcd757ec494ebf1803.camel@j-davis.com

src/backend/utils/adt/pg_locale.c
src/include/utils/pg_locale.h

index 5bef1b113a8b8a4b5b0d87ec671862a89fe3efee..8a7dde21398e78a0f20b72e820295f8ef892256c 100644 (file)
@@ -1297,14 +1297,15 @@ report_newlocale_failure(const char *localename)
 }
 
 /*
- * Initialize the locale_t field.
+ * Create a locale_t with the given collation and ctype.
  *
- * The "C" and "POSIX" locales are not actually handled by libc, so set the
- * locale_t to zero in that case.
+ * The "C" and "POSIX" locales are not actually handled by libc, so return
+ * NULL.
+ *
+ * Ensure that no path leaks a locale_t.
  */
-static void
-make_libc_collator(const char *collate, const char *ctype,
-                                  pg_locale_t result)
+static locale_t
+make_libc_collator(const char *collate, const char *ctype)
 {
        locale_t        loc = 0;
 
@@ -1343,7 +1344,11 @@ make_libc_collator(const char *collate, const char *ctype,
                        errno = 0;
                        loc = newlocale(LC_CTYPE_MASK, ctype, loc1);
                        if (!loc)
+                       {
+                               if (loc1)
+                                       freelocale(loc1);
                                report_newlocale_failure(ctype);
+                       }
                }
                else
                        loc = loc1;
@@ -1360,60 +1365,78 @@ make_libc_collator(const char *collate, const char *ctype,
 #endif
        }
 
-       result->info.lt = loc;
+       return loc;
 }
 
-void
-make_icu_collator(const char *iculocstr,
-                                 const char *icurules,
-                                 struct pg_locale_struct *resultp)
-{
+/*
+ * Create a UCollator with the given locale string and rules.
+ *
+ * Ensure that no path leaks a UCollator.
+ */
 #ifdef USE_ICU
-       UCollator  *collator;
-
-       collator = pg_ucol_open(iculocstr);
-
-       /*
-        * If rules are specified, we extract the rules of the standard collation,
-        * add our own rules, and make a new collator with the combined rules.
-        */
-       if (icurules)
+static UCollator *
+make_icu_collator(const char *iculocstr, const char *icurules)
+{
+       if (!icurules)
+       {
+               /* simple case without rules */
+               return pg_ucol_open(iculocstr);
+       }
+       else
        {
-               const UChar *default_rules;
-               UChar      *agg_rules;
+               UCollator  *collator_std_rules;
+               UCollator  *collator_all_rules;
+               const UChar *std_rules;
                UChar      *my_rules;
-               UErrorCode      status;
+               UChar      *all_rules;
                int32_t         length;
+               int32_t         total;
+               UErrorCode      status;
 
-               default_rules = ucol_getRules(collator, &length);
+               /*
+                * If rules are specified, we extract the rules of the standard
+                * collation, add our own rules, and make a new collator with the
+                * combined rules.
+                */
                icu_to_uchar(&my_rules, icurules, strlen(icurules));
 
-               agg_rules = palloc_array(UChar, u_strlen(default_rules) + u_strlen(my_rules) + 1);
-               u_strcpy(agg_rules, default_rules);
-               u_strcat(agg_rules, my_rules);
+               collator_std_rules = pg_ucol_open(iculocstr);
 
-               ucol_close(collator);
+               std_rules = ucol_getRules(collator_std_rules, &length);
+
+               total = u_strlen(std_rules) + u_strlen(my_rules) + 1;
+
+               /* avoid leaking collator on OOM */
+               all_rules = palloc_extended(sizeof(UChar) * total, MCXT_ALLOC_NO_OOM);
+               if (!all_rules)
+               {
+                       ucol_close(collator_std_rules);
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OUT_OF_MEMORY),
+                                        errmsg("out of memory")));
+               }
+
+               u_strcpy(all_rules, std_rules);
+               u_strcat(all_rules, my_rules);
+
+               ucol_close(collator_std_rules);
 
                status = U_ZERO_ERROR;
-               collator = ucol_openRules(agg_rules, u_strlen(agg_rules),
-                                                                 UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH, NULL, &status);
+               collator_all_rules = ucol_openRules(all_rules, u_strlen(all_rules),
+                                                                                       UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH,
+                                                                                       NULL, &status);
                if (U_FAILURE(status))
+               {
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                         errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s",
                                                        iculocstr, icurules, u_errorName(status))));
-       }
+               }
 
-       /* We will leak this string if the caller errors later :-( */
-       resultp->info.icu.locale = MemoryContextStrdup(TopMemoryContext, iculocstr);
-       resultp->info.icu.ucol = collator;
-#else                                                  /* not USE_ICU */
-       /* could get here if a collation was created by a build with ICU */
-       ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("ICU is not supported in this build")));
-#endif                                                 /* not USE_ICU */
+               return collator_all_rules;
+       }
 }
+#endif                                                 /* not USE_ICU */
 
 /*
  * Initialize default_locale with database locale settings.
@@ -1424,7 +1447,6 @@ init_database_collation(void)
        HeapTuple       tup;
        Form_pg_database dbform;
        Datum           datum;
-       bool            isnull;
 
        /* Fetch our pg_database row normally, via syscache */
        tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
@@ -1449,8 +1471,10 @@ init_database_collation(void)
        }
        else if (dbform->datlocprovider == COLLPROVIDER_ICU)
        {
+#ifdef USE_ICU
                char       *datlocale;
                char       *icurules;
+               bool            isnull;
 
                datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datlocale);
                datlocale = TextDatumGetCString(datum);
@@ -1464,15 +1488,20 @@ init_database_collation(void)
                else
                        icurules = NULL;
 
-               make_icu_collator(datlocale, icurules, &default_locale);
+               default_locale.info.icu.locale = MemoryContextStrdup(TopMemoryContext, datlocale);
+               default_locale.info.icu.ucol = make_icu_collator(datlocale, icurules);
+#else
+               /* could get here if a collation was created by a build with ICU */
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("ICU is not supported in this build")));
+#endif
        }
-       else
+       else if (dbform->datlocprovider == COLLPROVIDER_LIBC)
        {
                const char *datcollate;
                const char *datctype;
 
-               Assert(dbform->datlocprovider == COLLPROVIDER_LIBC);
-
                datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datcollate);
                datcollate = TextDatumGetCString(datum);
                datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype);
@@ -1483,8 +1512,12 @@ init_database_collation(void)
                default_locale.ctype_is_c = (strcmp(datctype, "C") == 0) ||
                        (strcmp(datctype, "POSIX") == 0);
 
-               make_libc_collator(datcollate, datctype, &default_locale);
+               default_locale.info.lt = make_libc_collator(datcollate, datctype);
        }
+       else
+               /* shouldn't happen */
+               PGLOCALE_SUPPORT_ERROR(dbform->datlocprovider);
+
 
        default_locale.provider = dbform->datlocprovider;
 
@@ -1557,25 +1590,9 @@ pg_newlocale_from_collation(Oid collid)
                        result.info.builtin.locale = MemoryContextStrdup(TopMemoryContext,
                                                                                                                         locstr);
                }
-               else if (collform->collprovider == COLLPROVIDER_LIBC)
-               {
-                       const char *collcollate;
-                       const char *collctype;
-
-                       datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate);
-                       collcollate = TextDatumGetCString(datum);
-                       datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype);
-                       collctype = TextDatumGetCString(datum);
-
-                       result.collate_is_c = (strcmp(collcollate, "C") == 0) ||
-                               (strcmp(collcollate, "POSIX") == 0);
-                       result.ctype_is_c = (strcmp(collctype, "C") == 0) ||
-                               (strcmp(collctype, "POSIX") == 0);
-
-                       make_libc_collator(collcollate, collctype, &result);
-               }
                else if (collform->collprovider == COLLPROVIDER_ICU)
                {
+#ifdef USE_ICU
                        const char *iculocstr;
                        const char *icurules;
 
@@ -1591,8 +1608,35 @@ pg_newlocale_from_collation(Oid collid)
                        else
                                icurules = NULL;
 
-                       make_icu_collator(iculocstr, icurules, &result);
+                       result.info.icu.locale = MemoryContextStrdup(TopMemoryContext, iculocstr);
+                       result.info.icu.ucol = make_icu_collator(iculocstr, icurules);
+#else
+                       /* could get here if a collation was created by a build with ICU */
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("ICU is not supported in this build")));
+#endif
                }
+               else if (collform->collprovider == COLLPROVIDER_LIBC)
+               {
+                       const char *collcollate;
+                       const char *collctype;
+
+                       datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate);
+                       collcollate = TextDatumGetCString(datum);
+                       datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype);
+                       collctype = TextDatumGetCString(datum);
+
+                       result.collate_is_c = (strcmp(collcollate, "C") == 0) ||
+                               (strcmp(collcollate, "POSIX") == 0);
+                       result.ctype_is_c = (strcmp(collctype, "C") == 0) ||
+                               (strcmp(collctype, "POSIX") == 0);
+
+                       result.info.lt = make_libc_collator(collcollate, collctype);
+               }
+               else
+                       /* shouldn't happen */
+                       PGLOCALE_SUPPORT_ERROR(collform->collprovider);
 
                datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion,
                                                                &isnull);
@@ -2500,6 +2544,8 @@ builtin_validate_locale(int encoding, const char *locale)
 /*
  * Wrapper around ucol_open() to handle API differences for older ICU
  * versions.
+ *
+ * Ensure that no path leaks a UCollator.
  */
 static UCollator *
 pg_ucol_open(const char *loc_str)
index faae868bfccff7b24518d646748b593dfbbcb462..c2d95411e0a53eaf75852ec939ac8df61cc2f159 100644 (file)
@@ -104,10 +104,6 @@ struct pg_locale_struct
 
 typedef struct pg_locale_struct *pg_locale_t;
 
-extern void make_icu_collator(const char *iculocstr,
-                                                         const char *icurules,
-                                                         struct pg_locale_struct *resultp);
-
 extern void init_database_collation(void);
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);