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:38 +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

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

index 45d224ef409e5dd299d257f81d7a44c712b608d9..e2e11163e9058548670dbd0856c6f6ff0b89e17c 100644 (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);
index 2151fd94f09d0e4a270c8654028881c2c1515c1d..db22a288885688d708902776d152c047293362f3 100644 (file)
@@ -929,7 +929,6 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
                    prorows);
 }
 
-
 /*
  * RemoveFunction
  *     Deletes a function.
index 00711a6f86c73369161272a9b9c14c731aaaaa3f..bcfbf5ac373bd9eb7d91283deabaae7b4e5d8d7f 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"
@@ -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;
+}
index cf309cdbd63ece89303d350016e37c5b816a9a68..83dba75f30dc41f8b17005f73a9de3ad2ba1312d 100644 (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
index 01c95ff6b14e1f4b8ef295d5e4d41e844ffe5b89..b65ea31a86cabaf5c1c8d8cc0ba831a7ba7cc4a7 100644 (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),
index 3b362e78e763ba72040f20bec1798ba12ffc5250..9e07a2dc6a58ad19635198de8f188f1c0ccfdc19 100644 (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),