]> granicus.if.org Git - postgresql/blobdiff - src/backend/catalog/pg_proc.c
Prevent privilege escalation in explicit calls to PL validators.
[postgresql] / src / backend / catalog / pg_proc.c
index 2b216098939cf68594b5935304bf0db00c33bf30..abf2f497e411818c9ce58ec4286a8534c86b862a 100644 (file)
@@ -3,21 +3,22 @@
  * pg_proc.c
  *       routines to support manipulation of the pg_proc relation
  *
- * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.172 2010/02/26 02:00:37 momjian Exp $
+ *       src/backend/catalog/pg_proc.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-#include "access/heapam.h"
+#include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
@@ -34,6 +35,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 #include "utils/syscache.h"
 
 
@@ -41,6 +43,12 @@ Datum                fmgr_internal_validator(PG_FUNCTION_ARGS);
 Datum          fmgr_c_validator(PG_FUNCTION_ARGS);
 Datum          fmgr_sql_validator(PG_FUNCTION_ARGS);
 
+typedef struct
+{
+       char       *proname;
+       char       *prosrc;
+} parse_error_callback_arg;
+
 static void sql_function_parse_error_callback(void *arg);
 static int match_prosrc_to_query(const char *prosrc, const char *queryText,
                                          int cursorpos);
