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
This commit is contained in:
Noah Misch 2014-02-17 09:33:31 -05:00
parent ff35425c8f
commit 823b9dc256
6 changed files with 101 additions and 1 deletions

View File

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

View File

@ -929,7 +929,6 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
prorows);
}
/*
* RemoveFunction
* Deletes a function.

View File

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

View File

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

View File

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

View File

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