Skip to content

Commit 1d701d2

Browse files
committed
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
1 parent 15a8f97 commit 1d701d2

File tree

8 files changed

+109
-2
lines changed

8 files changed

+109
-2
lines changed

doc/src/sgml/plhandler.sgml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,10 @@ CREATE LANGUAGE plsample
178178
or updated a function written in the procedural language.
179179
The passed-in OID is the OID of the function's <classname>pg_proc</>
180180
row. The validator must fetch this row in the usual way, and do
181-
whatever checking is appropriate. Typical checks include verifying
181+
whatever checking is appropriate.
182+
First, call <function>CheckFunctionValidatorAccess()</> to diagnose
183+
explicit calls to the validator that the user could not achieve through
184+
<command>CREATE FUNCTION</>. Typical checks then include verifying
182185
that the function's argument and result types are supported by the
183186
language, and that the function's body is syntactically correct
184187
in the language. If the validator finds the function to be okay,

src/backend/catalog/pg_proc.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ fmgr_internal_validator(PG_FUNCTION_ARGS)
718718
Datum tmp;
719719
char *prosrc;
720720

721+
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
722+
PG_RETURN_VOID();
723+
721724
/*
722725
* We do not honor check_function_bodies since it's unlikely the function
723726
* name will be found later if it isn't there now.
@@ -763,6 +766,9 @@ fmgr_c_validator(PG_FUNCTION_ARGS)
763766
char *prosrc;
764767
char *probin;
765768

769+
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
770+
PG_RETURN_VOID();
771+
766772
/*
767773
* It'd be most consistent to skip the check if !check_function_bodies,
768774
* but the purpose of that switch is to be helpful for pg_dump loading,
@@ -814,6 +820,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
814820
bool haspolyarg;
815821
int i;
816822

823+
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
824+
PG_RETURN_VOID();
825+
817826
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
818827
if (!HeapTupleIsValid(tuple))
819828
elog(ERROR, "cache lookup failed for function %u", funcoid);

src/backend/commands/functioncmds.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,6 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
997997
prorows);
998998
}
999999

1000-
10011000
/*
10021001
* Guts of function deletion.
10031002
*

src/backend/utils/fmgr/fmgr.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "miscadmin.h"
2525
#include "nodes/nodeFuncs.h"
2626
#include "pgstat.h"
27+
#include "utils/acl.h"
2728
#include "utils/builtins.h"
2829
#include "utils/fmgrtab.h"
2930
#include "utils/guc.h"
@@ -2445,3 +2446,86 @@ get_call_expr_arg_stable(Node *expr, int argnum)
24452446

24462447
return false;
24472448
}
2449+
2450+
/*-------------------------------------------------------------------------
2451+
* Support routines for procedural language implementations
2452+
*-------------------------------------------------------------------------
2453+
*/
2454+
2455+
/*
2456+
* Verify that a validator is actually associated with the language of a
2457+
* particular function and that the user has access to both the language and
2458+
* the function. All validators should call this before doing anything
2459+
* substantial. Doing so ensures a user cannot achieve anything with explicit
2460+
* calls to validators that he could not achieve with CREATE FUNCTION or by
2461+
* simply calling an existing function.
2462+
*
2463+
* When this function returns false, callers should skip all validation work
2464+
* and call PG_RETURN_VOID(). This never happens at present; it is reserved
2465+
* for future expansion.
2466+
*
2467+
* In particular, checking that the validator corresponds to the function's
2468+
* language allows untrusted language validators to assume they process only
2469+
* superuser-chosen source code. (Untrusted language call handlers, by
2470+
* definition, do assume that.) A user lacking the USAGE language privilege
2471+
* would be unable to reach the validator through CREATE FUNCTION, so we check
2472+
* that to block explicit calls as well. Checking the EXECUTE privilege on
2473+
* the function is often superfluous, because most users can clone the
2474+
* function to get an executable copy. It is meaningful against users with no
2475+
* database TEMP right and no permanent schema CREATE right, thereby unable to
2476+
* create any function. Also, if the function tracks persistent state by
2477+
* function OID or name, validating the original function might permit more
2478+
* mischief than creating and validating a clone thereof.
2479+
*/
2480+
bool
2481+
CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid)
2482+
{
2483+
HeapTuple procTup;
2484+
HeapTuple langTup;
2485+
Form_pg_proc procStruct;
2486+
Form_pg_language langStruct;
2487+
AclResult aclresult;
2488+
2489+
/* Get the function's pg_proc entry */
2490+
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid));
2491+
if (!HeapTupleIsValid(procTup))
2492+
elog(ERROR, "cache lookup failed for function %u", functionOid);
2493+
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
2494+
2495+
/*
2496+
* Fetch pg_language entry to know if this is the correct validation
2497+
* function for that pg_proc entry.
2498+
*/
2499+
langTup = SearchSysCache1(LANGOID, ObjectIdGetDatum(procStruct->prolang));
2500+
if (!HeapTupleIsValid(langTup))
2501+
elog(ERROR, "cache lookup failed for language %u", procStruct->prolang);
2502+
langStruct = (Form_pg_language) GETSTRUCT(langTup);
2503+
2504+
if (langStruct->lanvalidator != validatorOid)
2505+
ereport(ERROR,
2506+
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
2507+
errmsg("language validation function %u called for language %u instead of %u",
2508+
validatorOid, procStruct->prolang,
2509+
langStruct->lanvalidator)));
2510+
2511+
/* first validate that we have permissions to use the language */
2512+
aclresult = pg_language_aclcheck(procStruct->prolang, GetUserId(),
2513+
ACL_USAGE);
2514+
if (aclresult != ACLCHECK_OK)
2515+
aclcheck_error(aclresult, ACL_KIND_LANGUAGE,
2516+
NameStr(langStruct->lanname));
2517+
2518+
/*
2519+
* Check whether we are allowed to execute the function itself. If we can
2520+
* execute it, there should be no possible side-effect of
2521+
* compiling/validation that execution can't have.
2522+
*/
2523+
aclresult = pg_proc_aclcheck(functionOid, GetUserId(), ACL_EXECUTE);
2524+
if (aclresult != ACLCHECK_OK)
2525+
aclcheck_error(aclresult, ACL_KIND_PROC, NameStr(procStruct->proname));
2526+
2527+
ReleaseSysCache(procTup);
2528+
ReleaseSysCache(langTup);
2529+
2530+
return true;
2531+
}