@@ -62,6 +70,7 @@ ProcedureCreate(const char *procedureName,
                                bool replace,
                                bool returnsSet,
                                Oid returnType,
+                               Oid proowner,
                                Oid languageObjectId,
                                Oid languageValidator,
                                const char *prosrc,
@@ -69,6 +78,7 @@ ProcedureCreate(const char *procedureName,
                                bool isAgg,
                                bool isWindowFunc,
                                bool security_definer,
+                               bool isLeakProof,
                                bool isStrict,
                                char volatility,
                                oidvector *parameterTypes,
@@ -84,12 +94,14 @@ ProcedureCreate(const char *procedureName,
        int                     parameterCount;
        int                     allParamCount;
        Oid                *allParams;
+       char       *paramModes = NULL;
        bool            genericInParam = false;
        bool            genericOutParam = false;
+       bool            anyrangeInParam = false;
+       bool            anyrangeOutParam = false;
        bool            internalInParam = false;
        bool            internalOutParam = false;
        Oid                     variadicType = InvalidOid;
-       Oid                     proowner = GetUserId();
        Acl                *proacl = NULL;
        Relation        rel;
        HeapTuple       tup;
@@ -120,6 +132,7 @@ ProcedureCreate(const char *procedureName,
                                                           FUNC_MAX_ARGS)));
        /* note: the above is correct, we do NOT count output arguments */
 
+       /* Deconstruct array inputs */
        if (allParameterTypes != PointerGetDatum(NULL))
        {
                /*
@@ -145,10 +158,26 @@ ProcedureCreate(const char *procedureName,
                allParams = parameterTypes->values;
        }
 
+       if (parameterModes != PointerGetDatum(NULL))
+       {
+               /*
+                * We expect the array to be a 1-D CHAR array; verify that. We don't
+                * need to use deconstruct_array() since the array data is just going
+                * to look like a C array of char values.
+                */
+               ArrayType  *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
+
+               if (ARR_NDIM(modesArray) != 1 ||
+                       ARR_DIMS(modesArray)[0] != allParamCount ||
+                       ARR_HASNULL(modesArray) ||
+                       ARR_ELEMTYPE(modesArray) != CHAROID)
+                       elog(ERROR, "parameterModes is not a 1-D char array");
+               paramModes = (char *) ARR_DATA_PTR(modesArray);
+       }
+
        /*
-        * Do not allow polymorphic return type unless at least one input argument
-        * is polymorphic.      Also, do not allow return type INTERNAL unless at
-        * least one input argument is INTERNAL.
+        * Detect whether we have polymorphic or INTERNAL arguments.  The first
+        * loop checks input arguments, the second output arguments.
         */
        for (i = 0; i < parameterCount; i++)
        {
@@ -160,6 +189,10 @@ ProcedureCreate(const char *procedureName,
                        case ANYENUMOID:
                                genericInParam = true;
                                break;
+                       case ANYRANGEOID:
+                               genericInParam = true;
+                               anyrangeInParam = true;
+                               break;
                        case INTERNALOID:
                                internalInParam = true;
                                break;
@@ -170,12 +203,11 @@ ProcedureCreate(const char *procedureName,
        {
                for (i = 0; i < allParamCount; i++)
                {
-                       /*
-                        * We don't bother to distinguish input and output params here, so
-                        * if there is, say, just an input INTERNAL param then we will
-                        * still set internalOutParam.  This is OK since we don't really
-                        * care.
-                        */
+                       if (paramModes == NULL ||
+                               paramModes[i] == PROARGMODE_IN ||
+                               paramModes[i] == PROARGMODE_VARIADIC)
+                               continue;               /* ignore input-only params */
+
                        switch (allParams[i])
                        {
                                case ANYARRAYOID:
@@ -184,6 +216,10 @@ ProcedureCreate(const char *procedureName,
                                case ANYENUMOID:
                                        genericOutParam = true;
                                        break;
+                               case ANYRANGEOID:
+                                       genericOutParam = true;
+                                       anyrangeOutParam = true;
+                                       break;
                                case INTERNALOID:
                                        internalOutParam = true;
                                        break;
@@ -191,6 +227,13 @@ ProcedureCreate(const char *procedureName,
                }
        }
 
+       /*
+        * Do not allow polymorphic return type unless at least one input argument
+        * is polymorphic.      ANYRANGE return type is even stricter: must have an
+        * ANYRANGE input (since we can't deduce the specific range type from
+        * ANYELEMENT).  Also, do not allow return type INTERNAL unless at least
+        * one input argument is INTERNAL.
+        */
        if ((IsPolymorphicType(returnType) || genericOutParam)
                && !genericInParam)
                ereport(ERROR,
@@ -198,6 +241,13 @@ ProcedureCreate(const char *procedureName,
                                 errmsg("cannot determine result data type"),
                                 errdetail("A function returning a polymorphic type must have at least one polymorphic argument.")));
 
+       if ((returnType == ANYRANGEOID || anyrangeOutParam) &&
+               !anyrangeInParam)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+                                errmsg("cannot determine result data type"),
+                                errdetail("A function returning \"anyrange\" must have at least one \"anyrange\" argument.")));
+
        if ((returnType == INTERNALOID || internalOutParam) && !internalInParam)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
@@ -218,23 +268,8 @@ ProcedureCreate(const char *procedureName,
                                                procedureName,
                                                format_type_be(parameterTypes->values[0]))));
 
-       if (parameterModes != PointerGetDatum(NULL))
+       if (paramModes != NULL)
        {
-               /*
-                * We expect the array to be a 1-D CHAR array; verify that. We don't
-                * need to use deconstruct_array() since the array data is just going
-                * to look like a C array of char values.
-                */
-               ArrayType  *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
-               char       *modes;
-
-               if (ARR_NDIM(modesArray) != 1 ||
-                       ARR_DIMS(modesArray)[0] != allParamCount ||
-                       ARR_HASNULL(modesArray) ||
-                       ARR_ELEMTYPE(modesArray) != CHAROID)
-                       elog(ERROR, "parameterModes is not a 1-D char array");
-               modes = (char *) ARR_DATA_PTR(modesArray);
-
                /*
                 * Only the last input parameter can be variadic; if it is, save its
                 * element type.  Errors here are just elog since caller should have
@@ -242,7 +277,7 @@ ProcedureCreate(const char *procedureName,
                 */
                for (i = 0; i < allParamCount; i++)
                {
-                       switch (modes[i])
+                       switch (paramModes[i])
                        {
                                case PROARGMODE_IN:
                                case PROARGMODE_INOUT:
@@ -272,7 +307,7 @@ ProcedureCreate(const char *procedureName,
                                        }
                                        break;
                                default:
-                                       elog(ERROR, "invalid parameter mode '%c'", modes[i]);
+                                       elog(ERROR, "invalid parameter mode '%c'", paramModes[i]);
                                        break;
                        }
                }
@@ -297,9 +332,11 @@ ProcedureCreate(const char *procedureName,
        values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost);
        values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows);
        values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType);
+       values[Anum_pg_proc_protransform - 1] = ObjectIdGetDatum(InvalidOid);
        values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg);
        values[Anum_pg_proc_proiswindow - 1] = BoolGetDatum(isWindowFunc);
        values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
+       values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof);
        values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);
        values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet);
        values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);
