diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 0fac156feee..b1d8d0046f9 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -229,7 +229,8 @@ UPDATE OF column_name1 [, column_name2
To create a trigger on a table, the user must have the
- TRIGGER privilege on the table.
+ TRIGGER privilege on the table. The user must
+ also have EXECUTE privilege on the trigger function.
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e18fe725ab7..fb3204e873b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -104,8 +104,8 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
* if TRUE causes us to modify the given trigger name to ensure uniqueness.
*
* When isInternal is not true we require ACL_TRIGGER permissions on the
- * relation. For internal triggers the caller must apply any required
- * permission checks.
+ * relation, as well as ACL_EXECUTE on the trigger function. For internal
+ * triggers the caller must apply any required permission checks.
*
* Note: can return InvalidOid if we decided to not create a trigger at all,
* but a foreign-key constraint. This is a kluge for backwards compatibility.
@@ -309,6 +309,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Find and validate the trigger function.
*/
funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false);
+ if (!isInternal)
+ {
+ aclresult = pg_proc_aclcheck(funcoid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ NameListToString(stmt->funcname));
+ }
funcrettype = get_func_rettype(funcoid);
if (funcrettype != TRIGGEROID)
{