Fix memory leak when guc.c decides a setting can't be applied now.
authorTom Lane <[email protected]>
Mon, 12 Oct 2020 17:31:24 +0000 (13:31 -0400)
committerTom Lane <[email protected]>
Mon, 12 Oct 2020 17:31:24 +0000 (13:31 -0400)
The prohibitValueChange code paths in set_config_option(), which
are executed whenever we re-read a PGC_POSTMASTER variable from
postgresql.conf, neglected to free anything before exiting.  Thus
we'd leak the proposed new value of a PGC_STRING variable, as noted
by BoChen in bug #16666.  For all variable types, if the check hook
creates an "extra" chunk, we'd also leak that.

These are malloc not palloc chunks, so there is no mechanism for
recovering the leaks before process exit.  Fortunately, the values
are typically not very large, meaning you'd have to go through an
awful lot of SIGHUP configuration-reload cycles to make the leakage
amount to anything.  Still, for a long-lived postmaster process it
could potentially be a problem.

Oversight in commit 2594cf0e8.  Back-patch to all supported branches.

Discussion: https://postgr.es/m/16666-2c41a4eec61b03e1@postgresql.org

src/backend/utils/misc/guc.c

index 596bcb7b842ea3e84fbb9c59ab075ce288b9a93e..a62d64eaa4794a3c8b139558ba54a7f7746964cc 100644 (file)
@@ -7222,6 +7222,10 @@ set_config_option(const char *name, const char *value,
 
                if (prohibitValueChange)
                {
+                   /* Release newextra, unless it's reset_extra */
+                   if (newextra && !extra_field_used(&conf->gen, newextra))
+                       free(newextra);
+
                    if (*conf->variable != newval)
                    {
                        record->status |= GUC_PENDING_RESTART;
@@ -7312,6 +7316,10 @@ set_config_option(const char *name, const char *value,
 
                if (prohibitValueChange)
                {
+                   /* Release newextra, unless it's reset_extra */
+                   if (newextra && !extra_field_used(&conf->gen, newextra))
+                       free(newextra);
+
                    if (*conf->variable != newval)
                    {
                        record->status |= GUC_PENDING_RESTART;
@@ -7402,6 +7410,10 @@ set_config_option(const char *name, const char *value,
 
                if (prohibitValueChange)
                {
+                   /* Release newextra, unless it's reset_extra */
+                   if (newextra && !extra_field_used(&conf->gen, newextra))
+                       free(newextra);
+
                    if (*conf->variable != newval)
                    {
                        record->status |= GUC_PENDING_RESTART;
@@ -7508,9 +7520,21 @@ set_config_option(const char *name, const char *value,
 
                if (prohibitValueChange)
                {
+                   bool        newval_different;
+
                    /* newval shouldn't be NULL, so we're a bit sloppy here */
-                   if (*conf->variable == NULL || newval == NULL ||
-                       strcmp(*conf->variable, newval) != 0)
+                   newval_different = (*conf->variable == NULL ||
+                                       newval == NULL ||
+                                       strcmp(*conf->variable, newval) != 0);
+
+                   /* Release newval, unless it's reset_val */
+                   if (newval && !string_field_used(conf, newval))
+                       free(newval);
+                   /* Release newextra, unless it's reset_extra */
+                   if (newextra && !extra_field_used(&conf->gen, newextra))
+                       free(newextra);
+
+                   if (newval_different)
                    {
                        record->status |= GUC_PENDING_RESTART;
                        ereport(elevel,
@@ -7605,6 +7629,10 @@ set_config_option(const char *name, const char *value,
 
                if (prohibitValueChange)
                {
+                   /* Release newextra, unless it's reset_extra */
+                   if (newextra && !extra_field_used(&conf->gen, newextra))
+                       free(newextra);
+
                    if (*conf->variable != newval)
                    {
                        record->status |= GUC_PENDING_RESTART;