diff options
author | Noah Misch <noah@leadboat.com> | 2014-02-17 09:33:31 -0500 |
---|---|---|
committer | Noah Misch <noah@leadboat.com> | 2014-02-17 09:33:38 -0500 |
commit | 823b9dc2566dbdbdab3c08b83adb64eb428b8ca5 (patch) | |
tree | d275d3e31ab3070af84d25f1e19f9951bc3e0560 | |
parent | ff35425c8f81541c8dc10486ed21ef1cfdae693e (diff) | |
download | postgresql-823b9dc2566dbdbdab3c08b83adb64eb428b8ca5.tar.gz postgresql-823b9dc2566dbdbdab3c08b83adb64eb428b8ca5.zip |
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.
Security: CVE-2014-0061
-rw-r--r-- | src/backend/catalog/pg_proc.c | 9 | ||||
-rw-r--r-- | src/backend/commands/functioncmds.c | 1 | ||||
-rw-r--r-- | src/backend/utils/fmgr/fmgr.c | 85 | ||||
-rw-r--r-- | src/include/fmgr.h | 1 | ||||
-rw-r--r-- | src/pl/plperl/plperl.c | 3 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_handler.c | 3 |
6 files changed, 101 insertions, 1 deletions
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 45d224ef409..e2e11163e90 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -623,6 +623,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. @@ -672,6 +675,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, @@ -724,6 +730,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 2151fd94f09..db22a288885 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -929,7 +929,6 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) prorows); } - /* * RemoveFunction * Deletes a function. diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 00711a6f86c..bcfbf5ac373 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -24,6 +24,7 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "pgstat.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgrtab.h" #include "utils/guc.h" @@ -2428,3 +2429,87 @@ get_call_expr_arg_stable(Node *expr, int argnum) return false; } + +/*------------------------------------------------------------------------- + * 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 cf309cdbd63..83dba75f30d 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -518,6 +518,7 @@ extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum); extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum); extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum); extern bool get_call_expr_arg_stable(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 01c95ff6b14..b65ea31a86c 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -1094,6 +1094,9 @@ plperl_validator(PG_FUNCTION_ARGS) bool istrigger = false; int i; + 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 3b362e78e76..9e07a2dc6a5 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -136,6 +136,9 @@ plpgsql_validator(PG_FUNCTION_ARGS) bool istrigger = false; int i; + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + /* Get the new function's pg_proc entry */ tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(funcoid), |