src/include/fmgr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
624624
extern Oid get_call_expr_argtype(fmNodePtr expr, int argnum);
625625
extern bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum);
626626
extern bool get_call_expr_arg_stable(fmNodePtr expr, int argnum);
627+
extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
627628

628629
/*
629630
* Routines in dfmgr.c

src/pl/plperl/plperl.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,9 @@ plperl_validator(PG_FUNCTION_ARGS)
18471847
bool istrigger = false;
18481848
int i;
18491849

1850+
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
1851+
PG_RETURN_VOID();
1852+
18501853
/* Get the new function's pg_proc entry */
18511854
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
18521855
if (!HeapTupleIsValid(tuple))
@@ -1926,6 +1929,7 @@ PG_FUNCTION_INFO_V1(plperlu_validator);
19261929
Datum
19271930
plperlu_validator(PG_FUNCTION_ARGS)
19281931
{
1932+
/* call plperl validator with our fcinfo so it gets our oid */
19291933
return plperl_validator(fcinfo);
19301934
}
19311935

src/pl/plpgsql/src/pl_handler.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ plpgsql_validator(PG_FUNCTION_ARGS)
227227
bool istrigger = false;
228228
int i;
229229

230+
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
231+
PG_RETURN_VOID();
232+
230233
/* Get the new function's pg_proc entry */
231234
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
232235
if (!HeapTupleIsValid(tuple))

src/pl/plpython/plpy_main.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ plpython_validator(PG_FUNCTION_ARGS)
159159
Form_pg_proc procStruct;
160160
bool is_trigger;
161161

162+
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
163+
PG_RETURN_VOID();
164+
162165
if (!check_function_bodies)
163166
{
164167
PG_RETURN_VOID();
@@ -184,6 +187,7 @@ plpython_validator(PG_FUNCTION_ARGS)
184187
Datum
185188
plpython2_validator(PG_FUNCTION_ARGS)
186189
{
190+
/* call plpython validator with our fcinfo so it gets our oid */
187191
return plpython_validator(fcinfo);
188192
}
189193
#endif /* PY_MAJOR_VERSION < 3 */

0 commit comments

Comments
 (0)