Skip to content

Commit 3db826b

Browse files
committed
Tighten up allowed names for custom GUC parameters.
Formerly we were pretty lax about what a custom GUC's name could be; so long as it had at least one dot in it, we'd take it. However, corner cases such as dashes or equal signs in the name would cause various bits of functionality to misbehave. Rather than trying to make the world perfectly safe for that, let's just require that custom names look like "identifier.identifier", where "identifier" means something that scan.l would accept without double quotes. Along the way, this patch refactors things slightly in guc.c so that find_option() is responsible for reporting GUC-not-found cases, allowing removal of duplicative code from its callers. Per report from Hubert Depesz Lubaczewski. No back-patch, since the consequences of the problem don't seem to warrant changing behavior in stable branches. Discussion: https://postgr.es/m/[email protected]
1 parent 23607a8 commit 3db826b

File tree

4 files changed

+132
-70
lines changed

4 files changed

+132
-70
lines changed

src/backend/utils/misc/guc-file.l

+2-2
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
282282
* Try to find the variable; but do not create a custom placeholder if
283283
* it's not there already.
284284
*/
285-
record = find_option(item->name, false, elevel);
285+
record = find_option(item->name, false, true, elevel);
286286

287287
if (record)
288288
{
@@ -306,7 +306,7 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
306306
/* Now mark it as present in file */
307307
record->status |= GUC_IS_IN_FILE;
308308
}
309-
else if (strchr(item->name, GUC_QUALIFIER_SEPARATOR) == NULL)
309+
else if (!valid_custom_variable_name(item->name))
310310
{
311311
/* Invalid non-custom variable, so complain */
312312
ereport(elevel,

src/backend/utils/misc/guc.c

+99-68
Original file line numberDiff line numberDiff line change
@@ -5334,6 +5334,45 @@ add_guc_variable(struct config_generic *var, int elevel)
53345334
return true;
53355335
}
53365336

5337+
/*
5338+
* Decide whether a proposed custom variable name is allowed.
5339+
*
5340+
* It must be "identifier.identifier", where the rules for what is an
5341+
* identifier agree with scan.l.
5342+
*/
5343+
static bool
5344+
valid_custom_variable_name(const char *name)
5345+
{
5346+
int num_sep = 0;
5347+
bool name_start = true;
5348+
5349+
for (const char *p = name; *p; p++)
5350+
{
5351+
if (*p == GUC_QUALIFIER_SEPARATOR)
5352+
{
5353+
if (name_start)
5354+
return false; /* empty name component */
5355+
num_sep++;
5356+
name_start = true;
5357+
}
5358+
else if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
5359+
"abcdefghijklmnopqrstuvwxyz", *p) != NULL ||
5360+
IS_HIGHBIT_SET(*p))
5361+
{
5362+
/* okay as first or non-first character */
5363+
name_start = false;
5364+
}
5365+
else if (!name_start && strchr("0123456789_$", *p) != NULL)
5366+
/* okay as non-first character */ ;
5367+
else
5368+
return false;
5369+
}
5370+
if (name_start)
5371+
return false; /* empty name component */
5372+
/* OK if we had exactly one separator */
5373+
return (num_sep == 1);
5374+
}
5375+
53375376
/*
53385377
* Create and add a placeholder variable for a custom variable name.
53395378
*/
@@ -5381,12 +5420,23 @@ add_placeholder_variable(const char *name, int elevel)
53815420
}
53825421

