mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Protect some fixed-size arrays that have FUNC_MAX_ARGS elements.
The maximum number of arguments allowed for an aggregate function is FUNC_MAX_ARGS-1 (since the underlying transfn and/or finalfn will be called with one more argument). parse_func.c failed to enforce this, allowing construction of calls that would try to pass FUNC_MAX_ARGS+1 to the underlying functions, resulting in a memory stomp in the executor. Add correct checking there. Since it's possible that a bad call has been stored in a view or SQL function, also add checks in various aggregate-related and window-function-related code that there are not more than FUNC_MAX_ARGS arguments. These will also protect us against the possibility that we're trying to run a stored view that was made by a server executable with different FUNC_MAX_ARGS. (Arguably, that scenario does not qualify as a security problem. But let's just tighten up all of this while we're here, rather than split hairs over whether an overrun is reachable.) Likewise check in compute_function_hashkey. Here the hazard is directly from a pg_proc row, but the scenario is the same. PL/Tcl has a similar issue with a fixed-size string buffer. Let's just replace that buffer with a Tcl_DString, removing the whole issue and making the code look more like what's around it. There are a lot of other FUNC_MAX_ARGS-sized arrays, but the rest have nearby guards already, some with comments explicitly pointing out the hazard of FUNC_MAX_ARGS changing. I also used palloc_array() in a few related places in funcapi.c. Those aren't live hazards AFAICS, but nearby code has been palloc_array-ified already, so it seemed inconsistent to not use it here. Reported-by: Masahiko Sawada <sawada.mshk@gmail.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com> Backpatch-through: 14 Security: CVE-2026-14679
This commit is contained in:
@@ -1039,6 +1039,20 @@ eval_windowfunction(WindowAggState *winstate, WindowStatePerFunc perfuncstate,
|
||||
|
||||
oldContext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
|
||||
|
||||
/*
|
||||
* Protect fixed-size fcinfo. Ordinarily this would have been checked
|
||||
* while creating the WindowFunc, but it's possible that we are looking at
|
||||
* a parsetree from a stored view that was made by a server executable
|
||||
* with a different value of FUNC_MAX_ARGS.
|
||||
*/
|
||||
if (perfuncstate->numArguments > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg_plural("cannot pass more than %d argument to a function",
|
||||
"cannot pass more than %d arguments to a function",
|
||||
FUNC_MAX_ARGS,
|
||||
FUNC_MAX_ARGS)));
|
||||
|
||||
/*
|
||||
* We don't pass any normal arguments to a window function, but we do pass
|
||||
* it the number of arguments, in order to permit window function
|
||||
@@ -2884,6 +2898,25 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
|
||||
numArguments = list_length(wfunc->args);
|
||||
|
||||
/*
|
||||
* Check the number of arguments, to protect fixed-size arrays here and
|
||||
* later in node execution.
|
||||
*
|
||||
* Aggregates can have at most FUNC_MAX_ARGS-1 args (compare
|
||||
* AggregateCreate, whose error message we want to match). Ordinarily
|
||||
* this would have been checked while creating the WindowFunc, but it's
|
||||
* possible that we are looking at a parsetree from a stored view that was
|
||||
* made by a server executable with a different value of FUNC_MAX_ARGS, or
|
||||
* an executable in which parse_func.c didn't enforce the correct limit.
|
||||
*/
|
||||
if (numArguments > FUNC_MAX_ARGS - 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg_plural("aggregates cannot have more than %d argument",
|
||||
"aggregates cannot have more than %d arguments",
|
||||
FUNC_MAX_ARGS - 1,
|
||||
FUNC_MAX_ARGS - 1)));
|
||||
|
||||
i = 0;
|
||||
foreach(lc, wfunc->args)
|
||||
{
|
||||
|
||||
@@ -2054,7 +2054,23 @@ get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes)
|
||||
int numArguments = 0;
|
||||
ListCell *lc;
|
||||
|
||||
Assert(list_length(aggref->aggargtypes) <= FUNC_MAX_ARGS);
|
||||
/*
|
||||
* Check the number of arguments to protect fixed-size arrays in callers.
|
||||
*
|
||||
* Aggregates can have at most FUNC_MAX_ARGS-1 args (compare
|
||||
* AggregateCreate, whose error message we want to match). Ordinarily
|
||||
* this would have been checked while creating the Aggref, but it's
|
||||
* possible that we are looking at a parsetree from a stored view that was
|
||||
* made by a server executable with a different value of FUNC_MAX_ARGS, or
|
||||
* an executable in which parse_func.c didn't enforce the correct limit.
|
||||
*/
|
||||
if (list_length(aggref->aggargtypes) > FUNC_MAX_ARGS - 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg_plural("aggregates cannot have more than %d argument",
|
||||
"aggregates cannot have more than %d arguments",
|
||||
FUNC_MAX_ARGS - 1,
|
||||
FUNC_MAX_ARGS - 1)));
|
||||
|
||||
foreach(lc, aggref->aggargtypes)
|
||||
{
|
||||
|
||||
@@ -781,6 +781,22 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
|
||||
aggref->aggtransno = -1;
|
||||
aggref->location = location;
|
||||
|
||||
/*
|
||||
* The argument-count limit for aggregates is one less than for other
|
||||
* kinds of functions (cf. AggregateCreate). Now that we know it's an
|
||||
* aggregate, apply the stricter limit. We need an explicit check
|
||||
* because hypothetical-set aggregates don't have a fixed number of
|
||||
* arguments, so having matched the pg_proc entry proves nothing.
|
||||
*/
|
||||
if (list_length(fargs) > FUNC_MAX_ARGS - 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg_plural("aggregates cannot have more than %d argument",
|
||||
"aggregates cannot have more than %d arguments",
|
||||
FUNC_MAX_ARGS - 1,
|
||||
FUNC_MAX_ARGS - 1),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
/*
|
||||
* Reject attempt to call a parameterless aggregate without (*)
|
||||
* syntax. This is mere pedantry but some folks insisted ...
|
||||
@@ -846,6 +862,19 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
|
||||
errmsg("DISTINCT is not implemented for window functions"),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
/*
|
||||
* As above, enforce the correct argument-count limit if it's really
|
||||
* an aggregate.
|
||||
*/
|
||||
if (wfunc->winagg && list_length(fargs) > FUNC_MAX_ARGS - 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg_plural("aggregates cannot have more than %d argument",
|
||||
"aggregates cannot have more than %d arguments",
|
||||
FUNC_MAX_ARGS - 1,
|
||||
FUNC_MAX_ARGS - 1),
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
/*
|
||||
* Reject attempt to call a parameterless aggregate without (*)
|
||||
* syntax. This is mere pedantry but some folks insisted ...
|
||||
|
||||
14
src/backend/utils/cache/funccache.c
vendored
14
src/backend/utils/cache/funccache.c
vendored
@@ -294,6 +294,20 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
|
||||
*/
|
||||
if (procStruct->pronargs > 0)
|
||||
{
|
||||
/*
|
||||
* Protect against overrun of fixed-size hashkey->argtypes array. This
|
||||
* also protects later code in places such as PL/pgSQL. Ordinarily the
|
||||
* parser would have checked this long since, but it's possible that
|
||||
* we are looking at a pg_proc entry that was made by a server
|
||||
* executable with a different value of FUNC_MAX_ARGS.
|
||||
*/
|
||||
if (procStruct->pronargs > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg_plural("cannot pass more than %d argument to a function",
|
||||
"cannot pass more than %d arguments to a function",
|
||||
FUNC_MAX_ARGS,
|
||||
FUNC_MAX_ARGS)));
|
||||
hashkey->nargs = procStruct->pronargs;
|
||||
memcpy(hashkey->argtypes, procStruct->proargtypes.values,
|
||||
procStruct->pronargs * sizeof(Oid));
|
||||
|
||||
@@ -1410,7 +1410,7 @@ get_func_arg_info(HeapTuple procTup,
|
||||
ARR_ELEMTYPE(arr) != OIDOID)
|
||||
elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
|
||||
Assert(numargs >= procStruct->pronargs);
|
||||
*p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
|
||||
*p_argtypes = palloc_array(Oid, numargs);
|
||||
memcpy(*p_argtypes, ARR_DATA_PTR(arr),
|
||||
numargs * sizeof(Oid));
|
||||
}
|
||||
@@ -1419,7 +1419,7 @@ get_func_arg_info(HeapTuple procTup,
|
||||
/* If no proallargtypes, use proargtypes */
|
||||
numargs = procStruct->proargtypes.dim1;
|
||||
Assert(numargs == procStruct->pronargs);
|
||||
*p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
|
||||
*p_argtypes = palloc_array(Oid, numargs);
|
||||
memcpy(*p_argtypes, procStruct->proargtypes.values,
|
||||
numargs * sizeof(Oid));
|
||||
}
|
||||
@@ -1498,7 +1498,7 @@ get_func_trftypes(HeapTuple procTup,
|
||||
ARR_HASNULL(arr) ||
|
||||
ARR_ELEMTYPE(arr) != OIDOID)
|
||||
elog(ERROR, "protrftypes is not a 1-D Oid array or it contains nulls");
|
||||
*p_trftypes = (Oid *) palloc(nelems * sizeof(Oid));
|
||||
*p_trftypes = palloc_array(Oid, nelems);
|
||||
memcpy(*p_trftypes, ARR_DATA_PTR(arr),
|
||||
nelems * sizeof(Oid));
|
||||
|
||||
|
||||
@@ -1428,6 +1428,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
Tcl_DString proc_internal_def;
|
||||
Tcl_DString proc_internal_name;
|
||||
Tcl_DString proc_internal_body;
|
||||
Tcl_DString proc_internal_args;
|
||||
|
||||
/* We'll need the pg_proc tuple in any case... */
|
||||
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
|
||||
@@ -1477,6 +1478,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
Tcl_DStringInit(&proc_internal_def);
|
||||
Tcl_DStringInit(&proc_internal_name);
|
||||
Tcl_DStringInit(&proc_internal_body);
|
||||
Tcl_DStringInit(&proc_internal_args);
|
||||
PG_TRY();
|
||||
{
|
||||
bool is_trigger = OidIsValid(tgreloid);
|
||||
@@ -1486,10 +1488,9 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
bool need_underscore;
|
||||
HeapTuple typeTup;
|
||||
Form_pg_type typeStruct;
|
||||
char proc_internal_args[33 * FUNC_MAX_ARGS];
|
||||
Datum prosrcdatum;
|
||||
char *proc_source;
|
||||
char buf[48];
|
||||
char buf[64];
|
||||
pltcl_interp_desc *interp_desc;
|
||||
Tcl_Interp *interp;
|
||||
int i;
|
||||
@@ -1658,7 +1659,6 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
************************************************************/
|
||||
if (!is_trigger && !is_event_trigger)
|
||||
{
|
||||
proc_internal_args[0] = '\0';
|
||||
for (i = 0; i < prodesc->nargs; i++)
|
||||
{
|
||||
Oid argtype = procStruct->proargtypes.values[i];
|
||||
@@ -1691,8 +1691,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
strcat(proc_internal_args, " ");
|
||||
strcat(proc_internal_args, buf);
|
||||
Tcl_DStringAppend(&proc_internal_args, " ", -1);
|
||||
Tcl_DStringAppend(&proc_internal_args, buf, -1);
|
||||
|
||||
ReleaseSysCache(typeTup);
|
||||
}
|
||||
@@ -1700,13 +1700,14 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
else if (is_trigger)
|
||||
{
|
||||
/* trigger procedure has fixed args */
|
||||
strcpy(proc_internal_args,
|
||||
"TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
|
||||
Tcl_DStringAppend(&proc_internal_args,
|
||||
"TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args",
|
||||
-1);
|
||||
}
|
||||
else if (is_event_trigger)
|
||||
{
|
||||
/* event trigger procedure has fixed args */
|
||||
strcpy(proc_internal_args, "TG_event TG_tag");
|
||||
Tcl_DStringAppend(&proc_internal_args, "TG_event TG_tag", -1);
|
||||
}
|
||||
|
||||
/************************************************************
|
||||
@@ -1719,7 +1720,8 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
************************************************************/
|
||||
Tcl_DStringAppendElement(&proc_internal_def, "proc");
|
||||
Tcl_DStringAppendElement(&proc_internal_def, internal_proname);
|
||||
Tcl_DStringAppendElement(&proc_internal_def, proc_internal_args);
|
||||
Tcl_DStringAppendElement(&proc_internal_def,
|
||||
Tcl_DStringValue(&proc_internal_args));
|
||||
|
||||
/************************************************************
|
||||
* prefix procedure body with
|
||||
@@ -1800,6 +1802,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
Tcl_DStringFree(&proc_internal_def);
|
||||
Tcl_DStringFree(&proc_internal_name);
|
||||
Tcl_DStringFree(&proc_internal_body);
|
||||
Tcl_DStringFree(&proc_internal_args);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
@@ -1829,6 +1832,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
|
||||
Tcl_DStringFree(&proc_internal_def);
|
||||
Tcl_DStringFree(&proc_internal_name);
|
||||
Tcl_DStringFree(&proc_internal_body);
|
||||
Tcl_DStringFree(&proc_internal_args);
|
||||
|
||||
ReleaseSysCache(procTup);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user