1
0
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:
Tom Lane
2026-08-10 06:38:17 -07:00
committed by Noah Misch
parent c2b16f5d49
commit 8a40f4b092
6 changed files with 109 additions and 13 deletions

View File

@@ -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
@@ -2818,6 +2832,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)
{

View File

@@ -1961,7 +1961,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)
{

View File

@@ -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 ...

View File

@@ -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));

View File

@@ -2468,6 +2468,20 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
if (procStruct->pronargs > 0)
{
/*
* Protect against overrun of fixed-size hashkey->argtypes array.
* 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)));
/* get the argument types */
memcpy(hashkey->argtypes, procStruct->proargtypes.values,
procStruct->pronargs * sizeof(Oid));

View File

@@ -1410,6 +1410,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
volatile MemoryContext proc_cxt = NULL;
Tcl_DString proc_internal_def;
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));
@@ -1457,16 +1458,16 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
************************************************************/
Tcl_DStringInit(&proc_internal_def);
Tcl_DStringInit(&proc_internal_body);
Tcl_DStringInit(&proc_internal_args);
PG_TRY();
{
bool is_trigger = OidIsValid(tgreloid);
char internal_proname[128];
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];
Tcl_Interp *interp;
int i;
int tcl_rc;
@@ -1576,7 +1577,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];
@@ -1609,8 +1609,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);
}
@@ -1618,13 +1618,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);
}
/************************************************************
@@ -1637,7 +1638,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
@@ -1717,6 +1719,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
MemoryContextDelete(proc_cxt);
Tcl_DStringFree(&proc_internal_def);
Tcl_DStringFree(&proc_internal_body);
Tcl_DStringFree(&proc_internal_args);
PG_RE_THROW();
}
PG_END_TRY();
@@ -1745,6 +1748,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
Tcl_DStringFree(&proc_internal_def);
Tcl_DStringFree(&proc_internal_body);
Tcl_DStringFree(&proc_internal_args);
ReleaseSysCache(procTup);