53835422
/*
5384-
* Look up option NAME. If it exists, return a pointer to its record,
5385-
* else return NULL. If create_placeholders is true, we'll create a
5386-
* placeholder record for a valid-looking custom variable name.
5423+
* Look up option "name". If it exists, return a pointer to its record.
5424+
* Otherwise, if create_placeholders is true and name is a valid-looking
5425+
* custom variable name, we'll create and return a placeholder record.
5426+
* Otherwise, if skip_errors is true, then we silently return NULL for
5427+
* an unrecognized or invalid name. Otherwise, the error is reported at
5428+
* error level elevel (and we return NULL if that's less than ERROR).
5429+
*
5430+
* Note: internal errors, primarily out-of-memory, draw an elevel-level
5431+
* report and NULL return regardless of skip_errors. Hence, callers must
5432+
* handle a NULL return whenever elevel < ERROR, but they should not need
5433+
* to emit any additional error message. (In practice, internal errors
5434+
* can only happen when create_placeholders is true, so callers passing
5435+
* false need not think terribly hard about this.)
53875436
*/
53885437
static struct config_generic *
5389-
find_option(const char *name, bool create_placeholders, int elevel)
5438+
find_option(const char *name, bool create_placeholders, bool skip_errors,
5439+
int elevel)
53905440
{
53915441
const char **key = &name;
53925442
struct config_generic **res;
@@ -5414,19 +5464,38 @@ find_option(const char *name, bool create_placeholders, int elevel)
54145464
for (i = 0; map_old_guc_names[i] != NULL; i += 2)
54155465
{
54165466
if (guc_name_compare(name, map_old_guc_names[i]) == 0)
5417-
return find_option(map_old_guc_names[i + 1], false, elevel);
5467+
return find_option(map_old_guc_names[i + 1], false,
5468+
skip_errors, elevel);
54185469
}
54195470

54205471
if (create_placeholders)
54215472
{
54225473
/*
5423-
* Check if the name is qualified, and if so, add a placeholder.
5474+
* Check if the name is valid, and if so, add a placeholder. If it
5475+
* doesn't contain a separator, don't assume that it was meant to be a
5476+
* placeholder.
54245477
*/
54255478
if (strchr(name, GUC_QUALIFIER_SEPARATOR) != NULL)
5426-
return add_placeholder_variable(name, elevel);
5479+
{
5480+
if (valid_custom_variable_name(name))
5481+
return add_placeholder_variable(name, elevel);
5482+
/* A special error message seems desirable here */
5483+
if (!skip_errors)
5484+
ereport(elevel,
5485+
(errcode(ERRCODE_INVALID_NAME),
5486+
errmsg("invalid configuration parameter name \"%s\"",
5487+
name),
5488+
errdetail("Custom parameter names must be of the form \"identifier.identifier\".")));
5489+
return NULL;
5490+
}
54275491
}
54285492

54295493
/* Unknown name */
5494+
if (!skip_errors)
5495+
ereport(elevel,
5496+
(errcode(ERRCODE_UNDEFINED_OBJECT),
5497+
errmsg("unrecognized configuration parameter \"%s\"",
5498+
name)));
54305499
return NULL;
54315500
}
54325501

@@ -6444,7 +6513,7 @@ ReportChangedGUCOptions(void)
64446513
{
64456514
struct config_generic *record;
64466515

6447-
record = find_option("in_hot_standby", false, ERROR);
6516+
record = find_option("in_hot_standby", false, false, ERROR);
64486517
Assert(record != NULL);
64496518
record->status |= GUC_NEEDS_REPORT;
64506519
report_needed = true;
@@ -7218,14 +7287,9 @@ set_config_option(const char *name, const char *value,
72187287
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
72197288
errmsg("cannot set parameters during a parallel operation")));
72207289

7221-
record = find_option(name, true, elevel);
7290+
record = find_option(name, true, false, elevel);
72227291
if (record == NULL)
7223-
{
7224-
ereport(elevel,
7225-
(errcode(ERRCODE_UNDEFINED_OBJECT),
7226-
errmsg("unrecognized configuration parameter \"%s\"", name)));
72277292
return 0;
7228-
}
72297293

72307294
/*
72317295
* Check if the option can be set at this time. See guc.h for the precise
@@ -7947,10 +8011,10 @@ set_config_sourcefile(const char *name, char *sourcefile, int sourceline)
79478011
*/
79488012
elevel = IsUnderPostmaster ? DEBUG3 : LOG;
79498013

7950-
record = find_option(name, true, elevel);
8014+
record = find_option(name, true, false, elevel);
79518015
/* should not happen */
79528016
if (record == NULL)
7953-
elog(ERROR, "unrecognized configuration parameter \"%s\"", name);
8017+
return;
79548018

79558019
sourcefile = guc_strdup(elevel, sourcefile);
79568020
if (record->sourcefile)
@@ -7999,16 +8063,9 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
79998063
struct config_generic *record;
80008064
static char buffer[256];
80018065

