On Wed, May 30, 2012 at 07:34:16PM -0400, Noah Misch wrote: > ALTER FUNCTION OWNER TO on a C-language function conveys more trust than > meets the eye: > > BEGIN; > CREATE ROLE alice; > CREATE FUNCTION mylen(text) RETURNS integer LANGUAGE internal IMMUTABLE > STRICT AS 'textlen'; > ALTER FUNCTION mylen(text) OWNER TO alice; > COMMIT; > > SET SESSION AUTHORIZATION alice; > ALTER FUNCTION mylen(text) CALLED ON NULL INPUT; > SELECT mylen(NULL); -- SIGSEGV > > CREATE FUNCTION + ALTER FUNCTION OWNER TO is useful for creating another > user's untrusted-language SECURITY DEFINER function. ALTER FUNCTION CALLED ON > NULL INPUT ought to require that the user be eligible to redefine the function > completely.
Here's a patch implementing that restriction. To clarify, I see no need to repeat *all* the CREATE-time checks; for example, there's no need to recheck permission to use the return type. The language usage check is enough. I didn't feel the need to memorialize a test like the above in an actual regression test, but that's the one I used to verify the change. Considering the crash potential, I'd recommend backpatching this. Thanks, nm
diff --git a/doc/src/sgml/ref/alter_function.sgml b/doc/src/sgml/ref/alter_function.sgml index 013b6f8..cfc2a69 100644 *** a/doc/src/sgml/ref/alter_function.sgml --- b/doc/src/sgml/ref/alter_function.sgml *************** *** 54,59 **** ALTER FUNCTION <replaceable>name</replaceable> ( [ [ <replaceable class="paramet --- 54,61 ---- <para> You must own the function to use <command>ALTER FUNCTION</>. + Marking a function <literal>CALLED ON NULL INPUT</> also requires + permission to use the function's language when creating new functions. To change a function's schema, you must also have <literal>CREATE</> privilege on the new schema. To alter the owner, you must also be a direct or indirect member of the new diff --git a/src/backend/commands/functioncindex 13e30f4..b40bcdf 100644 *** a/src/backend/commands/functioncmds.c --- b/src/backend/commands/functioncmds.c *************** *** 70,75 **** static void AlterFunctionOwner_internal(Relation rel, HeapTuple tup, --- 70,107 ---- /* + * May the current user create functions of a given language? + */ + static void + check_language_usage(HeapTuple languageTuple) + { + Oid languageOid; + Form_pg_language languageStruct; + + languageOid = HeapTupleGetOid(languageTuple); + languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); + + if (languageStruct->lanpltrusted) + { + /* if trusted language, need USAGE privilege */ + AclResult aclresult; + + aclresult = pg_language_aclcheck(languageOid, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_LANGUAGE, + NameStr(languageStruct->lanname)); + } + else + { + /* if untrusted language, must be superuser */ + if (!superuser()) + aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE, + NameStr(languageStruct->lanname)); + } + } + + + /* * Examine the RETURNS clause of the CREATE FUNCTION statement * and return information about it as *prorettype_p and *returnsSet. * *************** *** 864,890 **** CreateFunction(CreateFunctionStmt *stmt, const char *queryString) (PLTemplateExists(language) ? errhint("Use CREATE LANGUAGE to load the language into the database.") : 0))); languageOid = HeapTupleGetOid(languageTuple); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); - - if (languageStruct->lanpltrusted) - { - /* if trusted language, need USAGE privilege */ - AclResult aclresult; - - aclresult = pg_language_aclcheck(languageOid, GetUserId(), ACL_USAGE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_LANGUAGE, - NameStr(languageStruct->lanname)); - } - else - { - /* if untrusted language, must be superuser */ - if (!superuser()) - aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE, - NameStr(languageStruct->lanname)); - } - languageValidator = languageStruct->lanvalidator; ReleaseSysCache(languageTuple); --- 896,905 ---- (PLTemplateExists(language) ? errhint("Use CREATE LANGUAGE to load the language into the database.") : 0))); + check_language_usage(languageTuple); + languageOid = HeapTupleGetOid(languageTuple); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); languageValidator = languageStruct->lanvalidator; ReleaseSysCache(languageTuple); *************** *** 1312,1318 **** AlterFunction(AlterFunctionStmt *stmt) --- 1327,1355 ---- if (volatility_item) procForm->provolatile = interpret_func_volatility(volatility_item); if (strict_item) + { + /* + * C-language functions that expect to be declared STRICT often omit + * NULL argument checks, crashing if they do receive a NULL. To + * protect such functions against less-privileged owners, clearing + * proisstrict requires the authority to define a new function of the + * same language. + */ + if (!intVal(strict_item->arg)) + { + HeapTuple langTuple; + + langTuple = SearchSysCache1(LANGOID, + PointerGetDatum(procForm->prolang)); + if (!HeapTupleIsValid(langTuple)) + elog(ERROR, "cache lookup failed for language %u", + procForm->prolang); + check_language_usage(langTuple); + ReleaseSysCache(langTuple); + } + procForm->proisstrict = intVal(strict_item->arg); + } if (security_def_item) procForm->prosecdef = intVal(security_def_item->arg); if (leakproof_item) *************** *** 1974,2002 **** ExecuteDoStmt(DoStmt *stmt) (PLTemplateExists(language) ? errhint("Use CREATE LANGUAGE to load the language into the database.") : 0))); codeblock->langOid = HeapTupleGetOid(languageTuple); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); codeblock->langIsTrusted = languageStruct->lanpltrusted; - if (languageStruct->lanpltrusted) - { - /* if trusted language, need USAGE privilege */ - AclResult aclresult; - - aclresult = pg_language_aclcheck(codeblock->langOid, GetUserId(), - ACL_USAGE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_LANGUAGE, - NameStr(languageStruct->lanname)); - } - else - { - /* if untrusted language, must be superuser */ - if (!superuser()) - aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE, - NameStr(languageStruct->lanname)); - } - /* get the handler function's OID */ laninline = languageStruct->laninline; if (!OidIsValid(laninline)) --- 2011,2022 ---- (PLTemplateExists(language) ? errhint("Use CREATE LANGUAGE to load the language into the database.") : 0))); + check_language_usage(languageTuple); + codeblock->langOid = HeapTupleGetOid(languageTuple); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); codeblock->langIsTrusted = languageStruct->lanpltrusted; /* get the handler function's OID */ laninline = languageStruct->laninline; if (!OidIsValid(laninline))
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers