Don't leak malloc'd strings when a GUC setting is rejected.
authorTom Lane
Fri, 19 Mar 2021 02:09:41 +0000 (22:09 -0400)
committerTom Lane
Fri, 19 Mar 2021 02:09:41 +0000 (22:09 -0400)
Because guc.c prefers to keep all its string values in malloc'd
not palloc'd storage, it has to be more careful than usual to
avoid leaks.  Error exits out of string GUC hook checks failed
to clear the proposed value string, and error exits out of
ProcessGUCArray() failed to clear the malloc'd results of
ParseLongOption().

Found via valgrind testing.
This problem is ancient, so back-patch to all supported branches.

Discussion: https://postgr.es/m/3816764.1616104288@sss.pgh.pa.us

src/backend/utils/misc/guc.c

index 974235d7057fb2248575e44e46bbfd2d62ce8aac..218ca77c98f73e6e6290283548d838f9ed2d5375 100644 (file)
@@ -10591,6 +10591,8 @@ ProcessGUCArray(ArrayType *array,
        char       *s;
        char       *name;
        char       *value;
+       char       *namecopy;
+       char       *valuecopy;
 
        d = array_ref(array, 1, &i,
                      -1 /* varlenarray */ ,
@@ -10615,13 +10617,18 @@ ProcessGUCArray(ArrayType *array,
            continue;
        }
 
-       (void) set_config_option(name, value,
+       /* free malloc'd strings immediately to avoid leak upon error */
+       namecopy = pstrdup(name);
+       free(name);
+       valuecopy = pstrdup(value);
+       free(value);
+
+       (void) set_config_option(namecopy, valuecopy,
                                 context, source,
                                 action, true, 0, false);
 
-       free(name);
-       if (value)
-           free(value);
+       pfree(namecopy);
+       pfree(valuecopy);
        pfree(s);
    }
 }
@@ -11053,34 +11060,50 @@ static bool
 call_string_check_hook(struct config_string *conf, char **newval, void **extra,
                       GucSource source, int elevel)
 {
+   volatile bool result = true;
+
    /* Quick success if no hook */
    if (!conf->check_hook)
        return true;
 
-   /* Reset variables that might be set by hook */
-   GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE;
-   GUC_check_errmsg_string = NULL;
-   GUC_check_errdetail_string = NULL;
-   GUC_check_errhint_string = NULL;
+   /*
+    * If elevel is ERROR, or if the check_hook itself throws an elog
+    * (undesirable, but not always avoidable), make sure we don't leak the
+    * already-malloc'd newval string.
+    */
+   PG_TRY();
+   {
+       /* Reset variables that might be set by hook */
+       GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE;
+       GUC_check_errmsg_string = NULL;
+       GUC_check_errdetail_string = NULL;
+       GUC_check_errhint_string = NULL;
 
-   if (!conf->check_hook(newval, extra, source))
+       if (!conf->check_hook(newval, extra, source))
+       {
+           ereport(elevel,
+                   (errcode(GUC_check_errcode_value),
+                    GUC_check_errmsg_string ?
+                    errmsg_internal("%s", GUC_check_errmsg_string) :
+                    errmsg("invalid value for parameter \"%s\": \"%s\"",
+                           conf->gen.name, *newval ? *newval : ""),
+                    GUC_check_errdetail_string ?
+                    errdetail_internal("%s", GUC_check_errdetail_string) : 0,
+                    GUC_check_errhint_string ?
+                    errhint("%s", GUC_check_errhint_string) : 0));
+           /* Flush any strings created in ErrorContext */
+           FlushErrorState();
+           result = false;
+       }
+   }
+   PG_CATCH();
    {
-       ereport(elevel,
-               (errcode(GUC_check_errcode_value),
-                GUC_check_errmsg_string ?
-                errmsg_internal("%s", GUC_check_errmsg_string) :
-                errmsg("invalid value for parameter \"%s\": \"%s\"",
-                       conf->gen.name, *newval ? *newval : ""),
-                GUC_check_errdetail_string ?
-                errdetail_internal("%s", GUC_check_errdetail_string) : 0,
-                GUC_check_errhint_string ?
-                errhint("%s", GUC_check_errhint_string) : 0));
-       /* Flush any strings created in ErrorContext */
-       FlushErrorState();
-       return false;
+       free(*newval);
+       PG_RE_THROW();
    }
+   PG_END_TRY();
 
-   return true;
+   return result;
 }
 
 static bool