@@ -368,7 +405,8 @@ ProcedureCreate(const char *procedureName,
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                                         errmsg("cannot change return type of existing function"),
-                                        errhint("Use DROP FUNCTION first.")));
+                                        errhint("Use DROP FUNCTION %s first.",
+                                                        format_procedure(HeapTupleGetOid(oldtup)))));
 
                /*
                 * If it returns RECORD, check for possible change of record type
@@ -391,7 +429,8 @@ ProcedureCreate(const char *procedureName,
                                                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                                        errmsg("cannot change return type of existing function"),
                                errdetail("Row type defined by OUT parameters is different."),
-                                                errhint("Use DROP FUNCTION first.")));
+                                                errhint("Use DROP FUNCTION %s first.",
+                                                                format_procedure(HeapTupleGetOid(oldtup)))));
                }
 
                /*
@@ -433,7 +472,8 @@ ProcedureCreate(const char *procedureName,
                                                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                                           errmsg("cannot change name of input parameter \"%s\"",
                                                          old_arg_names[j]),
-                                                        errhint("Use DROP FUNCTION first.")));
+                                                        errhint("Use DROP FUNCTION %s first.",
+                                                               format_procedure(HeapTupleGetOid(oldtup)))));
                        }
                }
 
@@ -456,7 +496,8 @@ ProcedureCreate(const char *procedureName,
                                ereport(ERROR,
                                                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                                                 errmsg("cannot remove parameter defaults from existing function"),
-                                                errhint("Use DROP FUNCTION first.")));
+                                                errhint("Use DROP FUNCTION %s first.",
+                                                                format_procedure(HeapTupleGetOid(oldtup)))));
 
                        proargdefaults = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup,
                                                                                         Anum_pg_proc_proargdefaults,
@@ -482,7 +523,8 @@ ProcedureCreate(const char *procedureName,
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                                                         errmsg("cannot change data type of existing parameter default value"),
-                                                        errhint("Use DROP FUNCTION first.")));
+                                                        errhint("Use DROP FUNCTION %s first.",
+                                                               format_procedure(HeapTupleGetOid(oldtup)))));
                                newlc = lnext(newlc);
                        }
                }
@@ -558,7 +600,7 @@ ProcedureCreate(const char *procedureName,
         * shared dependencies do *not* need to change, and we leave them alone.)
         */
        if (is_update)
-               deleteDependencyRecordsFor(ProcedureRelationId, retval);
+               deleteDependencyRecordsFor(ProcedureRelationId, retval, true);
 
        myself.classId = ProcedureRelationId;
        myself.objectId = retval;
@@ -591,6 +633,11 @@ ProcedureCreate(const char *procedureName,
                recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
        }
 
+       /* dependency on parameter default expressions */
+       if (parameterDefaults)
+               recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
+                                                          NIL, DEPENDENCY_NORMAL);
+
        /* dependency on owner */
        if (!is_update)
                recordDependencyOnOwner(ProcedureRelationId, retval, proowner);
@@ -603,21 +650,57 @@ ProcedureCreate(const char *procedureName,
 
                nnewmembers = aclmembers(proacl, &newmembers);
                updateAclDependencies(ProcedureRelationId, retval, 0,
-                                                         proowner, true,
+                                                         proowner,
                                                          0, NULL,
                                                          nnewmembers, newmembers);
        }
 
+       /* dependency on extension */
+       recordDependencyOnCurrentExtension(&myself, is_update);
+
        heap_freetuple(tup);
 
+       /* Post creation hook for new function */
+       InvokeObjectPostCreateHook(ProcedureRelationId, retval, 0);
+
        heap_close(rel, RowExclusiveLock);
 
        /* Verify function body */
        if (OidIsValid(languageValidator))
        {
+               ArrayType  *set_items = NULL;
+               int                     save_nestlevel = 0;
+
                /* Advance command counter so new tuple can be seen by validator */
                CommandCounterIncrement();
+
+               /*
+                * Set per-function configuration parameters so that the validation is
+                * done with the environment the function expects.      However, if
+                * check_function_bodies is off, we don't do this, because that would
+                * create dump ordering hazards that pg_dump doesn't know how to deal
+                * with.  (For example, a SET clause might refer to a not-yet-created
+                * text search configuration.)  This means that the validator
+                * shouldn't complain about anything that might depend on a GUC
+                * parameter when check_function_bodies is off.
+                */
+               if (check_function_bodies)
+               {
+                       set_items = (ArrayType *) DatumGetPointer(proconfig);
+                       if (set_items)          /* Need a new GUC nesting level */
+                       {
+                               save_nestlevel = NewGUCNestLevel();
+                               ProcessGUCArray(set_items,
+                                                               (superuser() ? PGC_SUSET : PGC_USERSET),
+                                                               PGC_S_SESSION,
+                                                               GUC_ACTION_SAVE);
+                       }
+               }
+
                OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval));
+
+               if (set_items)
+                       AtEOXact_GUC(true, save_nestlevel);
        }
 
        return retval;
