From 7082ce248a52d02507c69aa2eadafdd34552a059 Mon Sep 17 00:00:00 2001 From: Jacob Champion Date: Mon, 10 Aug 2026 06:38:05 -0700 Subject: [PATCH] Move SplitGUCList() to fe_utils This is needed so that all versions of pg_upgrade that migrate logical replication slots can parse the new output_plugin_libraries GUC. Backpatch-through: 17 Security: CVE-2026-6471 --- src/backend/utils/adt/varlena.c | 2 +- src/bin/pg_dump/dumputils.c | 109 --------------------------- src/bin/pg_dump/dumputils.h | 3 - src/fe_utils/string_utils.c | 110 ++++++++++++++++++++++++++++ src/include/fe_utils/string_utils.h | 2 + 5 files changed, 113 insertions(+), 113 deletions(-) diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index 0c6d3ba4d22..e9548bed2ce 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -3044,7 +3044,7 @@ SplitDirectoriesString(char *rawstring, char separator, * However, it's not clear that having one function with a bunch of option * flags would be much better. * - * XXX there is a version of this function in src/bin/pg_dump/dumputils.c. + * XXX there is a version of this function in src/fe_utils/string_utils.c. * Be sure to update that if you have to change this. * * Inputs: diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index dfb1f603a43..0d9f817875d 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -744,115 +744,6 @@ variable_is_guc_list_quote(const char *name) return false; } -/* - * SplitGUCList --- parse a string containing identifiers or file names - * - * This is used to split the value of a GUC_LIST_QUOTE GUC variable, without - * presuming whether the elements will be taken as identifiers or file names. - * See comparable code in src/backend/utils/adt/varlena.c. - * - * Inputs: - * rawstring: the input string; must be overwritable! On return, it's - * been modified to contain the separated identifiers. - * separator: the separator punctuation expected between identifiers - * (typically '.' or ','). Whitespace may also appear around - * identifiers. - * Outputs: - * namelist: receives a malloc'd, null-terminated array of pointers to - * identifiers within rawstring. Caller should free this - * even on error return. - * - * Returns true if okay, false if there is a syntax error in the string. - */ -bool -SplitGUCList(char *rawstring, char separator, - char ***namelist) -{ - char *nextp = rawstring; - bool done = false; - char **nextptr; - - /* - * Since we disallow empty identifiers, this is a conservative - * overestimate of the number of pointers we could need. Allow one for - * list terminator. - */ - *namelist = nextptr = - pg_malloc_array(char *, (strlen(rawstring) / 2 + 2)); - *nextptr = NULL; - - while (isspace((unsigned char) *nextp)) - nextp++; /* skip leading whitespace */ - - if (*nextp == '\0') - return true; /* empty string represents empty list */ - - /* At the top of the loop, we are at start of a new identifier. */ - do - { - char *curname; - char *endp; - - if (*nextp == '"') - { - /* Quoted name --- collapse quote-quote pairs */ - curname = nextp + 1; - for (;;) - { - endp = strchr(nextp + 1, '"'); - if (endp == NULL) - return false; /* mismatched quotes */ - if (endp[1] != '"') - break; /* found end of quoted name */ - /* Collapse adjacent quotes into one quote, and look again */ - memmove(endp, endp + 1, strlen(endp)); - nextp = endp; - } - /* endp now points at the terminating quote */ - nextp = endp + 1; - } - else - { - /* Unquoted name --- extends to separator or whitespace */ - curname = nextp; - while (*nextp && *nextp != separator && - !isspace((unsigned char) *nextp)) - nextp++; - endp = nextp; - if (curname == nextp) - return false; /* empty unquoted name not allowed */ - } - - while (isspace((unsigned char) *nextp)) - nextp++; /* skip trailing whitespace */ - - if (*nextp == separator) - { - nextp++; - while (isspace((unsigned char) *nextp)) - nextp++; /* skip leading whitespace for next */ - /* we expect another name, so done remains false */ - } - else if (*nextp == '\0') - done = true; - else - return false; /* invalid syntax */ - - /* Now safe to overwrite separator with a null */ - *endp = '\0'; - - /* - * Finished isolating current name --- add it to output array - */ - *nextptr++ = curname; - - /* Loop back if we didn't reach end of string */ - } while (!done); - - *nextptr = NULL; - return true; -} - /* * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands. * diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index d231ce1d654..de84bea38be 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -57,9 +57,6 @@ extern void emitShSecLabels(PGconn *conn, PGresult *res, extern bool variable_is_guc_list_quote(const char *name); -extern bool SplitGUCList(char *rawstring, char separator, - char ***namelist); - extern void makeAlterConfigCommand(PGconn *conn, const char *configitem, const char *type, const char *name, const char *type2, const char *name2, diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c index 7a762251f32..c6d8d939836 100644 --- a/src/fe_utils/string_utils.c +++ b/src/fe_utils/string_utils.c @@ -803,6 +803,116 @@ appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname) } +/* + * SplitGUCList --- parse a string containing identifiers or file names + * + * This is used to split the value of a GUC_LIST_QUOTE GUC variable, without + * presuming whether the elements will be taken as identifiers or file names. + * See comparable code in src/backend/utils/adt/varlena.c. + * + * Inputs: + * rawstring: the input string; must be overwritable! On return, it's + * been modified to contain the separated identifiers. + * separator: the separator punctuation expected between identifiers + * (typically '.' or ','). Whitespace may also appear around + * identifiers. + * Outputs: + * namelist: receives a malloc'd, null-terminated array of pointers to + * identifiers within rawstring. Caller should free this + * even on error return. + * + * Returns true if okay, false if there is a syntax error in the string. + */ +bool +SplitGUCList(char *rawstring, char separator, + char ***namelist) +{ + char *nextp = rawstring; + bool done = false; + char **nextptr; + + /* + * Since we disallow empty identifiers, this is a conservative + * overestimate of the number of pointers we could need. Allow one for + * list terminator. + */ + *namelist = nextptr = + pg_malloc_array(char *, (strlen(rawstring) / 2 + 2)); + *nextptr = NULL; + + while (isspace((unsigned char) *nextp)) + nextp++; /* skip leading whitespace */ + + if (*nextp == '\0') + return true; /* empty string represents empty list */ + + /* At the top of the loop, we are at start of a new identifier. */ + do + { + char *curname; + char *endp; + + if (*nextp == '"') + { + /* Quoted name --- collapse quote-quote pairs */ + curname = nextp + 1; + for (;;) + { + endp = strchr(nextp + 1, '"'); + if (endp == NULL) + return false; /* mismatched quotes */ + if (endp[1] != '"') + break; /* found end of quoted name */ + /* Collapse adjacent quotes into one quote, and look again */ + memmove(endp, endp + 1, strlen(endp)); + nextp = endp; + } + /* endp now points at the terminating quote */ + nextp = endp + 1; + } + else + { + /* Unquoted name --- extends to separator or whitespace */ + curname = nextp; + while (*nextp && *nextp != separator && + !isspace((unsigned char) *nextp)) + nextp++; + endp = nextp; + if (curname == nextp) + return false; /* empty unquoted name not allowed */ + } + + while (isspace((unsigned char) *nextp)) + nextp++; /* skip trailing whitespace */ + + if (*nextp == separator) + { + nextp++; + while (isspace((unsigned char) *nextp)) + nextp++; /* skip leading whitespace for next */ + /* we expect another name, so done remains false */ + } + else if (*nextp == '\0') + done = true; + else + return false; /* invalid syntax */ + + /* Now safe to overwrite separator with a null */ + *endp = '\0'; + + /* + * Finished isolating current name --- add it to output array + */ + *nextptr++ = curname; + + /* Loop back if we didn't reach end of string */ + } while (!done); + + *nextptr = NULL; + return true; +} + + /* * Deconstruct the text representation of a 1-dimensional Postgres array * into individual items. diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h index 0680bb3c19e..3e455c8a415 100644 --- a/src/include/fe_utils/string_utils.h +++ b/src/include/fe_utils/string_utils.h @@ -48,6 +48,8 @@ extern bool appendShellStringNoError(PQExpBuffer buf, const char *str); extern void appendConnStrVal(PQExpBuffer buf, const char *str); extern void appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname); +extern bool SplitGUCList(char *rawstring, char separator, char ***namelist); + extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems); extern void appendPGArray(PQExpBuffer buffer, const char *value);