Backport fix for CVE-2014-0061 from REL8_4_STABLE. Author: Noah Misch <noah@leadboat.com> Document security implications of check_function_bodies. Author: Noah Misch <noah@leadboat.com> Prevent privilege escalation in explicit calls to PL validators. The primary role of PL validators is to be called implicitly during CREATE FUNCTION, but they are also normal functions that a user can call explicitly. Add a permissions check to each validator to ensure that a user cannot use explicit validator calls to achieve things he could not otherwise achieve. Back-patch to 8.4 (all supported versions). Non-core procedural language extensions ought to make the same two-line change to their own validators. Andres Freund, reviewed by Tom Lane and Noah Misch. diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index ecee550..42c802a 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -3215,9 +3215,11 @@ SELECT * FROM parent WHERE key = 2400; This parameter is normally on. When set to <literal>off</>, it disables validation of the function body string during <xref linkend="sql-createfunction" - endterm="sql-createfunction-title">. Disabling validation is - occasionally useful to avoid problems such as forward references - when restoring function definitions from a dump. + endterm="sql-createfunction-title">. Disabling validation avoids side + effects of the validation process and avoids false positives due + to problems such as forward references. Set this parameter + to <literal>off</> before loading functions on behalf of other + users; <application>pg_dump</> does so automatically. </para> </listitem> </varlistentry> diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index f1a75de..fc7494b 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -425,6 +425,9 @@ fmgr_internal_validator(PG_FUNCTION_ARGS) Datum tmp; char *prosrc; + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + /* * We do not honor check_function_bodies since it's unlikely the function * name will be found later if it isn't there now. @@ -474,6 +477,9 @@ fmgr_c_validator(PG_FUNCTION_ARGS) char *prosrc; char *probin; + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + /* * It'd be most consistent to skip the check if !check_function_bodies, * but the purpose of that switch is to be helpful for pg_dump loading, @@ -525,6 +531,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) bool haspolyarg; int i; + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(funcoid), 0, 0, 0); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 13e4b45..d26f7c7 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -669,7 +669,6 @@ CreateFunction(CreateFunctionStmt *stmt) PointerGetDatum(parameterNames)); } - /* * RemoveFunction * Deletes a function. diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 1c85102..eb1c42a 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -21,6 +21,7 @@ #include "executor/functions.h" #include "miscadmin.h" #include "parser/parse_expr.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgrtab.h" #include "utils/lsyscache.h" @@ -1924,3 +1925,87 @@ get_call_expr_argtype(Node *expr, int argnum) return argtype; } + +/*------------------------------------------------------------------------- + * Support routines for procedural language implementations + *------------------------------------------------------------------------- + */ + +/* + * Verify that a validator is actually associated with the language of a + * particular function and that the user has access to both the language and + * the function. All validators should call this before doing anything + * substantial. Doing so ensures a user cannot achieve anything with explicit + * calls to validators that he could not achieve with CREATE FUNCTION or by + * simply calling an existing function. + * + * When this function returns false, callers should skip all validation work + * and call PG_RETURN_VOID(). This never happens at present; it is reserved + * for future expansion. + * + * In particular, checking that the validator corresponds to the function's + * language allows untrusted language validators to assume they process only + * superuser-chosen source code. (Untrusted language call handlers, by + * definition, do assume that.) A user lacking the USAGE language privilege + * would be unable to reach the validator through CREATE FUNCTION, so we check + * that to block explicit calls as well. Checking the EXECUTE privilege on + * the function is often superfluous, because most users can clone the + * function to get an executable copy. It is meaningful against users with no + * database TEMP right and no permanent schema CREATE right, thereby unable to + * create any function. Also, if the function tracks persistent state by + * function OID or name, validating the original function might permit more + * mischief than creating and validating a clone thereof. + */ +bool +CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid) +{ + HeapTuple procTup; + HeapTuple langTup; + Form_pg_proc procStruct; + Form_pg_language langStruct; + AclResult aclresult; + + /* Get the function's pg_proc entry */ + procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(functionOid), 0, 0, 0); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function %u", functionOid); + procStruct = (Form_pg_proc) GETSTRUCT(procTup); + + /* + * Fetch pg_language entry to know if this is the correct validation + * function for that pg_proc entry. + */ + langTup = SearchSysCache(LANGOID, ObjectIdGetDatum(procStruct->prolang), + 0, 0, 0); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failed for language %u", procStruct->prolang); + langStruct = (Form_pg_language) GETSTRUCT(langTup); + + if (langStruct->lanvalidator != validatorOid) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("language validation function %u called for language %u instead of %u", + validatorOid, procStruct->prolang, + langStruct->lanvalidator))); + + /* first validate that we have permissions to use the language */ + aclresult = pg_language_aclcheck(procStruct->prolang, GetUserId(), + ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_LANGUAGE, + NameStr(langStruct->lanname)); + + /* + * Check whether we are allowed to execute the function itself. If we can + * execute it, there should be no possible side-effect of + * compiling/validation that execution can't have. + */ + aclresult = pg_proc_aclcheck(functionOid, GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_PROC, NameStr(procStruct->proname)); + + ReleaseSysCache(procTup); + ReleaseSysCache(langTup); + + return true; +} diff --git a/src/include/fmgr.h b/src/include/fmgr.h index b7a85f1..ccc6b0e 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -401,6 +401,7 @@ extern Oid fmgr_internal_function(const char *proname); extern Oid get_fn_expr_rettype(FmgrInfo *flinfo); extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum); extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum); +extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid); /* * Routines in dfmgr.c diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index bc80a51..084a38a 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -1032,6 +1032,9 @@ plperl_validator(PG_FUNCTION_ARGS) plperl_init_all(); + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + /* Get the new function's pg_proc entry */ tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(funcoid), diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 43446f0..04ebcaf 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -170,6 +170,9 @@ plpgsql_validator(PG_FUNCTION_ARGS) /* perform initialization */ plpgsql_init_all(); + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + /* Get the new function's pg_proc entry */ tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(funcoid),