@@ -636,11 +719,13 @@ fmgr_internal_validator(PG_FUNCTION_ARGS)
 {
        Oid                     funcoid = PG_GETARG_OID(0);
        HeapTuple       tuple;
-       Form_pg_proc proc;
        bool            isnull;
        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.
@@ -649,7 +734,6 @@ fmgr_internal_validator(PG_FUNCTION_ARGS)
        tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
        if (!HeapTupleIsValid(tuple))
                elog(ERROR, "cache lookup failed for function %u", funcoid);
-       proc = (Form_pg_proc) GETSTRUCT(tuple);
 
        tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
        if (isnull)
@@ -682,12 +766,14 @@ fmgr_c_validator(PG_FUNCTION_ARGS)
        Oid                     funcoid = PG_GETARG_OID(0);
        void       *libraryhandle;
        HeapTuple       tuple;
-       Form_pg_proc proc;
        bool            isnull;
        Datum           tmp;
        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,
@@ -697,7 +783,6 @@ fmgr_c_validator(PG_FUNCTION_ARGS)
        tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
        if (!HeapTupleIsValid(tuple))
                elog(ERROR, "cache lookup failed for function %u", funcoid);
-       proc = (Form_pg_proc) GETSTRUCT(tuple);
 
        tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
        if (isnull)
@@ -729,14 +814,20 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
        Oid                     funcoid = PG_GETARG_OID(0);
        HeapTuple       tuple;
        Form_pg_proc proc;
+       List       *raw_parsetree_list;
        List       *querytree_list;
+       ListCell   *lc;
        bool            isnull;
        Datum           tmp;
        char       *prosrc;
+       parse_error_callback_arg callback_arg;
        ErrorContextCallback sqlerrcontext;
        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);
@@ -782,8 +873,11 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
                /*
                 * Setup error traceback support for ereport().
                 */
+               callback_arg.proname = NameStr(proc->proname);
+               callback_arg.prosrc = prosrc;
+
                sqlerrcontext.callback = sql_function_parse_error_callback;
-               sqlerrcontext.arg = tuple;
+               sqlerrcontext.arg = (void *) &callback_arg;
                sqlerrcontext.previous = error_context_stack;
                error_context_stack = &sqlerrcontext;
 
@@ -796,17 +890,37 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
                 * We can run the text through the raw parser though; this will at
                 * least catch silly syntactic errors.
                 */
+               raw_parsetree_list = pg_parse_query(prosrc);
+
                if (!haspolyarg)
                {
-                       querytree_list = pg_parse_and_rewrite(prosrc,
-                                                                                                 proc->proargtypes.values,
-                                                                                                 proc->pronargs);
+                       /*
+                        * OK to do full precheck: analyze and rewrite the queries, then
+                        * verify the result type.
+                        */
+                       SQLFunctionParseInfoPtr pinfo;
+
+                       /* But first, set up parameter information */
+                       pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+                       querytree_list = NIL;
+                       foreach(lc, raw_parsetree_list)
+                       {
+                               Node       *parsetree = (Node *) lfirst(lc);
+                               List       *querytree_sublist;
+
+                               querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+                                                                                                                                 prosrc,
+                                                                          (ParserSetupHook) sql_fn_parser_setup,
+                                                                                                                                 pinfo);
+                               querytree_list = list_concat(querytree_list,
+                                                                                        querytree_sublist);
+                       }
+
                        (void) check_sql_fn_retval(funcoid, proc->prorettype,
                                                                           querytree_list,
                                                                           NULL, NULL);
                }
-               else
-                       querytree_list = pg_parse_query(prosrc);
 
                error_context_stack = sqlerrcontext.previous;
        }
@@ -822,25 +936,14 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 static void
 sql_function_parse_error_callback(void *arg)
 {
-       HeapTuple       tuple = (HeapTuple) arg;
-       Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tuple);
-       bool            isnull;
-       Datum           tmp;
-       char       *prosrc;
+       parse_error_callback_arg *callback_arg = (parse_error_callback_arg *) arg;
 
        /* See if it's a syntax error; if so, transpose to CREATE FUNCTION */
-       tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
-       if (isnull)
-               elog(ERROR, "null prosrc");
-       prosrc = TextDatumGetCString(tmp);
-
-       if (!function_parse_error_transpose(prosrc))
+       if (!function_parse_error_transpose(callback_arg->prosrc))
        {
                /* If it's not a syntax error, push info onto context stack */
-               errcontext("SQL function \"%s\"", NameStr(proc->proname));
+               errcontext("SQL function \"%s\"", callback_arg->proname);
        }
-
-       pfree(prosrc);
 }
 
 /*