1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Reject calls from SQL to functions that take or return type internal.

Allowing that is a security hole, since there are many different
functions with different ideas of what their "internal" argument or
result is.  We already had a defense against the easy case of
"'foo'::internal", but that turns out to be insufficient.  Lock down
both function and operator syntax.  Also disallow attempts to cast to
or from type internal; those would mostly fail anyway, but we have
created some holes with features such as CoerceViaIO.

Reported-by: Amy Burnett (OpenAI Codex Security)
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Backpatch-through: 14
Security: CVE-2026-14680
This commit is contained in:
Tom Lane
2026-08-10 06:38:35 -07:00
committed by Noah Misch
parent c7f4628382
commit 155dacbc54
4 changed files with 66 additions and 2 deletions

View File

@@ -571,6 +571,10 @@ can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids,
if (inputTypeId == targetTypeId)
continue;
/* reject all cases of casting something else to/from "internal" */
if (inputTypeId == INTERNALOID || targetTypeId == INTERNALOID)
return false;
/* accept if target is ANY */
if (targetTypeId == ANYOID)
continue;
@@ -3150,6 +3154,10 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId,
if (sourceTypeId == targetTypeId)
return COERCION_PATH_RELABELTYPE;
/* Reject all cases of casting something else to/from "internal" */
if (sourceTypeId == INTERNALOID || targetTypeId == INTERNALOID)
return COERCION_PATH_NONE;
/* Look in pg_cast */
tuple = SearchSysCache2(CASTSOURCETARGET,
ObjectIdGetDatum(sourceTypeId),

View File

@@ -672,6 +672,32 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
rettype,
false);
/*
* Reject any attempt to call a function that takes or returns type
* internal from SQL. (The FUNCDETAIL_COERCION case does not reach this
* check because of the early return above, but that's okay because we
* disallow coercions to or from type internal.) Note that we are
* checking the resolved argument and result types, so this will reject
* calls to polymorphic functions that pass internal-type arguments. The
* casting rules should prevent that anyway, since we won't cast internal
* to any polymorphic type, but no harm in being doubly sure.
*/
for (int i = 0; i < nargsplusdefs; i++)
{
if (declared_arg_types[i] == INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("functions accepting type \"%s\" cannot be called explicitly",
"internal"),
parser_errposition(pstate, location)));
}
if (rettype == INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("functions returning type \"%s\" cannot be called explicitly",
"internal"),
parser_errposition(pstate, location)));
/* perform the necessary typecasting of arguments */
make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types);

View File

@@ -749,6 +749,28 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
opform->oprresult,
false);
/*
* Reject any attempt to call a function that takes or returns type
* internal from SQL. This is just like the check in ParseFuncOrColumn,
* but for operator syntax. (Despite that, we say "function" in the error
* messages; doesn't seem worth having two sets of translatable strings.)
*/
for (int i = 0; i < nargs; i++)
{
if (declared_arg_types[i] == INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("functions accepting type \"%s\" cannot be called explicitly",
"internal"),
parser_errposition(pstate, location)));
}
if (rettype == INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("functions returning type \"%s\" cannot be called explicitly",
"internal"),
parser_errposition(pstate, location)));
/* perform the necessary typecasting of arguments */
make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);

View File

@@ -7829,13 +7829,21 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
* If there's no cast path according to the parser, fall back to using
* an I/O coercion; this is semantically dubious but matches plpgsql's
* historical behavior. We would need something of the sort for
* UNKNOWN literals in any case. (This is probably now only reachable
* in the case where srctype is UNKNOWN/RECORD.)
* UNKNOWN literals in any case. The only case we reject is casting
* to/from INTERNAL. (Other than that case, this is probably now only
* reachable in the case where srctype is UNKNOWN/RECORD.)
*/
if (cast_expr == NULL)
{
CoerceViaIO *iocoerce = makeNode(CoerceViaIO);
if (srctype == INTERNALOID || dsttype == INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_CANNOT_COERCE),
errmsg("cannot cast type %s to %s",
format_type_be(srctype),
format_type_be(dsttype))));
iocoerce->arg = (Expr *) placeholder;
iocoerce->resulttype = dsttype;
iocoerce->resultcollid = InvalidOid;