8002-
record = find_option(name, false, ERROR);
8066+
record = find_option(name, false, missing_ok, ERROR);
80038067
if (record == NULL)
8004-
{
8005-
if (missing_ok)
8006-
return NULL;
8007-
ereport(ERROR,
8008-
(errcode(ERRCODE_UNDEFINED_OBJECT),
8009-
errmsg("unrecognized configuration parameter \"%s\"",
8010-
name)));
8011-
}
8068+
return NULL;
80128069
if (restrict_privileged &&
80138070
(record->flags & GUC_SUPERUSER_ONLY) &&
80148071
!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
@@ -8055,11 +8112,8 @@ GetConfigOptionResetString(const char *name)
80558112
struct config_generic *record;
80568113
static char buffer[256];
80578114

8058-
record = find_option(name, false, ERROR);
8059-
if (record == NULL)
8060-
ereport(ERROR,
8061-
(errcode(ERRCODE_UNDEFINED_OBJECT),
8062-
errmsg("unrecognized configuration parameter \"%s\"", name)));
8115+
record = find_option(name, false, false, ERROR);
8116+
Assert(record != NULL);
80638117
if ((record->flags & GUC_SUPERUSER_ONLY) &&
80648118
!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
80658119
ereport(ERROR,
@@ -8103,16 +8157,9 @@ GetConfigOptionFlags(const char *name, bool missing_ok)
81038157
{
81048158
struct config_generic *record;
81058159

8106-
record = find_option(name, false, WARNING);
8160+
record = find_option(name, false, missing_ok, ERROR);
81078161
if (record == NULL)
8108-
{
8109-
if (missing_ok)
8110-
return 0;
8111-
ereport(ERROR,
8112-
(errcode(ERRCODE_UNDEFINED_OBJECT),
8113-
errmsg("unrecognized configuration parameter \"%s\"",
8114-
name)));
8115-
}
8162+
return 0;
81168163
return record->flags;
81178164
}
81188165

@@ -8144,7 +8191,7 @@ flatten_set_variable_args(const char *name, List *args)
81448191
* Get flags for the variable; if it's not known, use default flags.
81458192
* (Caller might throw error later, but not our business to do so here.)
81468193
*/
8147-
record = find_option(name, false, WARNING);
8194+
record = find_option(name, false, true, WARNING);
81488195
if (record)
81498196
flags = record->flags;
81508197
else
@@ -8439,12 +8486,8 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
84398486
{
84408487
struct config_generic *record;
84418488

8442-
record = find_option(name, false, ERROR);
8443-
if (record == NULL)
8444-
ereport(ERROR,
8445-
(errcode(ERRCODE_UNDEFINED_OBJECT),
8446-
errmsg("unrecognized configuration parameter \"%s\"",
8447-
name)));
8489+
record = find_option(name, false, false, ERROR);
8490+
Assert(record != NULL);
84488491

84498492
/*
84508493
* Don't allow parameters that can't be set in configuration files to
@@ -9460,19 +9503,12 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
94609503
{
94619504
struct config_generic *record;
94629505

9463-
record = find_option(name, false, ERROR);
9506+
record = find_option(name, false, missing_ok, ERROR);
94649507
if (record == NULL)
94659508
{
9466-
if (missing_ok)
9467-
{
9468-
if (varname)
9469-
*varname = NULL;
9470-
return NULL;
9471-
}
9472-
9473-
ereport(ERROR,
9474-
(errcode(ERRCODE_UNDEFINED_OBJECT),
9475-
errmsg("unrecognized configuration parameter \"%s\"", name)));
9509+
if (varname)
9510+
*varname = NULL;
9511+
return NULL;
94769512
}
94779513

94789514
if ((record->flags & GUC_SUPERUSER_ONLY) &&
@@ -10318,7 +10354,7 @@ read_nondefault_variables(void)
1031810354
if ((varname = read_string_with_null(fp)) == NULL)
1031910355
break;
1032010356

10321-
if ((record = find_option(varname, true, FATAL)) == NULL)
10357+
if ((record = find_option(varname, true, false, FATAL)) == NULL)
1032210358
elog(FATAL, "failed to locate variable \"%s\" in exec config params file", varname);
1032310359

1032410360
if ((varvalue = read_string_with_null(fp)) == NULL)
@@ -11008,7 +11044,7 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value)
1100811044
(void) validate_option_array_item(name, value, false);
1100911045

1101011046
/* normalize name (converts obsolete GUC names to modern spellings) */
11011-
record = find_option(name, false, WARNING);
11047+
record = find_option(name, false, true, WARNING);
1101211048
if (record)
1101311049
name = record->name;
1101411050

@@ -11087,7 +11123,7 @@ GUCArrayDelete(ArrayType *array, const char *name)
1108711123
(void) validate_option_array_item(name, NULL, false);
1108811124

1108911125
/* normalize name (converts obsolete GUC names to modern spellings) */
11090-
record = find_option(name, false, WARNING);
11126+
record = find_option(name, false, true, WARNING);
1109111127
if (record)
1109211128
name = record->name;
1109311129

@@ -11234,7 +11270,7 @@ validate_option_array_item(const char *name, const char *value,
1123411270
* SUSET and user is superuser).
1123511271
*
1123611272
* name is not known, but exists or can be created as a placeholder (i.e.,
11237-
* it has a prefixed name). We allow this case if you're a superuser,
11273+
* it has a valid custom name). We allow this case if you're a superuser,
1123811274
* otherwise not. Superusers are assumed to know what they're doing. We
1123911275
* can't allow it for other users, because when the placeholder is
1124011276
* resolved it might turn out to be a SUSET variable;
@@ -11243,16 +11279,11 @@ validate_option_array_item(const char *name, const char *value,
1124311279
* name is not known and can't be created as a placeholder. Throw error,
1124411280
* unless skipIfNoPermissions is true, in which case return false.
1124511281
*/
11246-
gconf = find_option(name, true, WARNING);
11282+
gconf = find_option(name, true, skipIfNoPermissions, ERROR);
1124711283
if (!gconf)
1124811284
{
1124911285
/* not known, failed to make a placeholder */
11250-
if (skipIfNoPermissions)
11251-
return false;
11252-
ereport(ERROR,
11253-
(errcode(ERRCODE_UNDEFINED_OBJECT),
11254-
errmsg("unrecognized configuration parameter \"%s\"",
11255-
name)));
11286+
return false;
1125611287
}
1125711288

1125811289
if (gconf->flags & GUC_CUSTOM_PLACEHOLDER)

src/test/regress/expected/guc.out

+21
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,27 @@ SET seq_page_cost TO 'NaN';
511511
ERROR: invalid value for parameter "seq_page_cost": "NaN"
512512
SET vacuum_cost_delay TO '10s';
513513
ERROR: 10000 ms is outside the valid range for parameter "vacuum_cost_delay" (0 .. 100)
514+
SET no_such_variable TO 42;
515+
ERROR: unrecognized configuration parameter "no_such_variable"
516+
-- Test "custom" GUCs created on the fly (which aren't really an
517+
-- intended feature, but many people use them).
518+
SET custom.my_guc = 42;
519+
SHOW custom.my_guc;
520+
custom.my_guc
521+
---------------
522+
42
523+
(1 row)
524+
525+
SET custom."bad-guc" = 42; -- disallowed because -c cannot set this name
526+
ERROR: invalid configuration parameter name "custom.bad-guc"
527+
DETAIL: Custom parameter names must be of the form "identifier.identifier".
528+
SHOW custom."bad-guc";
529+
ERROR: unrecognized configuration parameter "custom.bad-guc"
530+
SET special."weird name" = 'foo'; -- could be allowed, but we choose not to
531+
ERROR: invalid configuration parameter name "special.weird name"
532+
DETAIL: Custom parameter names must be of the form "identifier.identifier".
533+
SHOW special."weird name";
534+
ERROR: unrecognized configuration parameter "special.weird name"
514535
--
515536
-- Test DISCARD TEMP
516537
--

src/test/regress/sql/guc.sql

+10
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
147147
-- Test some simple error cases
148148
SET seq_page_cost TO 'NaN';
149149
SET vacuum_cost_delay TO '10s';
150+
SET no_such_variable TO 42;
151+
152+
-- Test "custom" GUCs created on the fly (which aren't really an
153+
-- intended feature, but many people use them).
154+
SET custom.my_guc = 42;
155+
SHOW custom.my_guc;
156+
SET custom."bad-guc" = 42; -- disallowed because -c cannot set this name
157+
SHOW custom."bad-guc";
158+
SET special."weird name" = 'foo'; -- could be allowed, but we choose not to
159+
SHOW special."weird name";
150160

151161
--
152162
-- Test DISCARD TEMP

0 commit comments

Comments
 (0)