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:36 -0500 |
commit | 23b5a85e60c464ab8bc438a547a4b15260ca9453 (patch) | |
tree | a7052c7e2039ec185e9b44fdf9b1596c48396a3c /src | |
parent | 5d320a16ca544d6e125e962ed325463ad8bec240 (diff) | |
download | postgresql-23b5a85e60c464ab8bc438a547a4b15260ca9453.tar.gz postgresql-23b5a85e60c464ab8bc438a547a4b15260ca9453.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
Diffstat (limited to 'src')
-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 | 84 | ||||
-rw-r--r-- | src/include/fmgr.h | 1 | ||||
-rw-r--r-- | src/pl/plperl/plperl.c | 4 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_handler.c | 3 | ||||
-rw-r--r-- | src/pl/plpython/plpython.c | 4 |
7 files changed, 105 insertions, 1 deletions
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 9d5f1a6cbc2..5de7b338079 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -694,6 +694,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. @@ -739,6 +742,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, @@ -790,6 +796,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) bool haspolyarg; int i; + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 92abd44a600..54b2054188e 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -963,7 +963,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 5a0e92a4fc7..0a2d4ef9565 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" @@ -2445,3 +2446,86 @@ 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 = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid)); + 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 = SearchSysCache1(LANGOID, ObjectIdGetDatum(procStruct->prolang)); + 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 4a412f8049c..84ef04de58b 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -624,6 +624,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 896d748f6ed..7ea428e845c 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -1846,6 +1846,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 = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) @@ -1925,6 +1928,7 @@ PG_FUNCTION_INFO_V1(plperlu_validator); Datum plperlu_validator(PG_FUNCTION_ARGS) { + /* call plperl validator with our fcinfo so it gets our oid */ return plperl_validator(fcinfo); } diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index e6e71432ff9..d5f696aaa8d 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -227,6 +227,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 = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 57801cc252e..7610031af19 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -519,6 +519,9 @@ plpython_validator(PG_FUNCTION_ARGS) Form_pg_proc procStruct; bool is_trigger; + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + if (!check_function_bodies) { PG_RETURN_VOID(); @@ -5012,6 +5015,7 @@ PG_FUNCTION_INFO_V1(plpython2_validator); Datum plpython2_validator(PG_FUNCTION_ARGS) { + /* call plpython validator with our fcinfo so it gets our oid */ return plpython_validator(fcinfo); } |