mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
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:
parent
fea164a72a
commit
537cbd35c8
@ -178,7 +178,10 @@ CREATE LANGUAGE plsample
|
|||||||
or updated a function written in the procedural language.
|
or updated a function written in the procedural language.
|
||||||
The passed-in OID is the OID of the function's <classname>pg_proc</>
|
The passed-in OID is the OID of the function's <classname>pg_proc</>
|
||||||
row. The validator must fetch this row in the usual way, and do
|
row. The validator must fetch this row in the usual way, and do
|
||||||
whatever checking is appropriate. Typical checks include verifying
|
whatever checking is appropriate.
|
||||||
|
First, call <function>CheckFunctionValidatorAccess()</> to diagnose
|
||||||
|
explicit calls to the validator that the user could not achieve through
|
||||||
|
<command>CREATE FUNCTION</>. Typical checks then include verifying
|
||||||
that the function's argument and result types are supported by the
|
that the function's argument and result types are supported by the
|
||||||
language, and that the function's body is syntactically correct
|
language, and that the function's body is syntactically correct
|
||||||
in the language. If the validator finds the function to be okay,
|
in the language. If the validator finds the function to be okay,
|
||||||
|
@ -723,6 +723,9 @@ fmgr_internal_validator(PG_FUNCTION_ARGS)
|
|||||||
Datum tmp;
|
Datum tmp;
|
||||||
char *prosrc;
|
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
|
* We do not honor check_function_bodies since it's unlikely the function
|
||||||
* name will be found later if it isn't there now.
|
* name will be found later if it isn't there now.
|
||||||
@ -768,6 +771,9 @@ fmgr_c_validator(PG_FUNCTION_ARGS)
|
|||||||
char *prosrc;
|
char *prosrc;
|
||||||
char *probin;
|
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,
|
* 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,
|
* but the purpose of that switch is to be helpful for pg_dump loading,
|
||||||
@ -819,6 +825,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
|
|||||||
bool haspolyarg;
|
bool haspolyarg;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
|
||||||
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
|
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
elog(ERROR, "cache lookup failed for function %u", funcoid);
|
elog(ERROR, "cache lookup failed for function %u", funcoid);
|
||||||
|
@ -1017,7 +1017,6 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
|
|||||||
prorows);
|
prorows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Guts of function deletion.
|
* Guts of function deletion.
|
||||||
*
|
*
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
|
#include "utils/acl.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/fmgrtab.h"
|
#include "utils/fmgrtab.h"
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
@ -2468,3 +2469,86 @@ get_fn_expr_variadic(FmgrInfo *flinfo)
|
|||||||
else
|
else
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
@ -629,6 +629,7 @@ extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum);
|
|||||||
extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, 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 get_call_expr_arg_stable(fmNodePtr expr, int argnum);
|
||||||
extern bool get_fn_expr_variadic(FmgrInfo *flinfo);
|
extern bool get_fn_expr_variadic(FmgrInfo *flinfo);
|
||||||
|
extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Routines in dfmgr.c
|
* Routines in dfmgr.c
|
||||||
|
@ -1883,6 +1883,9 @@ plperl_validator(PG_FUNCTION_ARGS)
|
|||||||
bool is_event_trigger = false;
|
bool is_event_trigger = false;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
|
||||||
/* Get the new function's pg_proc entry */
|
/* Get the new function's pg_proc entry */
|
||||||
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
|
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
@ -1964,6 +1967,7 @@ PG_FUNCTION_INFO_V1(plperlu_validator);
|
|||||||
Datum
|
Datum
|
||||||
plperlu_validator(PG_FUNCTION_ARGS)
|
plperlu_validator(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
|
/* call plperl validator with our fcinfo so it gets our oid */
|
||||||
return plperl_validator(fcinfo);
|
return plperl_validator(fcinfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,6 +290,9 @@ plpgsql_validator(PG_FUNCTION_ARGS)
|
|||||||
bool is_event_trigger = false;
|
bool is_event_trigger = false;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
|
||||||
/* Get the new function's pg_proc entry */
|
/* Get the new function's pg_proc entry */
|
||||||
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
|
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
@ -160,6 +160,9 @@ plpython_validator(PG_FUNCTION_ARGS)
|
|||||||
Form_pg_proc procStruct;
|
Form_pg_proc procStruct;
|
||||||
bool is_trigger;
|
bool is_trigger;
|
||||||
|
|
||||||
|
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
|
||||||
if (!check_function_bodies)
|
if (!check_function_bodies)
|
||||||
{
|
{
|
||||||
PG_RETURN_VOID();
|
PG_RETURN_VOID();
|
||||||
@ -185,6 +188,7 @@ plpython_validator(PG_FUNCTION_ARGS)
|
|||||||
Datum
|
Datum
|
||||||
plpython2_validator(PG_FUNCTION_ARGS)
|
plpython2_validator(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
|
/* call plpython validator with our fcinfo so it gets our oid */
|
||||||
return plpython_validator(fcinfo);
|
return plpython_validator(fcinfo);
|
||||||
}
|
}
|
||||||
#endif /* PY_MAJOR_VERSION < 3 */
|
#endif /* PY_MAJOR_VERSION < 3 */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user