]> granicus.if.org Git - postgresql/commitdiff
Prevent privilege escalation in explicit calls to PL validators.
authorNoah Misch <noah@leadboat.com>
Mon, 17 Feb 2014 14:33:31 +0000 (09:33 -0500)
committerNoah Misch <noah@leadboat.com>
Mon, 17 Feb 2014 14:33:31 +0000 (09:33 -0500)
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

doc/src/sgml/plhandler.sgml
src/backend/catalog/pg_proc.c
src/backend/commands/functioncmds.c
src/backend/utils/fmgr/fmgr.c
src/include/fmgr.h
src/pl/plperl/plperl.c
src/pl/plpgsql/src/pl_handler.c
src/pl/plpython/plpy_main.c

index 024ef9d3b851de4a287ded0a9123266d0cda6ada..aa4bba3bee1ab0e07d95a684684a2e37b4eab01c 100644 (file)
@@ -178,7 +178,10 @@ CREATE LANGUAGE plsample
     or updated a function written in the procedural language.
     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
-    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
     language, and that the function's body is syntactically correct
     in the language.  If the validator finds the function to be okay,
index 698ebafb67877cb345cb1196460718271b11af3c..abf2f497e411818c9ce58ec4286a8534c86b862a 100644 (file)
@@ -723,6 +723,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.
@@ -768,6 +771,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,
@@ -819,6 +825,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);
index 350cb76ab8cfd5f001f7e5426570f04d8d7de823..4c8119a474d63071d96e4f8e17bb514f2a45f6fb 100644 (file)
@@ -1017,7 +1017,6 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
                                                   prorows);
 }
 
-
 /*
  * Guts of function deletion.
  *
index 06409af5bb8a5e52afcded38d4cae524c3dd2f39..c7024b2225778ff469ee2892203b43eccfe91c34 100644 (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"
@@ -2468,3 +2469,86 @@ get_fn_expr_variadic(FmgrInfo *flinfo)
        else
                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;
+}
index 455c089084055fb74e372f93721bf3f7149953b6..aed81cdc26b540555db83fa8b444c363038985eb 100644 (file)
@@ -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_call_expr_arg_stable(fmNodePtr expr, int argnum);
 extern bool get_fn_expr_variadic(FmgrInfo *flinfo);
+extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
 
 /*
  * Routines in dfmgr.c
index d9aa5efa324347287c66b3a3ff44fa9ed4526d1f..ed6884e863a60b8e20c37471d0ce468035f0d0f2 100644 (file)
@@ -1883,6 +1883,9 @@ plperl_validator(PG_FUNCTION_ARGS)
        bool            is_event_trigger = 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))
@@ -1964,6 +1967,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);
 }
 
index f02203a5fb8eeed8a7de3d6f07a6be2ca5bdddf0..f21393ae41d5fee706a06266086ff509f024c413 100644 (file)
@@ -290,6 +290,9 @@ plpgsql_validator(PG_FUNCTION_ARGS)
        bool            is_event_trigger = 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))
index 0dad843956544a6b4b9d29426d804b91a1dba22b..4438721589e3c865cd223e8689b7cf0bc0d6b945 100644 (file)
@@ -160,6 +160,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();
@@ -185,6 +188,7 @@ plpython_validator(PG_FUNCTION_ARGS)
 Datum
 plpython2_validator(PG_FUNCTION_ARGS)
 {
+       /* call plpython validator with our fcinfo so it gets our oid */
        return plpython_validator(fcinfo);
 }
 #endif   /* PY_MAJOR_VERSION < 3 */