POC for a function trust mechanism

Started by Tom Laneover 7 years ago12 messages
#1Tom Lane
tgl@sss.pgh.pa.us
1 attachment(s)

This is sort of a counter-proposal to Noah's discussion of search path
security checking in <20180805080441.GH1688868@rfd.leadboat.com>.
(There's no technical reason we couldn't do both things, but I think
this'd be more useful to most people.)

Some back story here is that the PG security team has been aware of the
issues in CVE-2018-1058 for an embarrassing number of years, and we'd
been vainly working to find a fix that was both non-invasive to users
and practical to back-patch. Eventually our hands were forced by an
outside security researcher who discovered some of those problems, and
naturally wanted to publish on a fairly short time scale. So we ended
up with the decidedly not non-invasive approach of locking down
search_path in especially critical places, and otherwise telling people
that they had to worry about this themselves. Of the various ideas that
we'd kicked around and not been able to finish, the one that seemed most
promising to me was to invent a "function trust" mechanism.

The core idea here is to prevent security problems not by changing an
application's rules for operator/function name resolution, but by
detecting an attempted compromise and preventing the trojan-horse code
from being executed. Essentially, a user or application is to declare
a list of roles that it trusts functions owned by, and the system will
then refuse to execute functions owned by other not-trusted roles.
So, if $badguy creates a trojan-horse operator and manages to capture
a call from your SQL code, he'll nonetheless not be able to execute
code as you.

To reduce the overhead of the mechanism and chance of unintentionally
breaking things, superuser-owned functions (particularly, all built-in
functions) are always trusted by everybody. A superuser who wants to
become you can do so trivially, with no need for a trojan horse, so
this restriction isn't creating any new security hole.

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

So we'd kind of decided that the GUC solution wasn't good enough, but
it didn't seem like catalog additions would be feasible as a back-patched
security fix, which is why this didn't go anywhere. But it could work
as a new feature.

Anyway, I had written a small POC that did use a GUC for this, and just
checked function calls without any attempts to switch the active
trusted_roles setting in places like autovacuum. I've rebased it up to
HEAD and here it is.

regards, tom lane

Attachments:

trusted-roles-0.1.patchtext/x-diff; charset=us-ascii; name=trusted-roles-0.1.patchDownload
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 9a754da..ea24bc3 100644
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
*************** show_role(void)
*** 950,952 ****
--- 950,1075 ----
  	/* Otherwise we can just use the GUC string */
  	return role_string ? role_string : "none";
  }
+ 
+ 
+ /*
+  * TRUSTED_ROLES
+  */
+ 
+ /* XXX this needs to be replaced with some more complex cache structure */
+ static const char *trusted_roles = "";
+ 
+ /*
+  * check_trusted_roles: GUC check_hook for trusted_roles
+  */
+ bool
+ check_trusted_roles(char **newval, void **extra, GucSource source)
+ {
+ 	char	   *rawname;
+ 	List	   *namelist;
+ 
+ 	/* Need a modifiable copy of string */
+ 	rawname = pstrdup(*newval);
+ 
+ 	/* Parse string into list of identifiers */
+ 	if (!SplitIdentifierString(rawname, ',', &namelist))
+ 	{
+ 		/* syntax error in name list */
+ 		GUC_check_errdetail("List syntax is invalid.");
+ 		pfree(rawname);
+ 		list_free(namelist);
+ 		return false;
+ 	}
+ 
+ 	/*
+ 	 * No additional checks are possible now, because we might not be inside a
+ 	 * transaction; so we're done.
+ 	 */
+ 
+ 	pfree(rawname);
+ 	list_free(namelist);
+ 
+ 	return true;
+ }
+ 
+ /*
+  * assign_trusted_roles: GUC assign_hook for trusted_roles
+  */
+ void
+ assign_trusted_roles(const char *newval, void *extra)
+ {
+ 	/* Update the active value */
+ 	trusted_roles = newval;
+ }
+ 
+ /*
+  * role_trusts_role: does caller trust callee?
+  *
+  * Basically, this checks whether callee is a member of any role listed
+  * in trusted_roles; "$user" is resolved as being the caller.  However,
+  * if callee is a superuser, it'll be trusted regardless of the GUC.
+  */
+ bool
+ role_trusts_role(Oid caller, Oid callee)
+ {
+ 	bool		result = false;
+ 	char	   *rawname;
+ 	List	   *namelist;
+ 	ListCell   *lc;
+ 
+ 	/* Superusers are always trusted by everybody */
+ 	if (superuser_arg(callee))
+ 		return true;
+ 
+ 	/* Need a modifiable copy of string */
+ 	rawname = pstrdup(trusted_roles);
+ 
+ 	/* Parse string into list of identifiers */
+ 	if (!SplitIdentifierString(rawname, ',', &namelist))
+ 	{
+ 		/* syntax error in name list */
+ 		/* this should not happen if GUC checked check_trusted_roles */
+ 		elog(ERROR, "invalid list syntax");
+ 	}
+ 
+ 	/* Examine each identifier */
+ 	foreach(lc, namelist)
+ 	{
+ 		char	   *curname = (char *) lfirst(lc);
+ 		Oid			roleId;
+ 
+ 		/* Check special cases */
+ 		if (strcmp(curname, "$user") == 0)
+ 		{
+ 			/* Take this as the caller */
+ 			roleId = caller;
+ 		}
+ 		else if (strcmp(curname, "public") == 0)
+ 		{
+ 			/* We should trust everybody */
+ 			result = true;
+ 			break;
+ 		}
+ 		else
+ 		{
+ 			/* Normal role name, look it up */
+ 			roleId = get_role_oid(curname, true);
+ 			/* As with search_path, ignore unknown names for ease of use */
+ 			if (!OidIsValid(roleId))
+ 				continue;
+ 		}
+ 
+ 		/* No point in repeating superuserness check */
+ 		if (is_member_of_role_nosuper(callee, roleId))
+ 		{
+ 			/* We should trust callee */
+ 			result = true;
+ 			break;
+ 		}
+ 	}
+ 
+ 	pfree(rawname);
+ 	list_free(namelist);
+ 
+ 	return result;
+ }
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a04ad6e..444d1af 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 26,31 ****
--- 26,32 ----
  #include "catalog/pg_operator.h"
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
+ #include "commands/variable.h"
  #include "executor/executor.h"
  #include "executor/functions.h"
  #include "funcapi.h"
*************** simplify_function(Oid funcid, Oid result
*** 4063,4068 ****
--- 4064,4081 ----
  		*args_p = args;
  	}
  
+ 	/*
+ 	 * Don't try to simplify an untrusted function; evaluate_function() would
+ 	 * throw an error, but we'd rather postpone that failure to execution.  We
+ 	 * mustn't inline either, because that would bypass the trust check.  (XXX
+ 	 * maybe we should do the ACL check here too, to postpone that failure?)
+ 	 */
+ 	if (!role_trusts_role(GetUserId(), func_form->proowner))
+ 	{
+ 		ReleaseSysCache(func_tuple);
+ 		return NULL;
+ 	}
+ 
  	/* Now attempt simplification of the function call proper. */
  
  	newexpr = evaluate_function(funcid, result_type, result_typmod,
*************** inline_set_returning_function(PlannerInf
*** 5035,5041 ****
  		funcform->prorettype == VOIDOID ||
  		funcform->prosecdef ||
  		!funcform->proretset ||
! 		!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
  	{
  		ReleaseSysCache(func_tuple);
  		return NULL;
--- 5048,5055 ----
  		funcform->prorettype == VOIDOID ||
  		funcform->prosecdef ||
  		!funcform->proretset ||
! 		!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL) ||
! 		!role_trusts_role(GetUserId(), funcform->proowner))
  	{
  		ReleaseSysCache(func_tuple);
  		return NULL;
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 6cbbd5b..32dee72 100644
*** a/src/backend/utils/fmgr/fmgr.c
--- b/src/backend/utils/fmgr/fmgr.c
***************
*** 18,27 ****
--- 18,29 ----
  #include "access/tuptoaster.h"
  #include "catalog/pg_language.h"
  #include "catalog/pg_proc.h"
+ #include "commands/variable.h"
  #include "executor/functions.h"
  #include "lib/stringinfo.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
+ #include "parser/parse_func.h"
  #include "pgstat.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
*************** fmgr_info_cxt_security(Oid functionId, F
*** 164,170 ****
  	if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
  	{
  		/*
! 		 * Fast path for builtin functions: don't bother consulting pg_proc
  		 */
  		finfo->fn_nargs = fbp->nargs;
  		finfo->fn_strict = fbp->strict;
--- 166,173 ----
  	if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
  	{
  		/*
! 		 * Fast path for builtin functions: don't bother consulting pg_proc,
! 		 * and assume the function is trusted.
  		 */
  		finfo->fn_nargs = fbp->nargs;
  		finfo->fn_strict = fbp->strict;
*************** fmgr_info_cxt_security(Oid functionId, F
*** 186,191 ****
--- 189,212 ----
  	finfo->fn_retset = procedureStruct->proretset;
  
  	/*
+ 	 * Check to see if the current user trusts the owner of the function.  We
+ 	 * can skip this when recursing, since it was checked already.  If we do
+ 	 * throw an error, go to some lengths to identify the function exactly.
+ 	 */
+ 	if (!ignore_security &&
+ 		!role_trusts_role(GetUserId(), procedureStruct->proowner))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 				 errmsg("role %s does not trust role %s, which owns function %s.%s",
+ 						GetUserNameFromId(GetUserId(), false),
+ 						GetUserNameFromId(procedureStruct->proowner, false),
+ 						get_namespace_name(procedureStruct->pronamespace),
+ 						funcname_signature_string(NameStr(procedureStruct->proname),
+ 												  procedureStruct->pronargs,
+ 												  NIL,
+ 												  procedureStruct->proargtypes.values))));
+ 
+ 	/*
  	 * If it has prosecdef set, non-null proconfig, or if a plugin wants to
  	 * hook function entry/exit, use fmgr_security_definer call handler ---
  	 * unless we are being called again by fmgr_security_definer or
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c5ba149..4f7399d 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static char *client_encoding_string;
*** 508,513 ****
--- 508,514 ----
  static char *datestyle_string;
  static char *locale_collate;
  static char *locale_ctype;
+ static char *trusted_roles_string;
  static char *server_encoding_string;
  static char *server_version_string;
  static int	server_version_num;
*************** static struct config_string ConfigureNam
*** 3493,3498 ****
--- 3494,3510 ----
  	},
  
  	{
+ 		{"trusted_roles", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ 			gettext_noop("Only functions owned by roles in this list will be executed."),
+ 			NULL,
+ 			GUC_LIST_INPUT | GUC_LIST_QUOTE
+ 		},
+ 		&trusted_roles_string,
+ 		"\"$user\"",
+ 		check_trusted_roles, assign_trusted_roles, NULL
+ 	},
+ 
+ 	{
  		/* Can't be set in postgresql.conf */
  		{"server_encoding", PGC_INTERNAL, CLIENT_CONN_LOCALE,
  			gettext_noop("Sets the server (database) character set encoding."),
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
index fbe83c9..07329ec 100644
*** a/src/backend/utils/misc/superuser.c
--- b/src/backend/utils/misc/superuser.c
*************** superuser_arg(Oid roleid)
*** 63,70 ****
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
  		return last_roleid_is_super;
  
! 	/* Special escape path in case you deleted all your users. */
! 	if (!IsUnderPostmaster && roleid == BOOTSTRAP_SUPERUSERID)
  		return true;
  
  	/* OK, look up the information in pg_authid */
--- 63,78 ----
  	if (OidIsValid(last_roleid) && last_roleid == roleid)
  		return last_roleid_is_super;
  
! 	/*
! 	 * Quick out for the bootstrap superuser, too.  Aside from making trust
! 	 * checks for builtin functions fast, this provides a special escape path
! 	 * in case you deleted all your users.  Standalone backends hardwire their
! 	 * user OID as BOOTSTRAP_SUPERUSERID, and this check ensures that that OID
! 	 * will be given superuser privileges, even if there is no underlying
! 	 * catalog entry.  (This means you can't usefully revoke the bootstrap
! 	 * superuser's superuserness, but that seems fine.)
! 	 */
! 	if (roleid == BOOTSTRAP_SUPERUSERID)
  		return true;
  
  	/* OK, look up the information in pg_authid */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9baf7b2..b64e08d 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** setup_connection(Archive *AH, const char
*** 1119,1124 ****
--- 1119,1131 ----
  	}
  
  	/*
+ 	 * If supported, disable trust for all non-superuser-owned functions.
+ 	 * Rather than trying to track which minor versions trusted_roles was
+ 	 * introduced in, issue the SET unconditionally, and ignore any error.
+ 	 */
+ 	PQclear(PQexec(conn, "SET trusted_roles = ''"));
+ 
+ 	/*
  	 * Start transaction-snapshot mode transaction to dump consistent data.
  	 */
  	ExecuteSqlStatement(AH, "BEGIN");
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index eb29d31..6ff1edd 100644
*** a/src/bin/pg_dump/pg_dumpall.c
--- b/src/bin/pg_dump/pg_dumpall.c
*************** connectDatabase(const char *dbname, cons
*** 1725,1730 ****
--- 1725,1738 ----
  
  	PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL));
  
+ 	/*
+ 	 * If supported, disable trust for all non-superuser-owned functions (not
+ 	 * that there should be any in pg_catalog, but let's be paranoid).  Rather
+ 	 * than trying to track which minor versions trusted_roles was introduced
+ 	 * in, issue the SET unconditionally, and ignore any error.
+ 	 */
+ 	PQclear(PQexec(conn, "SET trusted_roles = ''"));
+ 
  	return conn;
  }
  
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 4ea3b02..425bf1a 100644
*** a/src/include/commands/variable.h
--- b/src/include/commands/variable.h
*************** extern void assign_session_authorization
*** 36,40 ****
--- 36,43 ----
  extern bool check_role(char **newval, void **extra, GucSource source);
  extern void assign_role(const char *newval, void *extra);
  extern const char *show_role(void);
+ extern bool check_trusted_roles(char **newval, void **extra, GucSource source);
+ extern void assign_trusted_roles(const char *newval, void *extra);
+ extern bool role_trusts_role(Oid caller, Oid callee);
  
  #endif							/* VARIABLE_H */
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index ac8968d..c1abb60 100644
*** a/src/test/regress/expected/privileges.out
--- b/src/test/regress/expected/privileges.out
*************** CREATE FUNCTION leak(integer,integer) RE
*** 197,202 ****
--- 197,203 ----
    LANGUAGE plpgsql immutable;
  CREATE OPERATOR <<< (procedure = leak, leftarg = integer, rightarg = integer,
                       restrict = scalarltsel);
+ SET trusted_roles = "$user", regress_priv_user1;  -- allow execution of leak()
  -- view with leaky operator
  CREATE VIEW atest12v AS
    SELECT * FROM atest12 WHERE b <<< 5;
*************** EXPLAIN (COSTS OFF) SELECT * FROM atest1
*** 281,286 ****
--- 282,288 ----
  -- clean up (regress_priv_user1's objects are all dropped later)
  DROP FUNCTION leak2(integer, integer) CASCADE;
  NOTICE:  drop cascades to operator >>>(integer,integer)
+ RESET trusted_roles;
  -- groups
  SET SESSION AUTHORIZATION regress_priv_user3;
  CREATE TABLE atest3 (one int, two int, three int);
*************** CREATE FUNCTION priv_testfunc4(boolean) 
*** 674,679 ****
--- 676,682 ----
    AS 'select col1 from atest2 where col2 = $1;'
    LANGUAGE sql SECURITY DEFINER;
  GRANT EXECUTE ON FUNCTION priv_testfunc4(boolean) TO regress_priv_user3;
+ SET trusted_roles = "$user", regress_priv_user1;  -- allow execution of priv_testfunc4
  SET SESSION AUTHORIZATION regress_priv_user2;
  SELECT priv_testfunc1(5), priv_testfunc2(5); -- ok
   priv_testfunc1 | priv_testfunc2 
*************** SELECT priv_testagg1(x) FROM (VALUES (1)
*** 719,724 ****
--- 722,728 ----
  (1 row)
  
  CALL priv_testproc1(6); -- ok
+ RESET trusted_roles;
  DROP FUNCTION priv_testfunc1(int); -- fail
  ERROR:  must be owner of function priv_testfunc1
  DROP AGGREGATE priv_testagg1(int); -- fail
*************** SELECT has_table_privilege('regress_priv
*** 1176,1181 ****
--- 1180,1186 ----
  SET SESSION AUTHORIZATION regress_priv_user4;
  CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
  	'GRANT regress_priv_group2 TO regress_priv_user5';
+ SET trusted_roles = "$user", regress_priv_user4;  -- allow execution of dogrant_ok
  GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
  SET ROLE regress_priv_group2;
  GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
*************** ERROR:  must have admin option on role "
*** 1203,1208 ****
--- 1208,1214 ----
  CONTEXT:  SQL function "dogrant_fails" statement 1
  DROP FUNCTION dogrant_fails();
  SET SESSION AUTHORIZATION regress_priv_user4;
+ RESET trusted_roles;
  DROP FUNCTION dogrant_ok();
  REVOKE regress_priv_group2 FROM regress_priv_user5;
  -- has_sequence_privilege tests
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index f7f3bbb..434676e 100644
*** a/src/test/regress/sql/privileges.sql
--- b/src/test/regress/sql/privileges.sql
*************** CREATE FUNCTION leak(integer,integer) RE
*** 143,148 ****
--- 143,149 ----
    LANGUAGE plpgsql immutable;
  CREATE OPERATOR <<< (procedure = leak, leftarg = integer, rightarg = integer,
                       restrict = scalarltsel);
+ SET trusted_roles = "$user", regress_priv_user1;  -- allow execution of leak()
  
  -- view with leaky operator
  CREATE VIEW atest12v AS
*************** EXPLAIN (COSTS OFF) SELECT * FROM atest1
*** 187,192 ****
--- 188,195 ----
  -- clean up (regress_priv_user1's objects are all dropped later)
  DROP FUNCTION leak2(integer, integer) CASCADE;
  
+ RESET trusted_roles;
+ 
  
  -- groups
  
*************** CREATE FUNCTION priv_testfunc4(boolean) 
*** 462,467 ****
--- 465,471 ----
    AS 'select col1 from atest2 where col2 = $1;'
    LANGUAGE sql SECURITY DEFINER;
  GRANT EXECUTE ON FUNCTION priv_testfunc4(boolean) TO regress_priv_user3;
+ SET trusted_roles = "$user", regress_priv_user1;  -- allow execution of priv_testfunc4
  
  SET SESSION AUTHORIZATION regress_priv_user2;
  SELECT priv_testfunc1(5), priv_testfunc2(5); -- ok
*************** SELECT priv_testfunc1(5); -- ok
*** 481,486 ****
--- 485,492 ----
  SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok
  CALL priv_testproc1(6); -- ok
  
+ RESET trusted_roles;
+ 
  DROP FUNCTION priv_testfunc1(int); -- fail
  DROP AGGREGATE priv_testagg1(int); -- fail
  DROP PROCEDURE priv_testproc1(int); -- fail
*************** SELECT has_table_privilege('regress_priv
*** 748,753 ****
--- 754,761 ----
  SET SESSION AUTHORIZATION regress_priv_user4;
  CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
  	'GRANT regress_priv_group2 TO regress_priv_user5';
+ SET trusted_roles = "$user", regress_priv_user4;  -- allow execution of dogrant_ok
+ 
  GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION
  SET ROLE regress_priv_group2;
  GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege
*************** SELECT dogrant_fails();			-- fails: no s
*** 766,771 ****
--- 774,780 ----
  DROP FUNCTION dogrant_fails();
  
  SET SESSION AUTHORIZATION regress_priv_user4;
+ RESET trusted_roles;
  DROP FUNCTION dogrant_ok();
  REVOKE regress_priv_group2 FROM regress_priv_user5;
  
#2Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#1)
Re: POC for a function trust mechanism

On Wed, Aug 8, 2018 at 01:15:38PM -0400, Tom Lane wrote:

This is sort of a counter-proposal to Noah's discussion of search path
security checking in <20180805080441.GH1688868@rfd.leadboat.com>.
(There's no technical reason we couldn't do both things, but I think
this'd be more useful to most people.)

Yes, the query from Noah below confirmed that schema qualification just
isn't a realistic approach:

CREATE FUNCTION latitude(earth)
RETURNS float8
LANGUAGE SQL
IMMUTABLE STRICT
PARALLEL SAFE
AS 'SELECT CASE
WHEN @cube_schema(at)(dot)cube_ll_coord($1, 3) OPERATOR(pg_catalog./)
@extschema(at)(dot)earth() OPERATOR(pg_catalog.<) -1 THEN -90::pg_catalog.float8
WHEN @cube_schema(at)(dot)cube_ll_coord($1, 3) OPERATOR(pg_catalog./)
@extschema(at)(dot)earth() OPERATOR(pg_catalog.>) 1 THEN 90::pg_catalog.float8
ELSE pg_catalog.degrees(pg_catalog.asin(@cube_schema(at)(dot)cube_ll_coord($1, 3)
OPERATOR(pg_catalog./) @extschema(at)(dot)earth()))
END';

Of course, with the limitations of backpatching and security-only
discussion, that was the best we could do in the past.

The core idea here is to prevent security problems not by changing an
application's rules for operator/function name resolution, but by
detecting an attempted compromise and preventing the trojan-horse code
from being executed. Essentially, a user or application is to declare
a list of roles that it trusts functions owned by, and the system will
then refuse to execute functions owned by other not-trusted roles.
So, if $badguy creates a trojan-horse operator and manages to capture
a call from your SQL code, he'll nonetheless not be able to execute
code as you.

Yes, this is the only reasonable approach I can think of.

To reduce the overhead of the mechanism and chance of unintentionally
breaking things, superuser-owned functions (particularly, all built-in
functions) are always trusted by everybody. A superuser who wants to
become you can do so trivially, with no need for a trojan horse, so
this restriction isn't creating any new security hole.

Agreed.

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

Yes, works for me.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

I can't think of any other places we do transitive permissions, except
for role membership. I don't see the logic in adding such transitivity
to function/operator calls, or even a per-function GUC. I assume most
sites have a small number of extensions installed by a predefined group
of users, usually superusers. If there is a larger group, a group role
should be created and those people put in the role, and the group role
trusted.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#3David Kohn
djk447@gmail.com
In reply to: Bruce Momjian (#2)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 12:11 PM Bruce Momjian <bruce@momjian.us> wrote:

...

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

Yes, works for me.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

I can't think of any other places we do transitive permissions, except
for role membership. I don't see the logic in adding such transitivity
to function/operator calls, or even a per-function GUC. I assume most
sites have a small number of extensions installed by a predefined group
of users, usually superusers. If there is a larger group, a group role
should be created and those people put in the role, and the group role
trusted.

I am wondering how this will interact with the inheritance of roles. For
instance, if two users are members of the same role, and one creates a
function the expectation would be that other users in the same role will
not trust that function. However, do I trust functions that are owned by
the roles that I am a member of? Or do I have to list any nested roles
explicitly? If the former, I suppose we'd have to modify how alter function
set owner works. It is currently allowed for roles that you are a member of
(and would then create a security hole). However, not trusting functions
owned by roles that I am a member of seems to also be a bit
counterintuitive.
Best,
David

#4Bruce Momjian
bruce@momjian.us
In reply to: David Kohn (#3)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 02:12:41PM -0400, David Kohn wrote:

On Thu, Aug 9, 2018 at 12:11 PM Bruce Momjian <bruce@momjian.us> wrote:
I can't think of any other places we do transitive permissions, except
for role membership.� I don't see the logic in adding such transitivity
to function/operator calls, or even a per-function GUC.� I assume most
sites have a small number of extensions installed by a predefined group
of users, usually superusers. If there is a larger group, a group role
should be created and those people put in the role, and the group role
trusted.

I am wondering how this will interact with the inheritance of roles. For
instance, if two users are members of the same role, and one creates a function
the expectation would be that other users in the same role will not trust that
function.

Well, right now, if you want to give members of a role rights to
something, you have to specifically grant rights to that role. I would
assume the same thing would happen here --- if you want to trust a group
role, you have to mention that group role in the GUC list (not
function-level GUC).

Do we allow any GUC on a function? Would not allowing this be confusing?

If we did transitive permissions, I could trust someone, and that person
could call a function of someone else they trust, and after a while you
don't know who you are trusting, which is why I think complex setups
like that are unwise.

However, do I trust functions that are owned by the roles that I am a
member of? Or do I have to list any nested roles explicitly? If the former, I
suppose we'd have to modify how alter function set owner works. It is currently
allowed for roles that you are a member of (and would then create a security
hole). However, not trusting functions owned by roles that I am a member of
seems to also be a bit counterintuitive.

Well, if someone adds me to the 'bad' role, do I have any control over
that? Seems someone adding me to their role is not something I am
requesting. Let's look at the docs on GRANT ROLE:

GRANT on Roles
This variant of the GRANT command grants membership in a role to
one or more other roles. Membership in a role is significant because
it conveys the privileges granted to a role to each of its members.

If WITH ADMIN OPTION is specified, the member can in turn grant
membership in the role to others, and revoke membership in the role
as well. Without the admin option, ordinary users cannot do that. A
role is not considered to hold WITH ADMIN OPTION on itself, but it
may grant or revoke membership in itself from a database session
where the session user matches the role. Database superusers can
grant or revoke membership in any role to anyone. Roles having
CREATEROLE privilege can grant or revoke membership in any role
that is not a superuser.

Unlike the case with privileges, membership in a role cannot be
granted to PUBLIC. Note also that this form of the command does
not allow the noise word GROUP.

The point is that it is granting the role _access_ to something, not
something trust that the role accepts. The WITH ADMIN OPTION would
allow ordinary users to add roles for whoever they want to attack.

Basically, as it is now, someone adding me to their role membership has
no downside for me. To trust my own role membership adds a downside to
role membership that I don't think we want to do --- it makes role
membership too complex in what it grants _and_ trusts.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#5Nico Williams
nico@cryptonector.com
In reply to: Tom Lane (#1)
Re: POC for a function trust mechanism

On Wed, Aug 08, 2018 at 01:15:38PM -0400, Tom Lane wrote:

This is sort of a counter-proposal to Noah's discussion of search path
security checking in <20180805080441.GH1688868@rfd.leadboat.com>.
(There's no technical reason we couldn't do both things, but I think
this'd be more useful to most people.)

So, this is why I always fully-qualify all references to functions,
tables, etc. I also always set a search_path on each function just in
case I accidentally leave a non-fully-qualified symbol.

I would like to have a way to request that all non-fully-qualified
symbols be resolved at function create/replace time and that the
resolution results be made permanent for the function. If I have
several schemas in a search_path at function definition time, this would
not allow me to move dependencies around without replacing the
dependents -- that's OK for me.

Nico
--

#6David Kohn
djk447@gmail.com
In reply to: Bruce Momjian (#4)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 3:04 PM Bruce Momjian <bruce@momjian.us> wrote:

Well, right now, if you want to give members of a role rights to
something, you have to specifically grant rights to that role. I would
assume the same thing would happen here --- if you want to trust a group
role, you have to mention that group role in the GUC list (not
function-level GUC).

Sure, but if I grant execute on a function to a role, members of that role
will be able to execute that function. Now, each member will (potentially)
need to update their trust list before doing that. Which seems a bit odd.
Or will I be able to modify the some sort of default trust list of the
group role? If not, it seems like it could be an administrative nightmare,
if so there are potential issues with who is allowed to modify the list of
trusted users that then gets inherited.

...

Basically, as it is now, someone adding me to their role membership has
no downside for me. To trust my own role membership adds a downside to
role membership that I don't think we want to do --- it makes role
membership too complex in what it grants _and_ trusts.

Makes sense, and I can see how that could get out of hand in terms of

figuring out who you trust. I guess I don't know of other cases where this
concept of trusting comes about in our current permissions system? And it
seems to introduce a lot of odd cases where you end up with a sort of
permissions error or I guess a trust error in this case.

One possibility that might help this would be to only use the check this if
a) the user who created the function isn't in the trust list and b) there
is a function with the same name and equivalent argument classes that would
be called if you weren't to call the untrusted user's function. So it is
only used for disambiguation.

Best,
David

#7Bruce Momjian
bruce@momjian.us
In reply to: David Kohn (#6)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 04:01:09PM -0400, David Kohn wrote:

On Thu, Aug 9, 2018 at 3:04 PM Bruce Momjian <bruce@momjian.us> wrote:

Well, right now, if you want to give members of a role rights to
something, you have to specifically grant rights to that role.� I would
assume the same thing would happen here --- if you want to trust a group
role, you have to mention that group role in the GUC list (not
function-level GUC).

Sure, but if I grant execute on a function to a role, members of that role will
be able to execute that function. Now, each member will (potentially) need to
update their trust list before doing that. Which seems a bit odd. Or will I be

Look at your wording above, "I grant execute" --- you are opening up
permissions to them. There is no approval on their part that signifies
they should trust you. If I open permissions for a file on my web
server, it doesn't mean people should trust me more than before.

able to modify the some sort of default trust list of the group role? If not,
it seems like it could be an administrative nightmare, if so there are
potential issues with who is allowed to modify the list of trusted users that
then gets inherited.

We certainly don't want to double-down on extending trust by allowing
someone to modify someone else's trusted role list. Practically, if you
are opening up permissions to someone, you will need to create a group
that you both belong to first, and have them trust the group, or they
can trust you directly. The benefit of a group role is that other
people can be added to that group without having to modify the trusted
role list. Basically, the function caller is trusting whoever controls
membership to that group role. This is different from having someone
trusting a role just because they were added to it (perhaps without
their knowledge).

Basically, as it is now, someone adding me to their role membership has
no downside for me.� To trust my own role membership adds a downside to
role membership that I don't think we want to do --- it makes role
membership too complex in what it grants _and_ trusts.

Makes sense, and I can see how that could get out of hand in terms of figuring
out who you trust. I guess I don't know of other cases where this concept of
trusting comes about in our current permissions system? And it seems to
introduce a lot of odd cases where you end up with a sort of permissions error
or I guess a trust error in this case.�

Yep.

One possibility that might help this would be to only use the check this if a)
the user who created the function isn't in the trust list and b) there is a
function with the same name and equivalent argument classes that would be
called if you weren't to call the untrusted user's function. So it is only used
for disambiguation.�

You can't do that because if you do, someone creating a ambiguous
function would cause a denial-of-service attack --- better to fail at
the time of the first function, when you are likely watching the output.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#8David Kohn
djk447@gmail.com
In reply to: Bruce Momjian (#7)
Re: POC for a function trust mechanism

We certainly don't want to double-down on extending trust by allowing
someone to modify someone else's trusted role list. Practically, if you
are opening up permissions to someone, you will need to create a group
that you both belong to first, and have them trust the group, or they
can trust you directly. The benefit of a group role is that other
people can be added to that group without having to modify the trusted
role list. Basically, the function caller is trusting whoever controls
membership to that group role. This is different from having someone
trusting a role just because they were added to it (perhaps without
their knowledge).

I think if you trust a group role you are implicitly trusting any members
of that group, because one can always alter the function owner from
themselves to the group role, because they are a member of that group. So
what you'd need to do is create a special group role that only owned the
functions and then not make anyone an actual member of that group, but you
could trust that group role. Then a separate group role that everyone would
be a member of and you'd do grants from the first role to the second.
So for what you proposed, if you are opening up permissions to someone by
using a role that you are both members of, then you implicitly open up
permissions from them to you as well.

Anyway, I guess all of this seems to introduce a lot more complexity into
an already complex permissions management system...is this all about the
public schema? Can we just make create function/operator etc something you
have to grant even in the public schema? It seems like that could be
significantly more user friendly than this. Or otherwise, would functions
owned by the database or schema owner be exempt from this? Because there
are many setups where people try to avoid superuser usage by creating
database or schema owner users who can do things like create function,
which a normal users can now use. Would checks be skipped if the function
call is schema qualified because then there's no reasonable way to think
that someone is being fooled about which function they are executing?

Best,
David

#9Isaac Morland
isaac.morland@gmail.com
In reply to: David Kohn (#8)
Re: POC for a function trust mechanism

On 9 August 2018 at 18:18, David Kohn <djk447@gmail.com> wrote:

Anyway, I guess all of this seems to introduce a lot more complexity into

an already complex permissions management system...is this all about the
public schema? Can we just make create function/operator etc something you
have to grant even in the public schema? It seems like that could be
significantly more user friendly than this.

Already true, if you do:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;

Which I do, in all my databases, and which is probably a good idea in most
scenarios.

Or otherwise, would functions owned by the database or schema owner be
exempt from this? Because there are many setups where people try to avoid
superuser usage by creating database or schema owner users who can do
things like create function, which a normal users can now use. Would checks
be skipped if the function call is schema qualified because then there's no
reasonable way to think that someone is being fooled about which function
they are executing?

At present, permissions are completely separate from ownership: your
ability to use an object does not depend on who owns what (I believe you
can even revoke your own rights to use your own stuff). I suspect changing
this is probably not a good idea.

#10Bruce Momjian
bruce@momjian.us
In reply to: David Kohn (#8)
Re: POC for a function trust mechanism

On Thu, Aug 9, 2018 at 06:18:16PM -0400, David Kohn wrote:

We certainly don't want to double-down on extending trust by allowing
someone to modify someone else's trusted role list.� Practically, if you
are opening up permissions to someone, you will need to create a group
that you both belong to first, and have them trust the group, or they
can trust you directly.� The benefit of a group role is that other
people can be added to that group without having to modify the trusted
role list.� Basically, the function caller is trusting whoever controls
membership to that group role.� This is different from having someone
trusting a role just because they were added to it (perhaps without
their knowledge).

I think if you trust a group role you are implicitly trusting any members of
that group, because one can always alter the function owner from themselves to
the group role, because they are a member of that group. So what you'd need to
do is create a special group role that only owned the functions and then not
make anyone an actual member of that group, but you could trust that group
role. Then a separate group role that everyone would be a member of and you'd

Good point. I think you are right that if you trust a role, you trust
whoever controls role membership to add only trust-worthy people --- that
seems obvious because any member can create functions you will trust,
even if they don't change the function owner.

do grants from the first role to the second.�
So for what you proposed, if you are opening up permissions to someone by using
a role that you are both members of, then you implicitly open up permissions
from them to you as well.�

I don't see the value in that. My point is that you can't trust anyone
in a role you are a member of, by default --- you have to make that
decision as a user.

Anyway, I guess all of this seems to introduce a lot more complexity into an
already complex permissions management system...is this all about the public
schema? Can we just make create function/operator etc something you have to
grant even in the public schema? It seems like that could be significantly more
user friendly than this. Or otherwise, would functions owned by the database or

I think 95% of users are proabably creating these things as the
accessing user or super-user.

schema owner be exempt from this? Because there are many setups where people
try to avoid superuser usage by creating database or schema owner users who can
do things like create function, which a normal users can now use. Would checks
be skipped if the function call is schema qualified because then there's no
reasonable way to think that someone is being fooled about which function they
are executing?�

I think most setups will be pretty simple.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ As you are, so once was I.  As I am, so you will be. +
+                      Ancient Roman grave inscription +
#11Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#1)
Re: POC for a function trust mechanism

On Wed, Aug 8, 2018 at 1:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

that they had to worry about this themselves. Of the various ideas that
we'd kicked around and not been able to finish, the one that seemed most
promising to me was to invent a "function trust" mechanism.

In the interest of giving credit where credit is due, I went back and
researched this. I discovered that you were the first one to suggest
this idea, and that Noah was the first to produce a completed patch
for it. That was in 2013.

(Also in 2013, I was arguing that the first thing we should do is fix
pg_dump and our in-core tools to be secure against this sort of
attack. Noah had a patch for it, in 2013. Five years later, that was
indeed the first thing we did. We should have done it back then.)

The things that we hadn't resolved, which is why this didn't get further
than POC stage, were

(1) What's the mechanism for declaring trust? In this POC, it's just
a GUC that you can set to a list of role names, with $user for yourself
and "public" if you want to trust everybody. It's not clear if that's
good enough, or if we want something a bit more locked-down.

Back in 2013, I pointed out that we need to consider the case where
the database owner has done ALTER DATABASE .. SET search_path =
'malevolence', hoping that the superuser will log in and do something
that allows control to be captured. If trusted_roles is merely a GUC,
then the database owner could set that, too. If we want this solution
to be water-tight, I think we need a way to make very sure that a user
never uses a trust setting configured by someone else. We could
implement a special rule (as you proposed back at the time) that
limits the ability to ALTER DATABASE .. SET trusted_roles, but I'm a
little worried that there may be other ways that one user could end up
using a trusted_roles setting configured by somebody else. I wonder
whether thoroughly isolating the manner of configuring trust from the
GUC system might make it easier to avoid unpredictable interactions.

(2) Is trust transitive? Where and how would the list of trusted roles
change? Arguably, if you call a SECURITY DEFINER function, then once
you've decided that you trust the function owner, actual execution of the
function should use the function owner's list of trusted roles not yours.
With the GUC approach, it'd be necessary for SECURITY DEFINER functions
to implement this with a "SET trusted_roles" clause, much as they now
have to do with search_path. That's possible but it's again not very
non-invasive, so we'd been speculating about automating this more.
If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

Yeah, I think these are all good points. It seems natural for trust
to be a property of a role, for just the reasons that you mention.
However, there does also seem to be a use case for varying it
temporarily on a per-session or per-function basis, and I'm not
exactly sure how to cater to those needs. I have a feeling that it
would be really useful to attach a listed of trusted roles, and for
that matter also a search path, to the *lexical scope* of a function.

So we'd kind of decided that the GUC solution wasn't good enough, but
it didn't seem like catalog additions would be feasible as a back-patched
security fix, which is why this didn't go anywhere. But it could work
as a new feature.

Check.

Anyway, I had written a small POC that did use a GUC for this, and just
checked function calls without any attempts to switch the active
trusted_roles setting in places like autovacuum. I've rebased it up to
HEAD and here it is.

I wonder if Noah would like to rebase and post his version also.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#11)
1 attachment(s)
Re: POC for a function trust mechanism

On Sun, Aug 12, 2018 at 10:40:30PM -0400, Robert Haas wrote:

On Wed, Aug 8, 2018 at 1:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If we had, say, a catalog that provided the desired list of trusted roles
for every role, then we could imagine implementing that context change
automatically. Likewise, stuff like autovacuum or REINDEX would want
to run with the table owner's list of trusted roles, but the GUC approach
doesn't really provide enough infrastructure to know what to do there.

Yeah, I think these are all good points. It seems natural for trust
to be a property of a role, for just the reasons that you mention.
However, there does also seem to be a use case for varying it
temporarily on a per-session or per-function basis, and I'm not
exactly sure how to cater to those needs.

Yep. A GUC is great for src/bin sessions, since many of those applications
consistently want tight trust settings.

I wonder if Noah would like to rebase and post his version also.

Sure, attached (last merge at 757c518). The owner_trustcheck() header comment
is a good starting point for reading it. Tom Lane later asserted that it's
okay to perform the function trust check in fmgr_info_cxt_security() instead
of doing it in most fmgr_info() callers and some *FunctionCall* callers.
While I suspected he was right, I have not made that change in this rebase.
(I also haven't audited every fmgr_info() call added after -v1.) When I
shared -v1 with the security team, I included the following submission notes:

===

Here's an implementation. Design decisions:

1. A role trusts itself implicitly

2. Default pg_auth_trust entry for "public TRUST public"

This effectively disables the new restrictions; concerned administrators will
test their applications with it removed, then remove it.

3. Default pg_auth_trust entry for "public TRUST <bootstrap superuser>"

Almost everyone will leave this untouched.

4. Changing trust for a role requires ADMIN OPTION on the role.

Trust is the next step down from granting role membership. ("ALTER ROLE
public TRUST ..." does require superuser.)

5. Non-inheritance of trust

Trust is like a reverse permission; I find it helpful to think of "ALTER ROLE
foo TRUST bar" as "GRANT may_supply_functions_to_foo TO bar". It would be
reasonable to have that trust relationship be effective for INHERITS members
of bar; after all, they could always "ALTER FUNCTION ... OWNER TO bar". For
now, trust relationships are not subject to inheritance. This keeps things
simpler to understand and poses no major downsides. For similar reasons, I
have not made a role trust its own members implicitly.

6. Non-transitivity of trust

If I trust only alice, alice trusts only bob, and I call alice's function that
calls bob's function, should the call succeed? This is a tough one. In favor
of "yes", this choice would allow alice to refactor her function without
needing her callers to revise their trust settings. That is, she could switch
from bob's function to carol's function, and her callers would never notice.
My trust in alice is probably misplaced if she chooses her friends badly.

In favor of "no", making the trust relationship transitive seems to mix two
otherwise-separate concepts: alice's willingness to run code as herself and
alice's willingness to certify third-party functions to her friends. In
particular, current defects in the system are at odds with conflating those
concepts. Everyone should trust the bootstrap superuser, but it currently
owns functions like integer_pl_date that are not careful in what they call.
That closed the issue for me; trust is not transitive by default. Even so, I
think there would be value in a facility for requesting trust transitivity in
specific situations.

7. (Lack of) negative trust entries

There's no way to opt out of a PUBLIC trust relationship; until a superuser
removes the "public TRUST public" entry, no user can achieve anything with
ALTER USER [NO] TRUST. I considered adding the concept of a negative trust
relationship to remedy this problem; it could also be used to remove the
implicit self-trust for testing purposes. I have refrained; I don't know
whether the benefits would pay for the extra user-facing complexity.

8. Applicable roles during trust checks

We had examined whether the check could be looser for SECURITY DEFINER
functions, since those can't perform arbitrary actions as the caller. I
described some challenges then, but a deeper look turned up more: a SECURITY
DEFINER function can do things like "SET search_path = ..." that affect the
caller. Consequently, I concluded that all roles on the active call stack
should have a stake in whether a particular function owner is permitted. Each
trust check walks the call stack and checks every role represented there; if
any lacks the trust relationships to approve the newly-contemplated function
call, the call is rejected.

The stack traversal may and does stop at the edge of a security-restricted
operation; since those restrictions must prevent important session changes,
the stack entries active before the operation started need not be concerned
with the code running therein. A CREATE FUNCTION option specifying voluntary
use of a security-restricted context would provide the "facility for
requesting trust transitivity" mentioned earlier.

To make walking the role stack possible, I redid the API for changing the
current user ID. SetUserIdAndSecContext() gives way to PushTransientUser()
and PopTransientUser(); xact.c has its own little API for unwinding this stack
at (sub)transaction abort. This patch just removes the old APIs, but that
probably wouldn't cut it for the back branches; I do have a design sketch for
reintroducing backward-compatible versions.

9. Trust checks on other object types

Despite trust checks on functions, hostile users can make mischief with
something like "CREATE OPERATOR * (PROCEDURE = int4pl, LEFTARG = int4,
RIGHTARG = int4)". Therefore, I also caused trust checks to be enforced on
operators themselves. As I opine in the comments at owner_trustcheck(), where
to stop is more a judgement call than a science.

The following work remains TODO:
- Documentation for the new concepts, syntax and system catalog
- pg_dumpall support
- Testing the performance impact and perhaps adding a cache
- Backward-compatible versions of removed miscinit.c functions

Attachments:

proc-trust-v2.patchtext/plain; charset=us-asciiDownload
diff --git a/contrib/ltree/ltree_op.c b/contrib/ltree/ltree_op.c
index df61c63..39451a9 100644
--- a/contrib/ltree/ltree_op.c
+++ b/contrib/ltree/ltree_op.c
@@ -599,13 +599,16 @@ ltreeparentsel(PG_FUNCTION_ARGS)
 	{
 		/* Variable is being compared to a known non-null constant */
 		Datum		constval = ((Const *) other)->constvalue;
+		Oid			contcode;
 		FmgrInfo	contproc;
 		double		mcvsum;
 		double		mcvsel;
 		double		nullfrac;
 		int			hist_size;
 
-		fmgr_info(get_opcode(operator), &contproc);
+		contcode = get_opcode(operator);
+		pg_proc_trustcheck(contcode);
+		fmgr_info(contcode, &contproc);
 
 		/*
 		 * Is the constant "<@" to any of the column's most common values?
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index db84273..41d1524 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -43,6 +43,7 @@ CREATE ROLE <replaceable class="parameter">name</replaceable> [ [ WITH ] <replac
     | SYSID <replaceable class="parameter">uid</replaceable>
 </synopsis>
  </refsynopsisdiv>
+ <!-- FIXME document TRUST here, trust concepts, and pg_auth_trust catalog -->
 
  <refsect1>
   <title>Description</title>
diff --git a/src/backend/access/common/scankey.c b/src/backend/access/common/scankey.c
index 781516c..60ddfcc 100644
--- a/src/backend/access/common/scankey.c
+++ b/src/backend/access/common/scankey.c
@@ -20,9 +20,11 @@
 
 /*
  * ScanKeyEntryInitialize
- *		Initializes a scan key entry given all the field values.
- *		The target procedure is specified by OID (but can be invalid
- *		if SK_SEARCHNULL or SK_SEARCHNOTNULL is set).
+ *		Initializes a scan key entry given all the field values.  The target
+ *		procedure is specified by OID (but can be invalid if SK_SEARCHNULL or
+ *		SK_SEARCHNOTNULL is set).  No access or trust check is done, so the
+ *		procedure had better be innocuous.  This is normally used for operator
+ *		class members, which are subject to superuser approval.
  *
  * Note: CurrentMemoryContext at call should be as long-lived as the ScanKey
  * itself, because that's what will be used for any subsidiary info attached
@@ -62,7 +64,8 @@ ScanKeyEntryInitialize(ScanKey entry,
  *
  * This is the recommended version for hardwired lookups in system catalogs.
  * It cannot handle NULL arguments, unary operators, or nondefault operators,
- * but we need none of those features for most hardwired lookups.
+ * but we need none of those features for most hardwired lookups.  Like other
+ * versions, it performs no security checks.
  *
  * We set collation to DEFAULT_COLLATION_OID always.  This is appropriate
  * for textual columns in system catalogs, and it will be ignored for
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index eade540..cbf9cea 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -93,6 +93,8 @@
  * doesn't prevent the actual rebuild because we don't use RELATION_CHECKS
  * when calling the index AM's ambuild routine, and there is no reason for
  * ambuild to call its subsidiary routines through this file.
+ *
+ * We use neither access nor trust checks.
  * ----------------------------------------------------------------
  */
 #define RELATION_CHECKS \
diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel
index 85e5840..c333504 100644
--- a/src/backend/access/transam/README.parallel
+++ b/src/backend/access/transam/README.parallel
@@ -116,11 +116,10 @@ worker.  This includes:
   - The active snapshot, which might be different from the transaction
     snapshot.
 
-  - The currently active user ID and security context.  Note that this is
-    the fourth user ID we restore: the initial step of binding to the correct
-    database also involves restoring the authenticated user ID.  When GUC
-    values are restored, this incidentally sets SessionUserId and OuterUserId
-    to the correct values.  This final step restores CurrentUserId.
+  - The TransientUserStack.  Note that we restored three user IDs already: the
+    initial step of binding to the correct database also involves restoring
+    the authenticated user ID.  When GUC values are restored, this
+    incidentally sets SessionUserId and OuterUser to the correct values.
 
   - State related to pending REINDEX operations, which prevents access to
     an index that is currently being rebuilt.
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index b9a9ae5..e2855a0 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -70,9 +70,10 @@
 #define PARALLEL_KEY_TRANSACTION_STATE		UINT64CONST(0xFFFFFFFFFFFF0008)
 #define PARALLEL_KEY_ENTRYPOINT				UINT64CONST(0xFFFFFFFFFFFF0009)
 #define PARALLEL_KEY_SESSION_DSM			UINT64CONST(0xFFFFFFFFFFFF000A)
-#define PARALLEL_KEY_REINDEX_STATE			UINT64CONST(0xFFFFFFFFFFFF000B)
-#define PARALLEL_KEY_RELMAPPER_STATE		UINT64CONST(0xFFFFFFFFFFFF000C)
-#define PARALLEL_KEY_ENUMBLACKLIST			UINT64CONST(0xFFFFFFFFFFFF000D)
+#define PARALLEL_KEY_USER					UINT64CONST(0xFFFFFFFFFFFF000B)
+#define PARALLEL_KEY_REINDEX_STATE			UINT64CONST(0xFFFFFFFFFFFF000C)
+#define PARALLEL_KEY_RELMAPPER_STATE		UINT64CONST(0xFFFFFFFFFFFF000D)
+#define PARALLEL_KEY_ENUMBLACKLIST			UINT64CONST(0xFFFFFFFFFFFF000E)
 
 /* Fixed-size parallel state. */
 typedef struct FixedParallelState
@@ -80,11 +81,9 @@ typedef struct FixedParallelState
 	/* Fixed-size state that workers must restore. */
 	Oid			database_id;
 	Oid			authenticated_user_id;
-	Oid			current_user_id;
 	Oid			outer_user_id;
 	Oid			temp_namespace_id;
 	Oid			temp_toast_namespace_id;
-	int			sec_context;
 	bool		is_superuser;
 	PGPROC	   *parallel_master_pgproc;
 	pid_t		parallel_master_pid;
@@ -210,6 +209,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	Size		tsnaplen = 0;
 	Size		asnaplen = 0;
 	Size		tstatelen = 0;
+	Size		userlen = 0;
 	Size		reindexlen = 0;
 	Size		relmapperlen = 0;
 	Size		enumblacklistlen = 0;
@@ -262,6 +262,8 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		tstatelen = EstimateTransactionStateSpace();
 		shm_toc_estimate_chunk(&pcxt->estimator, tstatelen);
 		shm_toc_estimate_chunk(&pcxt->estimator, sizeof(dsm_handle));
+		userlen = EstimateTransientUserStackSpace();
+		shm_toc_estimate_chunk(&pcxt->estimator, userlen);
 		reindexlen = EstimateReindexStateSpace();
 		shm_toc_estimate_chunk(&pcxt->estimator, reindexlen);
 		relmapperlen = EstimateRelationMapSpace();
@@ -319,7 +321,6 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	fps->authenticated_user_id = GetAuthenticatedUserId();
 	fps->outer_user_id = GetCurrentRoleId();
 	fps->is_superuser = session_auth_is_superuser;
-	GetUserIdAndSecContext(&fps->current_user_id, &fps->sec_context);
 	GetTempNamespaceState(&fps->temp_namespace_id,
 						  &fps->temp_toast_namespace_id);
 	fps->parallel_master_pgproc = MyProc;
@@ -340,6 +341,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		char	   *tsnapspace;
 		char	   *asnapspace;
 		char	   *tstatespace;
+		char	   *userspace;
 		char	   *reindexspace;
 		char	   *relmapperspace;
 		char	   *error_queue_space;
@@ -384,6 +386,11 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		SerializeTransactionState(tstatelen, tstatespace);
 		shm_toc_insert(pcxt->toc, PARALLEL_KEY_TRANSACTION_STATE, tstatespace);
 
+		/* Serialize transient user stack. */
+		userspace = shm_toc_allocate(pcxt->toc, userlen);
+		SerializeTransientUserStack(userlen, userspace);
+		shm_toc_insert(pcxt->toc, PARALLEL_KEY_USER, userspace);
+
 		/* Serialize reindex state. */
 		reindexspace = shm_toc_allocate(pcxt->toc, reindexlen);
 		SerializeReindexState(reindexlen, reindexspace);
@@ -1228,6 +1235,7 @@ ParallelWorkerMain(Datum main_arg)
 	char	   *tsnapspace;
 	char	   *asnapspace;
 	char	   *tstatespace;
+	char	   *userspace;
 	char	   *reindexspace;
 	char	   *relmapperspace;
 	char	   *enumblacklistspace;
@@ -1402,8 +1410,9 @@ ParallelWorkerMain(Datum main_arg)
 	 */
 	SetCurrentRoleId(fps->outer_user_id, fps->is_superuser);
 
-	/* Restore user ID and security context. */
-	SetUserIdAndSecContext(fps->current_user_id, fps->sec_context);
+	/* Restore transient user stack. */
+	userspace = shm_toc_lookup(toc, PARALLEL_KEY_USER, false);
+	RestoreTransientUserStack(userspace);
 
 	/* Restore temp-namespace state to ensure search path matches leader's. */
 	SetTempNamespaceState(fps->temp_namespace_id,
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d967400..db37912 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -183,8 +183,7 @@ typedef struct TransactionStateData
 	TransactionId *childXids;	/* subcommitted child XIDs, in XID order */
 	int			nChildXids;		/* # of subcommitted child XIDs */
 	int			maxChildXids;	/* allocated size of childXids[] */
-	Oid			prevUser;		/* previous CurrentUserId setting */
-	int			prevSecContext; /* previous SecurityRestrictionContext */
+	TransientUser prevUser;		/* cookie for previous user state */
 	bool		prevXactReadOnly;	/* entry-time xact r/o state */
 	bool		startedInRecovery;	/* did we start in recovery? */
 	bool		didLogXid;		/* has xid been included in WAL record? */
@@ -1836,10 +1835,9 @@ StartTransaction(void)
 	 * Once the current user ID and the security context flags are fetched,
 	 * both will be properly reset even if transaction startup fails.
 	 */
-	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
-
-	/* SecurityRestrictionContext should never be set outside a transaction */
-	Assert(s->prevSecContext == 0);
+	s->prevUser = GetTransientUser();
+	Assert(!InLocalUserIdChange());
+	Assert(!InSecurityRestrictedOperation());
 
 	/*
 	 * Make sure we've reset xact state variables
@@ -2538,14 +2536,13 @@ AbortTransaction(void)
 	/*
 	 * Reset user ID which might have been changed transiently.  We need this
 	 * to clean up in case control escaped out of a SECURITY DEFINER function
-	 * or other local change of CurrentUserId; therefore, the prior value of
-	 * SecurityRestrictionContext also needs to be restored.
+	 * or other local change of user ID.
 	 *
 	 * (Note: it is not necessary to restore session authorization or role
 	 * settings here because those can only be changed via GUC, and GUC will
 	 * take care of rolling them back if need be.)
 	 */
-	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
+	RestoreTransientUser(s->prevUser);
 
 	/* If in parallel mode, clean up workers and exit parallel mode. */
 	if (IsInParallelMode())
@@ -4755,7 +4752,7 @@ AbortSubTransaction(void)
 	 * Reset user ID which might have been changed transiently.  (See notes in
 	 * AbortTransaction.)
 	 */
-	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
+	RestoreTransientUser(s->prevUser);
 
 	/* Exit from parallel mode, if necessary. */
 	if (IsInParallelMode())
@@ -4904,7 +4901,7 @@ PushTransaction(void)
 	s->savepointLevel = p->savepointLevel;
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
-	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevUser = GetTransientUser();
 	s->prevXactReadOnly = XactReadOnly;
 	s->parallelModeLevel = 0;
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 0865240..6330e58 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -38,7 +38,8 @@ CATALOG_HEADERS := \
 	pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
 	pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
 	pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
-	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
+	pg_authid.h pg_auth_members.h pg_auth_trust.h \
+	pg_shdepend.h pg_shdescription.h \
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
@@ -59,7 +60,8 @@ POSTGRES_BKI_SRCS := $(addprefix $(top_srcdir)/src/include/catalog/,\
 
 # The .dat files we need can just be listed alphabetically.
 POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
-	pg_aggregate.dat pg_am.dat pg_amop.dat pg_amproc.dat pg_authid.dat \
+	pg_aggregate.dat pg_am.dat pg_amop.dat pg_amproc.dat \
+	pg_authid.dat pg_auth_trust.dat \
 	pg_cast.dat pg_class.dat pg_collation.dat \
 	pg_database.dat pg_language.dat \
 	pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1dd70bb..cd70ce5 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -4649,7 +4649,8 @@ pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode)
 }
 
 /*
- * Exported routine for checking a user's access privileges to a function
+ * Exported routine for checking a user's access privileges to a function.
+ * See also pg_proc_execcheck().
  */
 AclResult
 pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode)
@@ -4661,6 +4662,213 @@ pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode)
 }
 
 /*
+ * Perform a "trust check", a kind of mirror image of an ACL check, against a
+ * particular object owner.  ALTER ROLE TRUST creates a trust relationship,
+ * analogous to an ACL created by GRANT, from one user to another.  If Alice
+ * has granted trust to Bob, trust checks with Alice as the current user and
+ * Bob as the object owner will pass.  A trust relationship can name PUBLIC as
+ * one of the roles, and this serves as a wildcard.
+ *
+ * This mechanism arose as a response to the difficulty of avoiding accidental
+ * calls to functions controlled by potentially-hostile users.  One could not
+ * write to a table or select from a view without implicitly permitting the
+ * table or view owner to cause arbitrary code to run as oneself.  Hostile
+ * users can use function overloading and the search path to capture function
+ * calls.  For example, you may write length(varcharcol) expecting to call
+ * length(text), but a hostile user's public.length(varchar) function would be
+ * chosen.  The successful attacker can effectively hijack your database role.
+ * By establishing a conservative set of trust relationships, accidental calls
+ * to functions of untrusted users will end harmlessly with an error.
+ *
+ * Trust checks apply to functions (pg_proc_trustcheck_extended()) and
+ * operators (pg_oper_trustcheck_extended()).  The value of checking functions
+ * is clear enough; they can run arbitrary commands.  Operators are less
+ * threatening; trust is still checked for the underlying function, so
+ * tricking a user into calling an untrusted operator does not allow arbitrary
+ * code execution.  However, an attacker could silently compromise query
+ * results by creating an =(text,text) operator that actually calls textne().
+ *
+ * The choice to add operators and no other object type to the remit of trust
+ * checks is a judgement call rather than recognition of a bright line; one
+ * could argue for applying trust checks to objects like types, text search
+ * configurations, and even tables.  In each case, an attacker who can lure a
+ * user into referencing the substitute object can falsify the user's query
+ * results.  The decision arises from practical concerns:
+ *	1. The syntax for unambiguously-qualifying an operator is cumbersome.
+ *	2. Only functions and operators are subject to overloading; overloading
+ *	   makes it easier to reference the wrong object accidentally.
+ *	3. A typical query references more operators than any other object type,
+ *	   so simplifying the need for diligence brings a high relative payoff.
+ *	4. Types, probably the next most credible candidate, are difficult to
+ *	   inject unnoticed without also injecting an associated function.
+ *
+ * When SECURITY DEFINER functions and similar constructs are on the stack, we
+ * check users in addition to the current user for trust.  See comments at
+ * GetSecurityApplicableRoles().  We actually could check only the current
+ * user for operators, but it's not worth complicating the policy.
+ *
+ * Returns the first applicable call stack user not accepting this owner or
+ * InvalidOid if this owner is approved.
+ *
+ * XXX This can be called in performance-sensitive contexts; consider adding a
+ * cache of 1-4 owners that have passed the trust check, invalidating that
+ * cache when the list of applicable roles changes.
+ */
+static Oid
+owner_trustcheck(Oid owner)
+{
+	Oid		   *roles;
+	int			nroles;
+	int			i;
+	Oid			ret = InvalidOid;
+
+	if (
+	/* Does PUBLIC trust owner?  Common for BOOTSTRAP_SUPERUSERID. */
+		SearchSysCacheExists2(AUTHTRUSTGRANTORTRUSTEE,
+							  ObjectIdGetDatum(InvalidOid),
+							  ObjectIdGetDatum(owner)) ||
+	/* Does PUBLIC trust PUBLIC?  Not recommended but true by default. */
+		SearchSysCacheExists2(AUTHTRUSTGRANTORTRUSTEE,
+							  ObjectIdGetDatum(InvalidOid),
+							  ObjectIdGetDatum(InvalidOid)))
+	{
+		return InvalidOid;
+	}
+
+	/* Find all roles having a stake in code that runs at this moment. */
+	GetSecurityApplicableRoles(&roles, &nroles);
+
+	for (i = 0; i < nroles; ++i)
+	{
+		if (
+		/* A role implicitly trusts itself. */
+			roles[i] != owner &&
+		/* Does caller trust owner? */
+			!SearchSysCacheExists2(AUTHTRUSTGRANTORTRUSTEE,
+								   ObjectIdGetDatum(roles[i]),
+								   ObjectIdGetDatum(owner)) &&
+		/* Does caller trust PUBLIC?  Not recommended, so check last. */
+			!SearchSysCacheExists2(AUTHTRUSTGRANTORTRUSTEE,
+								   ObjectIdGetDatum(roles[i]),
+								   ObjectIdGetDatum(InvalidOid)))
+		{
+			ret = roles[i];
+			break;
+		}
+	}
+
+	pfree(roles);
+	return ret;
+}
+
+/*
+ * Exported routine for checking trust relationships of an operator's owner.
+ * See comments at owner_trustcheck() for the semantics of trust.
+ *
+ * Operator trust can be a bit looser than function trust, because the
+ * consequence of skipping a check can include wrong query results but not
+ * arbitrary code execution.  So, for example, one may skip checks before
+ * evaluating an operator in the planner so long as the executor will do them.
+ */
+bool
+pg_oper_trustcheck_extended(Oid oper_oid, bool errorOK)
+{
+	HeapTuple	tuple;
+	Oid			owner;
+	Oid			vetoer;
+
+	tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(oper_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("operator with OID %u does not exist", oper_oid)));
+	owner = ((Form_pg_operator) GETSTRUCT(tuple))->oprowner;
+	ReleaseSysCache(tuple);
+
+	vetoer = owner_trustcheck(owner);
+	if (!OidIsValid(vetoer))
+		return true;
+	else if (errorOK)
+		return false;
+
+	ereport(ERROR,
+			(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("user %s does not trust operator %s",
+					GetUserNameFromId(vetoer, false),
+					get_opname(oper_oid))));
+}
+
+/*
+ * Exported routine for checking trust relationships of a function's owner.
+ * See comments at owner_trustcheck() for the semantics of trust and at
+ * pg_proc_execcheck() regarding when to use each of these variants.
+ */
+bool
+pg_proc_trustcheck_extended(Oid proc_oid, bool errorOK)
+{
+	HeapTuple	tuple;
+	Oid			owner;
+	Oid			vetoer;
+
+	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_FUNCTION),
+				 errmsg("function with OID %u does not exist", proc_oid)));
+	owner = ((Form_pg_proc) GETSTRUCT(tuple))->proowner;
+	ReleaseSysCache(tuple);
+
+	vetoer = owner_trustcheck(owner);
+	if (!OidIsValid(vetoer))
+		return true;
+	else if (errorOK)
+		return false;
+
+	ereport(ERROR,
+			(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("user %s does not trust function %s",
+					GetUserNameFromId(vetoer, false),
+					get_func_name(proc_oid))));
+}
+
+/*
+ * Exported routine to make standard permissions checks applicable to running
+ * a function.  Verifies that the current role has permission to execute the
+ * function and that the function owner has the necessary trust relationships.
+ *
+ * DDL that binds a function to another object without executing the function,
+ * such as CREATE TRIGGER, should not perform a trust check; instead, call
+ * pg_proc_aclcheck() directly.  This allows a superuser to restore a database
+ * dump without trusting all trigger function owners.  On the flip side,
+ * objects like triggers and range types that check ACLs at creation time and
+ * skip them at runtime must still check trust at runtime; use
+ * pg_proc_trustcheck() directly.
+ *
+ * Functions named when creating a base type (input, output, receive, send,
+ * typmod_in, typmod_out, analyze) require no trust checks or ACL checks; only
+ * superusers can create new base types, and the creator is expected to use
+ * functions free from security considerations that would entail either check.
+ * Likewise for the functions associated with index access methods, operator
+ * families, languages, foreign data wrappers, text search templates, and text
+ * search parsers.  Selectivity functions also need no trust check, because
+ * only a superuser can define one by virtue of the arguments of type
+ * internal.  However, all of these exempt functions are responsible for
+ * performing trust checks on _functions they execute_ that are not themselves
+ * exempt; this affects selectivity functions especially.
+ */
+void
+pg_proc_execcheck(Oid proc_oid)
+{
+	AclResult	aclresult;
+
+	aclresult = pg_proc_aclcheck(proc_oid, GetUserId(), ACL_EXECUTE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(proc_oid));
+
+	pg_proc_trustcheck(proc_oid);
+}
+
+/*
  * Exported routine for checking a user's access privileges to a language
  */
 AclResult
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 5a160bf..173e314 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_trust.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
@@ -226,6 +227,7 @@ IsSharedRelation(Oid relationId)
 	/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
 	if (relationId == AuthIdRelationId ||
 		relationId == AuthMemRelationId ||
+		relationId == AuthTrustRelationId ||
 		relationId == DatabaseRelationId ||
 		relationId == PLTemplateRelationId ||
 		relationId == SharedDescriptionRelationId ||
@@ -241,6 +243,8 @@ IsSharedRelation(Oid relationId)
 		relationId == AuthIdOidIndexId ||
 		relationId == AuthMemRoleMemIndexId ||
 		relationId == AuthMemMemRoleIndexId ||
+		relationId == AuthTrustGrantorTrusteeIndexId ||
+		relationId == AuthTrustTrusteeGrantorIndexId ||
 		relationId == DatabaseNameIndexId ||
 		relationId == DatabaseOidIndexId ||
 		relationId == PLTemplateNameIndexId ||
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8709e8c..eefe269 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2241,8 +2241,6 @@ index_build(Relation heapRelation,
 			bool parallel)
 {
 	IndexBuildResult *stats;
-	Oid			save_userid;
-	int			save_sec_context;
 	int			save_nestlevel;
 
 	/*
@@ -2284,9 +2282,7 @@ index_build(Relation heapRelation,
 	 * as that user.  Also lock down security-restricted operations and
 	 * arrange to make GUC variable changes local to this command.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	PushTransientUser(heapRelation->rd_rel->relowner, true, false);
 	save_nestlevel = NewGUCNestLevel();
 
 	/*
@@ -2390,11 +2386,9 @@ index_build(Relation heapRelation,
 	if (indexInfo->ii_ExclusionOps != NULL)
 		IndexCheckExclusion(heapRelation, indexRelation, indexInfo);
 
-	/* Roll back any GUC changes executed by index functions */
+	/* Roll back GUC changes by index functions; revert user ID */
 	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	PopTransientUser();
 }
 
 
@@ -3127,8 +3121,6 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	v_i_state	state;
-	Oid			save_userid;
-	int			save_sec_context;
 	int			save_nestlevel;
 
 	/* Open and lock the parent heap relation */
@@ -3151,9 +3143,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	 * as that user.  Also lock down security-restricted operations and
 	 * arrange to make GUC variable changes local to this command.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	PushTransientUser(heapRelation->rd_rel->relowner, true, false);
 	save_nestlevel = NewGUCNestLevel();
 
 	/*
@@ -3200,11 +3190,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
+	/* Roll back GUC changes by index functions; revert user ID */
 	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	PopTransientUser();
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 4b12e9f..280c89f 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -772,7 +772,9 @@ AggregateCreate(const char *aggName,
 
 /*
  * lookup_agg_function
- * common code for finding aggregate support functions
+ * Subroutine of AggregateCreate for finding a support function, validating
+ * it, and performing an ACL check.  Skip the trust check, because CREATE
+ * AGGREGATE won't execute the function.
  *
  * fnName: possibly-schema-qualified function name
  * nargs, input_types: expected function argument types
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index b8445dc..9d89946 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -311,8 +311,6 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
 	int			save_nestlevel;
 
 	if (inh)
@@ -340,9 +338,7 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 	 * as that user.  Also lock down security-restricted operations and
 	 * arrange to make GUC variable changes local to this command.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	PushTransientUser(onerel->rd_rel->relowner, true, false);
 	save_nestlevel = NewGUCNestLevel();
 
 	/* measure elapsed time iff autovacuum logging requires it */
@@ -669,11 +665,9 @@ do_analyze_rel(Relation onerel, int options, VacuumParams *params,
 							pg_rusage_show(&ru0))));
 	}
 
-	/* Roll back any GUC changes executed by index functions */
+	/* Roll back GUC changes by index functions; revert user ID */
 	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	PopTransientUser();
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
@@ -1993,7 +1987,10 @@ compute_distinct_stats(VacAttrStatsP stats,
 		firstcount1 = track_cnt;
 		for (j = 0; j < track_cnt; j++)
 		{
-			/* We always use the default collation for statistics */
+			/*
+			 * We always use the default collation for statistics.  This is
+			 * always an operator class member, so no trust check.
+			 */
 			if (DatumGetBool(FunctionCall2Coll(&f_cmpeq,
 											   DEFAULT_COLLATION_OID,
 											   value, track[j].value)))
diff --git a/src/backend/commands/conversioncmds.c b/src/backend/commands/conversioncmds.c
index e36fc23..a9e257b 100644
--- a/src/backend/commands/conversioncmds.c
+++ b/src/backend/commands/conversioncmds.c
@@ -96,7 +96,8 @@ CreateConversionCommand(CreateConversionStmt *stmt)
 	 * Check that the conversion function is suitable for the requested source
 	 * and target encodings. We do that by calling the function with an empty
 	 * string; the conversion function should throw an error if it can't
-	 * perform the requested conversion.
+	 * perform the requested conversion.  Only superusers can define these
+	 * functions, so skip the trust check.
 	 */
 	OidFunctionCall5(funcoid,
 					 Int32GetDatum(from_encoding),
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index d01b258..a5cea20 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -229,8 +229,6 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	IntoClause *into = stmt->into;
 	bool		is_matview = (into->viewQuery != NULL);
 	DestReceiver *dest;
-	Oid			save_userid = InvalidOid;
-	int			save_sec_context = 0;
 	int			save_nestlevel = 0;
 	ObjectAddress address;
 	List	   *rewritten;
@@ -286,9 +284,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	 */
 	if (is_matview)
 	{
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(save_userid,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+		PushTransientUser(GetUserId(), true, false);
 		save_nestlevel = NewGUCNestLevel();
 	}
 
@@ -370,11 +366,9 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 
 	if (is_matview)
 	{
-		/* Roll back any GUC changes */
+		/* Roll back GUC changes by index functions; revert user ID */
 		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		PopTransientUser();
 	}
 
 	return address;
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ebece4d..8ecfa2d 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -2238,6 +2238,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
 	aclresult = pg_proc_aclcheck(fexpr->funcid, GetUserId(), ACL_EXECUTE);
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(fexpr->funcid));
+	pg_proc_trustcheck(fexpr->funcid);
 
 	/* Prep the context object we'll pass to the procedure */
 	callcontext = makeNode(CallContext);
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index a171eba..0c64603 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -64,8 +64,7 @@ static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
 						 const char *queryString);
 static char *make_temptable_name_n(char *tempname, int n);
-static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
-					   int save_sec_context);
+static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner);
 static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence);
 static bool is_usable_unique_index(Relation indexRel);
 static void OpenMatViewIncrementalMaintenance(void);
@@ -148,8 +147,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	bool		concurrent;
 	LOCKMODE	lockmode;
 	char		relpersistence;
-	Oid			save_userid;
-	int			save_sec_context;
 	int			save_nestlevel;
 	ObjectAddress address;
 
@@ -273,9 +270,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	 * Don't lock it down too tight to create a temporary table just yet.  We
 	 * will switch modes when we are about to execute user code.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	PushTransientUser(relowner, false, false);
 	save_nestlevel = NewGUCNestLevel();
 
 	/* Concurrent refresh builds new data in temp tablespace, and does diff. */
@@ -303,8 +298,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	/*
 	 * Now lock down security-restricted operations.
 	 */
-	SetUserIdAndSecContext(relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	PushTransientUser(relowner, true, false);
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
@@ -317,8 +311,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 		PG_TRY();
 		{
-			refresh_by_match_merge(matviewOid, OIDNewHeap, relowner,
-								   save_sec_context);
+			refresh_by_match_merge(matviewOid, OIDNewHeap, relowner);
 		}
 		PG_CATCH();
 		{
@@ -345,11 +338,10 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	heap_close(matviewRel, NoLock);
 
-	/* Roll back any GUC changes */
+	/* Roll back GUC changes by index functions; revert user ID */
 	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	PopTransientUser();
+	PopTransientUser();
 
 	ObjectAddressSet(address, RelationRelationId, matviewOid);
 
@@ -577,8 +569,7 @@ make_temptable_name_n(char *tempname, int n)
  * this command.
  */
 static void
-refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
-					   int save_sec_context)
+refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner)
 {
 	StringInfoData querybuf;
 	Relation	matviewRel;
@@ -648,8 +639,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 						   SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1))));
 	}
 
-	SetUserIdAndSecContext(relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	PopTransientUser();			/* exit security-restricted operation */
 
 	/* Start building the query for creating the diff table. */
 	resetStringInfo(&querybuf);
@@ -787,8 +777,8 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 	if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
 
-	SetUserIdAndSecContext(relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	/* reenter security-restricted operation */
+	PushTransientUser(relowner, true, false);
 
 	/*
 	 * We have no further use for data from the "full-data" temp table, but we
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index f0ebe2d1..e3f61db 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -56,13 +56,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 	OverrideSearchPath *overridePath;
 	List	   *parsetree_list;
 	ListCell   *parsetree_item;
+	Oid			issuer_uid;
 	Oid			owner_uid;
-	Oid			saved_uid;
-	int			save_sec_context;
 	AclResult	aclresult;
 	ObjectAddress address;
 
-	GetUserIdAndSecContext(&saved_uid, &save_sec_context);
+	issuer_uid = GetUserId();
 
 	/*
 	 * Who is supposed to own the new schema?
@@ -70,7 +69,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 	if (stmt->authrole)
 		owner_uid = get_rolespec_oid(stmt->authrole, false);
 	else
-		owner_uid = saved_uid;
+		owner_uid = issuer_uid;
 
 	/* fill schema name with the user name if not specified */
 	if (!schemaName)
@@ -92,12 +91,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 	 * The latter provision guards against "giveaway" attacks.  Note that a
 	 * superuser will always have both of these privileges a fortiori.
 	 */
-	aclresult = pg_database_aclcheck(MyDatabaseId, saved_uid, ACL_CREATE);
+	aclresult = pg_database_aclcheck(MyDatabaseId, issuer_uid, ACL_CREATE);
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_DATABASE,
 					   get_database_name(MyDatabaseId));
 
-	check_is_member_of_role(saved_uid, owner_uid);
+	check_is_member_of_role(issuer_uid, owner_uid);
 
 	/* Additional check to protect reserved schema names */
 	if (!allowSystemTableMods && IsReservedName(schemaName))
@@ -131,9 +130,8 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 	 * (The setting will be restored at the end of this routine, or in case of
 	 * error, transaction abort will clean things up.)
 	 */
-	if (saved_uid != owner_uid)
-		SetUserIdAndSecContext(owner_uid,
-							   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+	if (issuer_uid != owner_uid)
+		PushTransientUser(owner_uid, false, false);
 
 	/* Create the schema's namespace */
 	namespaceId = NamespaceCreate(schemaName, owner_uid, false);
@@ -205,8 +203,8 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 	/* Reset search path to normal state */
 	PopOverrideSearchPath();
 
-	/* Reset current user and security context */
-	SetUserIdAndSecContext(saved_uid, save_sec_context);
+	if (issuer_uid != owner_uid)
+		PopTransientUser();
 
 	return namespaceId;
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 843ed48..453a6bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11002,7 +11002,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 	HeapTuple	tuple;
 	Oid			orig_tablespaceoid;
 	Oid			new_tablespaceoid;
-	List	   *role_oids = roleSpecsToIds(stmt->roles);
+	List	   *role_oids = roleSpecsToIds(stmt->roles, false);
 
 	/* Ensure we were not asked to move something we can't */
 	if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index bcdd86c..310194a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2379,10 +2379,13 @@ ExecCallTriggerFunc(TriggerData *trigdata,
 
 	/*
 	 * We cache fmgr lookup info, to avoid making the lookup again on each
-	 * call.
+	 * call.  This is also a convenient moment to check function trust.
 	 */
 	if (finfo->fn_oid == InvalidOid)
+	{
+		pg_proc_trustcheck(trigdata->tg_trigger->tgfoid);
 		fmgr_info(trigdata->tg_trigger->tgfoid, finfo);
+	}
 
 	Assert(finfo->fn_oid == trigdata->tg_trigger->tgfoid);
 
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 12afa18..564a1d6 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -22,6 +22,7 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_trust.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
@@ -55,6 +56,12 @@ static void AddRoleMems(const char *rolename, Oid roleid,
 static void DelRoleMems(const char *rolename, Oid roleid,
 			List *memberSpecs, List *memberIds,
 			bool admin_opt);
+static void StoreRoleTrust(const RoleSpec *role, Oid roleid,
+			   List *addSpecs, List *delSpecs);
+static void AddRoleTrust(const RoleSpec *role, Oid roleid,
+			 List *memberNames, List *memberIds);
+static void DelRoleTrust(const RoleSpec *role, Oid roleid,
+			 List *memberNames, List *memberIds);
 
 
 /* Check if current user has createrole privileges */
@@ -77,6 +84,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	Datum		new_record[Natts_pg_authid];
 	bool		new_record_nulls[Natts_pg_authid];
 	Oid			roleid;
+	RoleSpec   *spec;
 	ListCell   *item;
 	ListCell   *option;
 	char	   *password = NULL;	/* user password */
@@ -94,6 +102,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	char	   *validUntil = NULL;	/* time the login is valid until */
 	Datum		validUntil_datum;	/* same, as timestamptz Datum */
 	bool		validUntil_null;
+	List	   *trust = NIL;
+	List	   *notrust = NIL;
 	DefElem    *dpassword = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
@@ -106,6 +116,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	DefElem    *drolemembers = NULL;
 	DefElem    *dadminmembers = NULL;
 	DefElem    *dvalidUntil = NULL;
+	DefElem    *dtrust = NULL;
+	DefElem    *dnotrust = NULL;
 	DefElem    *dbypassRLS = NULL;
 
 	/* The defaults can vary depending on the original statement type */
@@ -239,6 +251,26 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 						 parser_errposition(pstate, defel->location)));
 			dvalidUntil = defel;
 		}
+		else if (strcmp(defel->defname, "trust") == 0)
+		{
+			if (dtrust)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dtrust = defel;
+		}
+		else if (strcmp(defel->defname, "notrust") == 0)
+		{
+			/*
+			 * XXX This will only ever issue warnings about the absence of
+			 * entries to remove; should we forbid it entirely?
+			 */
+			if (dnotrust)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dnotrust = defel;
+		}
 		else if (strcmp(defel->defname, "bypassrls") == 0)
 		{
 			if (dbypassRLS)
@@ -283,6 +315,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 		adminmembers = (List *) dadminmembers->arg;
 	if (dvalidUntil)
 		validUntil = strVal(dvalidUntil->arg);
+	if (dtrust)
+		trust = (List *) dtrust->arg;
+	if (dnotrust)
+		notrust = (List *) dnotrust->arg;
 	if (dbypassRLS)
 		bypassrls = intVal(dbypassRLS->arg) != 0;
 
@@ -452,13 +488,17 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	 */
 	CatalogTupleInsert(pg_authid_rel, tuple);
 
-	/*
-	 * Advance command counter so we can see new record; else tests in
-	 * AddRoleMems may fail.
-	 */
-	if (addroleto || adminmembers || rolemembers)
+	/* Next steps may examine the pg_authid row. */
+	if (trust || notrust || addroleto || adminmembers || rolemembers)
 		CommandCounterIncrement();
 
+	/* pg_auth_trust */
+	spec = makeNode(RoleSpec);
+	spec->roletype = ROLESPEC_CSTRING;
+	spec->rolename = stmt->role;
+	spec->location = -1;
+	StoreRoleTrust(spec, roleid, trust, notrust);
+
 	/*
 	 * Add the new role to the specified existing roles.
 	 */
@@ -483,10 +523,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	 * option, rolemembers don't.
 	 */
 	AddRoleMems(stmt->role, roleid,
-				adminmembers, roleSpecsToIds(adminmembers),
+				adminmembers, roleSpecsToIds(adminmembers, false),
 				GetUserId(), true);
 	AddRoleMems(stmt->role, roleid,
-				rolemembers, roleSpecsToIds(rolemembers),
+				rolemembers, roleSpecsToIds(rolemembers, false),
 				GetUserId(), false);
 
 	/* Post creation hook for new role */
@@ -533,6 +573,8 @@ AlterRole(AlterRoleStmt *stmt)
 	char	   *validUntil = NULL;	/* time the login is valid until */
 	Datum		validUntil_datum;	/* same, as timestamptz Datum */
 	bool		validUntil_null;
+	List	   *trust = NIL;
+	List	   *notrust = NIL;
 	int			bypassrls = -1;
 	DefElem    *dpassword = NULL;
 	DefElem    *dissuper = NULL;
@@ -544,6 +586,8 @@ AlterRole(AlterRoleStmt *stmt)
 	DefElem    *dconnlimit = NULL;
 	DefElem    *drolemembers = NULL;
 	DefElem    *dvalidUntil = NULL;
+	DefElem    *dtrust = NULL;
+	DefElem    *dnotrust = NULL;
 	DefElem    *dbypassRLS = NULL;
 	Oid			roleid;
 
@@ -636,6 +680,22 @@ AlterRole(AlterRoleStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			dvalidUntil = defel;
 		}
+		else if (strcmp(defel->defname, "trust") == 0)
+		{
+			if (dtrust)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dtrust = defel;
+		}
+		else if (strcmp(defel->defname, "notrust") == 0)
+		{
+			if (dnotrust)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dnotrust = defel;
+		}
 		else if (strcmp(defel->defname, "bypassrls") == 0)
 		{
 			if (dbypassRLS)
@@ -647,6 +707,13 @@ AlterRole(AlterRoleStmt *stmt)
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
+
+		if (stmt->role->roletype == ROLESPEC_PUBLIC &&
+			strcmp(defel->defname, "trust") != 0 &&
+			strcmp(defel->defname, "notrust") != 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("only [NO] TRUST allowed for PUBLIC")));
 	}
 
 	if (dpassword && dpassword->arg)
@@ -675,10 +742,24 @@ AlterRole(AlterRoleStmt *stmt)
 		rolemembers = (List *) drolemembers->arg;
 	if (dvalidUntil)
 		validUntil = strVal(dvalidUntil->arg);
+	if (dtrust)
+		trust = (List *) dtrust->arg;
+	if (dnotrust)
+		notrust = (List *) dnotrust->arg;
 	if (dbypassRLS)
 		bypassrls = intVal(dbypassRLS->arg);
 
 	/*
+	 * If changing PUBLIC, modify pg_auth_trust and we're done.  Since there's
+	 * no pg_authid row, don't fire OAT_POST_ALTER.
+	 */
+	if (stmt->role->roletype == ROLESPEC_PUBLIC)
+	{
+		StoreRoleTrust(stmt->role, InvalidOid, trust, notrust);
+		return 0;
+	}
+
+	/*
 	 * Scan the pg_authid relation to be certain the user exists.
 	 */
 	pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
@@ -690,8 +771,15 @@ AlterRole(AlterRoleStmt *stmt)
 	roleid = authform->oid;
 
 	/*
-	 * To mess with a superuser you gotta be superuser; else you need
-	 * createrole, or just want to change your own password
+	 * Lock the role; among other things, this prevents a concurrent session
+	 * from dropping it while we add then-orphaned pg_auth_trust roles.
+	 */
+	shdepLockAndCheckObject(AuthIdRelationId, roleid);
+
+	/*
+	 * To mess with a superuser or replication user, you gotta be superuser.
+	 * StoreRoleTrust() applies its own permissions checks.  Any role may
+	 * change its own password.  For other settings, you need createrole.
 	 */
 	if (authform->rolsuper || issuper >= 0)
 	{
@@ -724,8 +812,8 @@ AlterRole(AlterRoleStmt *stmt)
 			  !dconnlimit &&
 			  !rolemembers &&
 			  !validUntil &&
-			  dpassword &&
-			  roleid == GetUserId()))
+			  ((dpassword && roleid == GetUserId()) ||
+			   trust || notrust)))
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("permission denied")));
@@ -866,16 +954,19 @@ AlterRole(AlterRoleStmt *stmt)
 	 * Advance command counter so we can see new record; else tests in
 	 * AddRoleMems may fail.
 	 */
-	if (rolemembers)
+	if (trust || notrust || rolemembers)
 		CommandCounterIncrement();
 
+	/* pg_auth_trust */
+	StoreRoleTrust(stmt->role, roleid, trust, notrust);
+
 	if (stmt->action == +1)		/* add members to role */
 		AddRoleMems(rolename, roleid,
-					rolemembers, roleSpecsToIds(rolemembers),
+					rolemembers, roleSpecsToIds(rolemembers, false),
 					GetUserId(), false);
 	else if (stmt->action == -1)	/* drop members from role */
 		DelRoleMems(rolename, roleid,
-					rolemembers, roleSpecsToIds(rolemembers),
+					rolemembers, roleSpecsToIds(rolemembers, false),
 					false);
 
 	/*
@@ -971,11 +1062,30 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
 /*
  * DROP ROLE
  */
+static void
+DeleteRoleCatalogRows(Relation rel, Oid index, AttrNumber att, Oid key)
+{
+	ScanKeyData scankey;
+	SysScanDesc sscan;
+	HeapTuple	tuple;
+
+	ScanKeyInit(&scankey, att, BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(key));
+
+	sscan = systable_beginscan(rel, index, true, NULL, 1, &scankey);
+
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		CatalogTupleDelete(rel, &tuple->t_self);
+
+	systable_endscan(sscan);
+}
+
 void
 DropRole(DropRoleStmt *stmt)
 {
 	Relation	pg_authid_rel,
-				pg_auth_members_rel;
+				pg_auth_members_rel,
+				pg_auth_trust_rel;
 	ListCell   *item;
 
 	if (!have_createrole_privilege())
@@ -989,18 +1099,16 @@ DropRole(DropRoleStmt *stmt)
 	 */
 	pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
 	pg_auth_members_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
+	pg_auth_trust_rel = heap_open(AuthTrustRelationId, RowExclusiveLock);
 
 	foreach(item, stmt->roles)
 	{
 		RoleSpec   *rolspec = lfirst(item);
 		char	   *role;
-		HeapTuple	tuple,
-					tmp_tuple;
+		HeapTuple	tuple;
 		Form_pg_authid roleform;
-		ScanKeyData scankey;
 		char	   *detail;
 		char	   *detail_log;
-		SysScanDesc sscan;
 		Oid			roleid;
 
 		if (rolspec->roletype != ROLESPEC_CSTRING)
@@ -1086,35 +1194,26 @@ DropRole(DropRoleStmt *stmt)
 		 *
 		 * XXX what about grantor entries?	Maybe we should do one heap scan.
 		 */
-		ScanKeyInit(&scankey,
-					Anum_pg_auth_members_roleid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(roleid));
-
-		sscan = systable_beginscan(pg_auth_members_rel, AuthMemRoleMemIndexId,
-								   true, NULL, 1, &scankey);
-
-		while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
-		{
-			CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self);
-		}
-
-		systable_endscan(sscan);
+		DeleteRoleCatalogRows(pg_auth_members_rel,
+							  AuthMemRoleMemIndexId,
+							  Anum_pg_auth_members_roleid,
+							  roleid);
+		DeleteRoleCatalogRows(pg_auth_members_rel,
+							  AuthMemMemRoleIndexId,
+							  Anum_pg_auth_members_member,
+							  roleid);
 
-		ScanKeyInit(&scankey,
-					Anum_pg_auth_members_member,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(roleid));
-
-		sscan = systable_beginscan(pg_auth_members_rel, AuthMemMemRoleIndexId,
-								   true, NULL, 1, &scankey);
-
-		while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
-		{
-			CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self);
-		}
-
-		systable_endscan(sscan);
+		/*
+		 * Remove role from pg_auth_trust table (as grantor and trustee).
+		 */
+		DeleteRoleCatalogRows(pg_auth_trust_rel,
+							  AuthTrustGrantorTrusteeIndexId,
+							  Anum_pg_auth_trust_grantor,
+							  roleid);
+		DeleteRoleCatalogRows(pg_auth_trust_rel,
+							  AuthTrustTrusteeGrantorIndexId,
+							  Anum_pg_auth_trust_trustee,
+							  roleid);
 
 		/*
 		 * Remove any comments or security labels on this role.
@@ -1142,6 +1241,7 @@ DropRole(DropRoleStmt *stmt)
 	/*
 	 * Now we can clean up; but keep locks until commit.
 	 */
+	heap_close(pg_auth_trust_rel, NoLock);
 	heap_close(pg_auth_members_rel, NoLock);
 	heap_close(pg_authid_rel, NoLock);
 }
@@ -1293,7 +1393,7 @@ GrantRole(GrantRoleStmt *stmt)
 	else
 		grantor = GetUserId();
 
-	grantee_ids = roleSpecsToIds(stmt->grantee_roles);
+	grantee_ids = roleSpecsToIds(stmt->grantee_roles, false);
 
 	/* AccessShareLock is enough since we aren't modifying pg_authid */
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
@@ -1342,7 +1442,7 @@ GrantRole(GrantRoleStmt *stmt)
 void
 DropOwnedObjects(DropOwnedStmt *stmt)
 {
-	List	   *role_ids = roleSpecsToIds(stmt->roles);
+	List	   *role_ids = roleSpecsToIds(stmt->roles, false);
 	ListCell   *cell;
 
 	/* Check privileges */
@@ -1368,7 +1468,7 @@ DropOwnedObjects(DropOwnedStmt *stmt)
 void
 ReassignOwnedObjects(ReassignOwnedStmt *stmt)
 {
-	List	   *role_ids = roleSpecsToIds(stmt->roles);
+	List	   *role_ids = roleSpecsToIds(stmt->roles, false);
 	ListCell   *cell;
 	Oid			newrole;
 
@@ -1399,11 +1499,11 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
  * roleSpecsToIds
  *
  * Given a list of RoleSpecs, generate a list of role OIDs in the same order.
- *
- * ROLESPEC_PUBLIC is not allowed.
+ * If public_ok, ROLESPEC_PUBLIC translates to InvalidOid; otherwise, the
+ * presence of ROLESPEC_PUBLIC is an error.
  */
 List *
-roleSpecsToIds(List *memberNames)
+roleSpecsToIds(List *memberNames, bool public_ok)
 {
 	List	   *result = NIL;
 	ListCell   *l;
@@ -1413,7 +1513,10 @@ roleSpecsToIds(List *memberNames)
 		RoleSpec   *rolespec = lfirst_node(RoleSpec, l);
 		Oid			roleid;
 
-		roleid = get_rolespec_oid(rolespec, false);
+		if (public_ok && rolespec->roletype == ROLESPEC_PUBLIC)
+			roleid = InvalidOid;
+		else
+			roleid = get_rolespec_oid(rolespec, false);
 		result = lappend_oid(result, roleid);
 	}
 	return result;
@@ -1673,3 +1776,181 @@ DelRoleMems(const char *rolename, Oid roleid,
 	 */
 	heap_close(pg_authmem_rel, NoLock);
 }
+
+
+/*
+ * StoreRoleTrust -- apply [NO] TRUST clauses
+ *
+ * role: RoleSpec of role to add to (used only for error messages)
+ * roleid: OID of role to add to (InvalidOid means PUBLIC)
+ * addSpecs: list of RoleSpec of roles to add
+ * delSpecs: list of RoleSpec of roles to remove
+ */
+static void
+StoreRoleTrust(const RoleSpec *role, Oid roleid,
+			   List *addSpecs, List *delSpecs)
+{
+	List	   *addIds,
+			   *delIds;
+
+	if (!addSpecs && !delSpecs)
+		return;					/* nothing to do */
+
+	/* Reject request to both add and remove the same role. */
+	addIds = roleSpecsToIds(addSpecs, true);
+	delIds = roleSpecsToIds(delSpecs, true);
+	if (list_intersection_oid(addIds, delIds) != NIL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("conflicting or redundant options")));
+
+	/*
+	 * Check permissions: must have createrole or admin option on the role to
+	 * be changed.	Must be superuser if the target is PUBLIC or a superuser.
+	 */
+	if (superuser_arg(roleid))
+	{
+		if (!superuser())
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("must be superuser to alter superusers")));
+	}
+	else if (role->roletype == ROLESPEC_PUBLIC)
+	{
+		Assert(roleid == InvalidOid);
+		if (!superuser())
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("must be superuser to alter trust of PUBLIC")));
+	}
+	else
+	{
+		if (!have_createrole_privilege() &&
+			!is_admin_of_role(GetUserId(), roleid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("must have admin option on role \"%s\"",
+							get_rolespec_name(role))));
+	}
+
+	if (addSpecs)
+		AddRoleTrust(role, roleid, addSpecs, addIds);
+	if (delSpecs)
+		DelRoleTrust(role, roleid, delSpecs, delIds);
+}
+
+/*
+ * AddRoleTrust -- Record a role as trusting some other roles
+ *
+ * role: RoleSpec of role to add to (used only for error messages)
+ * roleid: OID of role to add to (InvalidOid means PUBLIC)
+ * memberSpecs: list of RoleSpec of roles to add (used only for error messages)
+ * memberIds: OIDs of roles to add (InvalidOid means PUBLIC)
+ */
+static void
+AddRoleTrust(const RoleSpec *role, Oid roleid,
+			 List *memberSpecs, List *memberIds)
+{
+	Relation	pg_auth_trust_rel;
+	TupleDesc	pg_auth_trust_dsc;
+	ListCell   *specitem;
+	ListCell   *iditem;
+
+	Assert(list_length(memberSpecs) == list_length(memberIds));
+
+	pg_auth_trust_rel = heap_open(AuthTrustRelationId, RowExclusiveLock);
+	pg_auth_trust_dsc = RelationGetDescr(pg_auth_trust_rel);
+
+	forboth(specitem, memberSpecs, iditem, memberIds)
+	{
+		RoleSpec   *memberRole = lfirst(specitem);
+		Oid			memberid = lfirst_oid(iditem);
+		HeapTuple	tuple;
+		Datum		new_record[Natts_pg_auth_trust];
+		bool		new_record_nulls[Natts_pg_auth_trust];
+
+		/* Unlike role membership, loops in role trust are fine. */
+
+		/*
+		 * Lock prevents a dangling pg_auth_trust row when someone is dropping
+		 * the target member concurrently.
+		 */
+		if (OidIsValid(memberid))
+			shdepLockAndCheckObject(AuthIdRelationId, memberid);
+
+		/* Warn if entry for this role/member already exists. */
+		if (SearchSysCacheExists2(AUTHTRUSTGRANTORTRUSTEE,
+								  ObjectIdGetDatum(roleid),
+								  ObjectIdGetDatum(memberid)))
+		{
+			ereport(NOTICE,
+					(errmsg("role \"%s\" already trusts role \"%s\"",
+							get_rolespec_name(role),
+							get_rolespec_name(memberRole))));
+			continue;
+		}
+
+		/* Insert a tuple */
+		new_record[Anum_pg_auth_trust_grantor - 1] = ObjectIdGetDatum(roleid);
+		new_record[Anum_pg_auth_trust_trustee - 1] = ObjectIdGetDatum(memberid);
+		MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+		tuple = heap_form_tuple(pg_auth_trust_dsc,
+								new_record, new_record_nulls);
+		CatalogTupleInsert(pg_auth_trust_rel, tuple);
+
+		/* CCI after each change, in case there are duplicates in list */
+		CommandCounterIncrement();
+	}
+
+	heap_close(pg_auth_trust_rel, NoLock);
+}
+
+/*
+ * DelRoleTrust -- Remove members from a role's trusted list
+ *
+ * role: RoleSpec of role to del from (used only for error messages)
+ * roleid: OID of role to del from
+ * memberSpecs: list of RoleSpec of roles to del (used only for error messages)
+ * memberIds: OIDs of roles to del
+ */
+static void
+DelRoleTrust(const RoleSpec *role, Oid roleid,
+			 List *memberSpecs, List *memberIds)
+{
+	Relation	pg_auth_trust_rel;
+	ListCell   *specitem;
+	ListCell   *iditem;
+
+	Assert(list_length(memberSpecs) == list_length(memberIds));
+
+	pg_auth_trust_rel = heap_open(AuthTrustRelationId, RowExclusiveLock);
+
+	forboth(specitem, memberSpecs, iditem, memberIds)
+	{
+		RoleSpec   *memberRole = lfirst(specitem);
+		Oid			memberid = lfirst_oid(iditem);
+		HeapTuple	tuple;
+
+		/* Warn if no corresponding entry exists. */
+		tuple = SearchSysCache2(AUTHTRUSTGRANTORTRUSTEE,
+								ObjectIdGetDatum(roleid),
+								ObjectIdGetDatum(memberid));
+		if (!HeapTupleIsValid(tuple))
+		{
+			ereport(WARNING,
+					(errmsg("role \"%s\" does not trust role \"%s\"",
+							get_rolespec_name(role),
+							get_rolespec_name(memberRole))));
+			continue;
+		}
+
+		CatalogTupleDelete(pg_auth_trust_rel, &tuple->t_self);
+		ReleaseSysCache(tuple);
+
+		/* CCI after each change, in case there are duplicates in list */
+		CommandCounterIncrement();
+	}
+
+	heap_close(pg_auth_trust_rel, NoLock);
+}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 25b3b03..0fb9019 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1527,8 +1527,6 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	Relation	onerel;
 	LockRelId	onerelid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
 	int			save_nestlevel;
 
 	Assert(params != NULL);
@@ -1688,9 +1686,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * arrange to make GUC variable changes local to this command. (This is
 	 * unnecessary, but harmless, for lazy VACUUM.)
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
+	PushTransientUser(onerel->rd_rel->relowner, true, false);
 	save_nestlevel = NewGUCNestLevel();
 
 	/*
@@ -1713,11 +1709,9 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	else
 		lazy_vacuum_rel(onerel, options, params, vac_strategy);
 
-	/* Roll back any GUC changes executed by index functions */
+	/* Roll back GUC changes by index functions; revert user ID */
 	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	PopTransientUser();
 
 	/* all done with this class, but hold lock until commit */
 	if (onerel)
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index d9087ca..1a242e5 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -891,6 +891,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				OpExpr	   *op = (OpExpr *) node;
 
+				pg_oper_trustcheck_extended(op->opno, false);
 				ExecInitFunc(&scratch, node,
 							 op->args, op->opfuncid, op->inputcollid,
 							 state);
@@ -902,6 +903,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				DistinctExpr *op = (DistinctExpr *) node;
 
+				pg_oper_trustcheck_extended(op->opno, false);
 				ExecInitFunc(&scratch, node,
 							 op->args, op->opfuncid, op->inputcollid,
 							 state);
@@ -924,6 +926,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 			{
 				NullIfExpr *op = (NullIfExpr *) node;
 
+				pg_oper_trustcheck_extended(op->opno, false);
 				ExecInitFunc(&scratch, node,
 							 op->args, op->opfuncid, op->inputcollid,
 							 state);
@@ -949,19 +952,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				Expr	   *arrayarg;
 				FmgrInfo   *finfo;
 				FunctionCallInfo fcinfo;
-				AclResult	aclresult;
 
 				Assert(list_length(opexpr->args) == 2);
 				scalararg = (Expr *) linitial(opexpr->args);
 				arrayarg = (Expr *) lsecond(opexpr->args);
 
 				/* Check permission to call function */
-				aclresult = pg_proc_aclcheck(opexpr->opfuncid,
-											 GetUserId(),
-											 ACL_EXECUTE);
-				if (aclresult != ACLCHECK_OK)
-					aclcheck_error(aclresult, OBJECT_FUNCTION,
-								   get_func_name(opexpr->opfuncid));
+				pg_oper_trustcheck_extended(opexpr->opno, false);
+				pg_proc_execcheck(opexpr->opfuncid);
 				InvokeFunctionExecuteHook(opexpr->opfuncid);
 
 				/* Set up the primary fmgr lookup information */
@@ -2159,16 +2157,13 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 			 Oid inputcollid, ExprState *state)
 {
 	int			nargs = list_length(args);
-	AclResult	aclresult;
 	FmgrInfo   *flinfo;
 	FunctionCallInfo fcinfo;
 	int			argno;
 	ListCell   *lc;
 
 	/* Check permission to call function */
-	aclresult = pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(funcid));
+	pg_proc_execcheck(funcid);
 	InvokeFunctionExecuteHook(funcid);
 
 	/*
@@ -3378,13 +3373,9 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
 		Oid			foid = eqfunctions[natt];
 		FmgrInfo   *finfo;
 		FunctionCallInfo fcinfo;
-		AclResult	aclresult;
 
 		/* Check permission to call function */
-		aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(foid));
-
+		pg_proc_execcheck(foid);
 		InvokeFunctionExecuteHook(foid);
 
 		/* Set up the primary fmgr lookup information */
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index bf73f05..0ff5293 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -677,12 +677,8 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node,
 		   SetExprState *sexpr, PlanState *parent,
 		   MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF)
 {
-	AclResult	aclresult;
-
 	/* Check permission to call function */
-	aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(foid));
+	pg_proc_execcheck(foid);
 	InvokeFunctionExecuteHook(foid);
 
 	/*
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index daf56cd..dc5bb48 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -2569,6 +2569,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		if (aclresult != ACLCHECK_OK)
 			aclcheck_error(aclresult, OBJECT_AGGREGATE,
 						   get_func_name(aggref->aggfnoid));
+		pg_proc_trustcheck(aggref->aggfnoid);
 		InvokeFunctionExecuteHook(aggref->aggfnoid);
 
 		/* planner recorded transition state type in the Aggref itself */
@@ -2641,7 +2642,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 			}
 		}
 
-		/* Check that aggregate owner has permission to call component fns */
+		/*
+		 * Check that aggregate owner has permission to call component
+		 * functions and that the necessary trust relationships exist.
+		 */
 		{
 			HeapTuple	procTuple;
 			Oid			aggOwner;
@@ -2659,6 +2663,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 			if (aclresult != ACLCHECK_OK)
 				aclcheck_error(aclresult, OBJECT_FUNCTION,
 							   get_func_name(transfn_oid));
+			pg_proc_trustcheck(transfn_oid);
 			InvokeFunctionExecuteHook(transfn_oid);
 			if (OidIsValid(finalfn_oid))
 			{
@@ -2667,6 +2672,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(finalfn_oid));
+				pg_proc_trustcheck(finalfn_oid);
 				InvokeFunctionExecuteHook(finalfn_oid);
 			}
 			if (OidIsValid(serialfn_oid))
@@ -2676,6 +2682,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(serialfn_oid));
+				pg_proc_trustcheck(serialfn_oid);
 				InvokeFunctionExecuteHook(serialfn_oid);
 			}
 			if (OidIsValid(deserialfn_oid))
@@ -2685,6 +2692,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				if (aclresult != ACLCHECK_OK)
 					aclcheck_error(aclresult, OBJECT_FUNCTION,
 								   get_func_name(deserialfn_oid));
+				pg_proc_trustcheck(deserialfn_oid);
 				InvokeFunctionExecuteHook(deserialfn_oid);
 			}
 		}
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 298e370..1537ac6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2404,7 +2404,6 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
 		WindowFunc *wfunc = wfuncstate->wfunc;
 		WindowStatePerFunc perfuncstate;
-		AclResult	aclresult;
 		int			i;
 
 		if (wfunc->winref != node->winref)	/* planner screwed up? */
@@ -2432,11 +2431,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 		wfuncstate->wfuncno = wfuncno;
 
 		/* Check permission to call window function */
-		aclresult = pg_proc_aclcheck(wfunc->winfnoid, GetUserId(),
-									 ACL_EXECUTE);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult, OBJECT_FUNCTION,
-						   get_func_name(wfunc->winfnoid));
+		pg_proc_execcheck(wfunc->winfnoid);
 		InvokeFunctionExecuteHook(wfunc->winfnoid);
 
 		/* Fill in the perfuncstate data */
@@ -2715,6 +2710,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 		if (aclresult != ACLCHECK_OK)
 			aclcheck_error(aclresult, OBJECT_FUNCTION,
 						   get_func_name(transfn_oid));
+		pg_proc_trustcheck(transfn_oid);
 		InvokeFunctionExecuteHook(transfn_oid);
 
 		if (OidIsValid(invtransfn_oid))
@@ -2724,6 +2720,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 			if (aclresult != ACLCHECK_OK)
 				aclcheck_error(aclresult, OBJECT_FUNCTION,
 							   get_func_name(invtransfn_oid));
+			pg_proc_trustcheck(invtransfn_oid);
 			InvokeFunctionExecuteHook(invtransfn_oid);
 		}
 
@@ -2734,6 +2731,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 			if (aclresult != ACLCHECK_OK)
 				aclcheck_error(aclresult, OBJECT_FUNCTION,
 							   get_func_name(finalfn_oid));
+			pg_proc_trustcheck(finalfn_oid);
 			InvokeFunctionExecuteHook(finalfn_oid);
 		}
 	}
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 55fd4c3..61ad7e6 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -846,6 +846,32 @@ list_intersection_int(const List *list1, const List *list2)
 }
 
 /*
+ * This variant of list_intersection() operates upon lists of OIDs.
+ */
+List *
+list_intersection_oid(const List *list1, const List *list2)
+{
+	List	   *result;
+	const ListCell *cell;
+
+	if (list1 == NIL || list2 == NIL)
+		return NIL;
+
+	Assert(IsOidList(list1));
+	Assert(IsOidList(list2));
+
+	result = NIL;
+	foreach(cell, list1)
+	{
+		if (list_member_oid(list2, lfirst_oid(cell)))
+			result = lappend_oid(result, lfirst_oid(cell));
+	}
+
+	check_list_invariants(result);
+	return result;
+}
+
+/*
  * Return a list that contains all the cells in list1 that are not in
  * list2. The returned list is freshly allocated via palloc(), but the
  * cells themselves point to the same objects as the cells of the
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8df3693..b18cc39 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2767,7 +2767,12 @@ eval_const_expressions_mutator(Node *node,
 										   true,
 										   true,
 										   context);
-				if (simple)		/* successfully simplified it */
+
+				/*
+				 * If we don't trust the operator owner, forget the
+				 * simplification and expect an error later.
+				 */
+				if (simple && pg_oper_trustcheck_extended(expr->opno, true))
 					return (Node *) simple;
 
 				/*
@@ -4164,6 +4169,18 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		*args_p = args;
 	}
 
+	/*
+	 * Don't try to simplify an untrusted function; evaluate_function() would
+	 * throw an error, and we'd rather fail later.  inline_function() must not
+	 * be attempted, because we could no longer check trust.  XXX Should we
+	 * likewise preview the ACL check?
+	 */
+	if (!pg_proc_trustcheck_extended(funcid, true))
+	{
+		ReleaseSysCache(func_tuple);
+		return NULL;
+	}
+
 	/* Now attempt simplification of the function call proper. */
 
 	newexpr = evaluate_function(funcid, result_type, result_typmod,
@@ -4191,6 +4208,12 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		fexpr.args = args;
 		fexpr.location = -1;
 
+		/*
+		 * Only superusers can associate a transform function, so skip
+		 * security checks.  If the original function has security relevance,
+		 * its transform function must either perform required checks or
+		 * return a node tree that still elicits the right runtime checks.
+		 */
 		newexpr = (Expr *)
 			DatumGetPointer(OidFunctionCall1(func_form->protransform,
 											 PointerGetDatum(&fexpr)));
@@ -4607,6 +4630,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 	/* Check permission to call function (fail later, if not) */
 	if (pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
+	/* simplify_function() already checked trust. */
 
 	/* Check whether a plugin wants to hook function entry/exit */
 	if (FmgrHookIsNeeded(funcid))
@@ -5104,7 +5128,8 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 		return NULL;
 
 	/* Check permission to call function (fail later, if not) */
-	if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
+	if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK ||
+		!pg_proc_trustcheck_extended(func_oid, true))
 		return NULL;
 
 	/* Check whether a plugin wants to hook function entry/exit */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2c2208f..23e3290 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -682,7 +682,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
 	TREAT TRIGGER TRIM TRUE_P
-	TRUNCATE TRUSTED TYPE_P TYPES_P
+	TRUNCATE TRUST TRUSTED TYPE_P TYPES_P
 
 	UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
 	UNTIL UPDATE USER USING
@@ -1038,6 +1038,14 @@ AlterOptRoleElem:
 				{
 					$$ = makeDefElem("validUntil", (Node *)makeString($3), @1);
 				}
+			| TRUST role_list
+				{
+					$$ = makeDefElem("trust", (Node *)$2, @1);
+				}
+			| NO TRUST role_list
+				{
+					$$ = makeDefElem("notrust", (Node *)$3, @1);
+				}
 		/*	Supported but not documented for roles, for use by ALTER GROUP. */
 			| USER role_list
 				{
@@ -15225,6 +15233,7 @@ unreserved_keyword:
 			| TRANSFORM
 			| TRIGGER
 			| TRUNCATE
+			| TRUST
 			| TRUSTED
 			| TYPE_P
 			| TYPES_P
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index d16ba5e..4b0b96c 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -319,10 +319,12 @@ HandleFunctionRequest(StringInfo msgBuf)
 					   get_namespace_name(fip->namespace));
 	InvokeNamespaceSearchHook(fip->namespace, true);
 
-	aclresult = pg_proc_aclcheck(fid, GetUserId(), ACL_EXECUTE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, OBJECT_FUNCTION,
-					   get_func_name(fid));
+	/*
+	 * A trust check is arguably superfluous,, seeing the user specified the
+	 * function by OID.  Doesn't seem worth a special case in the trust rules,
+	 * though.
+	 */
+	pg_proc_execcheck(fid);
 	InvokeFunctionExecuteHook(fid);
 
 	/*
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 30cf3d0..82bab7c 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -5310,6 +5310,10 @@ get_rolespec_name(const RoleSpec *role)
 	Form_pg_authid authForm;
 	char	   *rolename;
 
+	if (role->roletype == ROLESPEC_PUBLIC)
+		return pstrdup("public");
+	Assert(role->roletype == ROLESPEC_CSTRING);
+
 	tp = get_rolespec_tuple(role);
 	authForm = (Form_pg_authid) GETSTRUCT(tp);
 	rolename = pstrdup(NameStr(authForm->rolname));
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 5a5d0a0..cc7c626 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -34,6 +34,7 @@
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/int8.h"
@@ -1799,8 +1800,11 @@ make_range(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper,
 	/* no need to call canonical on empty ranges ... */
 	if (OidIsValid(typcache->rng_canonical_finfo.fn_oid) &&
 		!RangeIsEmpty(range))
+	{
+		pg_proc_trustcheck(typcache->rng_canonical_finfo.fn_oid);
 		range = DatumGetRangeTypeP(FunctionCall1(&typcache->rng_canonical_finfo,
 												 RangeTypePGetDatum(range)));
+	}
 
 	return range;
 }
diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index 450479e..87248f4 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -16,6 +16,7 @@
 
 #include "access/gist.h"
 #include "access/stratnum.h"
+#include "utils/acl.h"
 #include "utils/float.h"
 #include "utils/fmgrprotos.h"
 #include "utils/datum.h"
@@ -1522,6 +1523,7 @@ call_subtype_diff(TypeCacheEntry *typcache, Datum val1, Datum val2)
 {
 	float8		value;
 
+	pg_proc_trustcheck(typcache->rng_subdiff_finfo.fn_oid);
 	value = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
 											 typcache->rng_collation,
 											 val1, val2));
diff --git a/src/backend/utils/adt/rangetypes_selfuncs.c b/src/backend/utils/adt/rangetypes_selfuncs.c
index 32c4933..7337a50 100644
--- a/src/backend/utils/adt/rangetypes_selfuncs.c
+++ b/src/backend/utils/adt/rangetypes_selfuncs.c
@@ -23,6 +23,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
+#include "utils/acl.h"
 #include "utils/float.h"
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
@@ -695,6 +696,8 @@ get_position(TypeCacheEntry *typcache, RangeBound *value, RangeBound *hist1,
 		if (!has_subdiff)
 			return 0.5;
 
+		pg_proc_trustcheck(typcache->rng_subdiff_finfo.fn_oid);
+
 		/* Calculate relative position using subdiff function. */
 		bin_width = DatumGetFloat8(FunctionCall2Coll(
 													 &typcache->rng_subdiff_finfo,
@@ -806,11 +809,14 @@ get_distance(TypeCacheEntry *typcache, RangeBound *bound1, RangeBound *bound2)
 		 * value of 1.0 if no subdiff is available.
 		 */
 		if (has_subdiff)
+		{
+			pg_proc_trustcheck(typcache->rng_subdiff_finfo.fn_oid);
 			return
 				DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
 												 typcache->rng_collation,
 												 bound2->val,
 												 bound1->val));
+		}
 		else
 			return 1.0;
 	}
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 9c50e4c..b344825 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -26,6 +26,7 @@
 
 #include "catalog/pg_operator.h"
 #include "commands/vacuum.h"
+#include "utils/acl.h"
 #include "utils/float.h"
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
@@ -165,6 +166,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 				 * For an ordinary range, use subdiff function between upper
 				 * and lower bound values.
 				 */
+				pg_proc_trustcheck(typcache->rng_subdiff_finfo.fn_oid);
 				length = DatumGetFloat8(FunctionCall2Coll(
 														  &typcache->rng_subdiff_finfo,
 														  typcache->rng_collation,
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index cdda860..f43e5a9 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2385,8 +2385,6 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 {
 	SPIPlanPtr	qplan;
 	Relation	query_rel;
-	Oid			save_userid;
-	int			save_sec_context;
 
 	/*
 	 * Use the query type code to determine whether the query is run against
@@ -2398,10 +2396,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 		query_rel = fk_rel;
 
 	/* Switch to proper UID to perform check as */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE |
-						   SECURITY_NOFORCE_RLS);
+	PushTransientUser(RelationGetForm(query_rel)->relowner, false, true);
 
 	/* Create the plan */
 	qplan = SPI_prepare(querystr, nargs, argtypes);
@@ -2409,8 +2404,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 	if (qplan == NULL)
 		elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), querystr);
 
-	/* Restore UID and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	PopTransientUser();
 
 	/* Save the plan if requested */
 	if (cache_plan)
@@ -2439,8 +2433,6 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	Snapshot	crosscheck_snapshot;
 	int			limit;
 	int			spi_result;
-	Oid			save_userid;
-	int			save_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -2519,10 +2511,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0;
 
 	/* Switch to proper UID to perform check as */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE |
-						   SECURITY_NOFORCE_RLS);
+	PushTransientUser(RelationGetForm(query_rel)->relowner, false, true);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
@@ -2530,8 +2519,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 									  test_snapshot, crosscheck_snapshot,
 									  false, false, limit);
 
-	/* Restore UID and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	PopTransientUser();
 
 	/* Check result */
 	if (spi_result < 0)
@@ -2964,6 +2952,12 @@ ri_AttributesEqual(Oid eq_opr, Oid typeid,
 	/* Do we need to cast the values? */
 	if (OidIsValid(entry->cast_func_finfo.fn_oid))
 	{
+		/*
+		 * Superusers govern operator classes, but creating a cast is not so
+		 * restricted.  Protect against trojan cast functions.
+		 */
+		pg_proc_trustcheck(entry->cast_func_finfo.fn_oid);
+
 		oldvalue = FunctionCall3(&entry->cast_func_finfo,
 								 oldvalue,
 								 Int32GetDatum(-1), /* typmod */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index ffca0fe..b5605c0 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -92,7 +92,14 @@
  * should ignore the actual operator collation and use DEFAULT_COLLATION_OID.
  * We expect that the error induced by doing this is usually not large enough
  * to justify complicating matters.
- *----------
+ *
+ * Only superusers can define selectivity functions, so there's no need for a
+ * trust check before calling one.  Since unprivileged users can attach core
+ * selectivity functions to any operator, selectivity functions that call the
+ * associated operator do perform a trust check on the operator's underlying
+ * function.  They do not check trust on the operator itself; the executor
+ * will check, and any misguidance here would merely deceive the planner.
+ * ----------
  */
 
 #include "postgres.h"
@@ -363,6 +370,7 @@ var_eq_const(VariableStatData *vardata, Oid operator,
 		{
 			FmgrInfo	eqproc;
 
+			pg_proc_trustcheck(opfuncoid);
 			fmgr_info(opfuncoid, &eqproc);
 
 			for (i = 0; i < sslot.nvalues; i++)
@@ -573,6 +581,7 @@ scalarineqsel(PlannerInfo *root, Oid operator, bool isgt, bool iseq,
 			  VariableStatData *vardata, Datum constval, Oid consttype)
 {
 	Form_pg_statistic stats;
+	Oid			opcode;
 	FmgrInfo	opproc;
 	double		mcv_selec,
 				hist_selec,
@@ -586,7 +595,9 @@ scalarineqsel(PlannerInfo *root, Oid operator, bool isgt, bool iseq,
 	}
 	stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
 
-	fmgr_info(get_opcode(operator), &opproc);
+	opcode = get_opcode(operator);
+	pg_proc_trustcheck(opcode);
+	fmgr_info(opcode, &opproc);
 
 	/*
 	 * If we have most-common-values info, add up the fractions of the MCV
@@ -664,6 +675,7 @@ mcv_selectivity(VariableStatData *vardata, FmgrInfo *opproc,
 	{
 		for (i = 0; i < sslot.nvalues; i++)
 		{
+			/* Caller is responsible for trust check. */
 			if (varonleft ?
 				DatumGetBool(FunctionCall2Coll(opproc,
 											   DEFAULT_COLLATION_OID,
@@ -742,6 +754,7 @@ histogram_selectivity(VariableStatData *vardata, FmgrInfo *opproc,
 
 			for (i = n_skip; i < sslot.nvalues - n_skip; i++)
 			{
+				/* Caller is responsible for trust check. */
 				if (varonleft ?
 					DatumGetBool(FunctionCall2Coll(opproc,
 												   DEFAULT_COLLATION_OID,
@@ -872,6 +885,7 @@ ineq_histogram_selectivity(PlannerInfo *root,
 														 NULL,
 														 &sslot.values[probe]);
 
+				/* Caller is responsible for trust check. */
 				ltcmp = DatumGetBool(FunctionCall2Coll(opproc,
 													   DEFAULT_COLLATION_OID,
 													   sslot.values[probe],
@@ -1387,12 +1401,15 @@ patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate)
 		 */
 		Selectivity selec;
 		int			hist_size;
+		Oid			opcode;
 		FmgrInfo	opproc;
 		double		mcv_selec,
 					sumcommon;
 
 		/* Try to use the histogram entries to get selectivity */
-		fmgr_info(get_opcode(operator), &opproc);
+		opcode = get_opcode(operator);
+		pg_proc_trustcheck(opcode);
+		fmgr_info(opcode, &opproc);
 
 		selec = histogram_selectivity(&vardata, &opproc, constval, true,
 									  10, 1, &hist_size);
@@ -2478,6 +2495,7 @@ eqjoinsel_inner(Oid opfuncoid,
 		int			i,
 					nmatches;
 
+		pg_proc_trustcheck(opfuncoid);
 		fmgr_info(opfuncoid, &eqproc);
 		hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool));
 		hasmatch2 = (bool *) palloc0(sslot2->nvalues * sizeof(bool));
@@ -2691,6 +2709,7 @@ eqjoinsel_semi(Oid opfuncoid,
 		 */
 		clamped_nvalues2 = Min(sslot2->nvalues, nd2);
 
+		pg_proc_trustcheck(opfuncoid);
 		fmgr_info(opfuncoid, &eqproc);
 		hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool));
 		hasmatch2 = (bool *) palloc0(clamped_nvalues2 * sizeof(bool));
@@ -5396,6 +5415,8 @@ get_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop,
 		bool		tmax_is_mcv = false;
 		FmgrInfo	opproc;
 
+		/* Could skip the trust check: currently always an opclass member. */
+		pg_proc_trustcheck(opfuncoid);
 		fmgr_info(opfuncoid, &opproc);
 
 		for (i = 0; i < sslot.nvalues; i++)
@@ -6021,6 +6042,7 @@ prefix_selectivity(PlannerInfo *root, VariableStatData *vardata,
 								 BTGreaterEqualStrategyNumber);
 	if (cmpopr == InvalidOid)
 		elog(ERROR, "no >= operator for opfamily %u", opfamily);
+	/* Skip trust check since it's obviously an opclass member. */
 	fmgr_info(get_opcode(cmpopr), &opproc);
 
 	prefixsel = ineq_histogram_selectivity(root, vardata,
@@ -6427,6 +6449,7 @@ make_greater_string(const Const *str_const, FmgrInfo *ltproc, Oid collation)
 			else
 				workstr_const = string_to_const(workstr, datatype);
 
+			/* opclass function; no trust check */
 			if (DatumGetBool(FunctionCall2Coll(ltproc,
 											   collation,
 											   cmpstr,
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index c26808a..3087fd4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_trust.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
@@ -231,6 +232,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{AuthTrustRelationId,		/* AUTHTRUSTGRANTORTRUSTEE */
+		AuthTrustGrantorTrusteeIndexId,
+		2,
+		{
+			Anum_pg_auth_trust_grantor,
+			Anum_pg_auth_trust_trustee,
+			0,
+			0
+		},
+		32
+	},
 	{AuthIdRelationId,			/* AUTHNAME */
 		AuthIdRolnameIndexId,
 		1,
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 73ff48c..0fda601 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -662,8 +662,6 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	Datum		result;
 	struct fmgr_security_definer_cache *volatile fcache;
 	FmgrInfo   *save_flinfo;
-	Oid			save_userid;
-	int			save_sec_context;
 	volatile int save_nestlevel;
 	PgStat_FunctionCallUsage fcusage;
 
@@ -708,16 +706,13 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	else
 		fcache = fcinfo->flinfo->fn_extra;
 
-	/* GetUserIdAndSecContext is cheap enough that no harm in a wasted call */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
 	if (fcache->proconfig)		/* Need a new GUC nesting level */
 		save_nestlevel = NewGUCNestLevel();
 	else
 		save_nestlevel = 0;		/* keep compiler quiet */
 
 	if (OidIsValid(fcache->userid))
-		SetUserIdAndSecContext(fcache->userid,
-							   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+		PushTransientUser(fcache->userid, false, false);
 
 	if (fcache->proconfig)
 	{
@@ -770,7 +765,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	if (fcache->proconfig)
 		AtEOXact_GUC(true, save_nestlevel);
 	if (OidIsValid(fcache->userid))
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		PopTransientUser();
 	if (fmgr_hook)
 		(*fmgr_hook) (FHET_END, &fcache->flinfo, &fcache->arg);
 
@@ -2223,9 +2218,7 @@ CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid)
 	 * 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, OBJECT_FUNCTION, NameStr(procStruct->proname));
+	pg_proc_execcheck(functionOid);
 
 	ReleaseSysCache(procTup);
 	ReleaseSysCache(langTup);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 3d10aa5..d94819c 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -234,36 +234,48 @@ ChangeToDataDir(void)
  * changed by SET SESSION AUTHORIZATION (if AuthenticatedUserIsSuperuser).
  * This is the ID reported by the SESSION_USER SQL function.
  *
- * OuterUserId is the current user ID in effect at the "outer level" (outside
- * any transaction or function).  This is initially the same as SessionUserId,
- * but can be changed by SET ROLE to any role that SessionUserId is a
- * member of.  (XXX rename to something like CurrentRoleId?)
+ * OuterUser is the current user in effect at the "outer level" (outside any
+ * transaction or function).  This initially has SessionUserId, but SET ROLE
+ * can change that to any role that SessionUserId is a member of.  (XXX rename
+ * to something like CurrentRole?)
  *
- * CurrentUserId is the current effective user ID; this is the one to use
- * for all normal permissions-checking purposes.  At outer level this will
- * be the same as OuterUserId, but it changes during calls to SECURITY
- * DEFINER functions, as well as locally in some specialized commands.
+ * CurrentUser is the effective user; this is the one to use for all normal
+ * permissions-checking purposes.  At outer level this will be the same as
+ * OuterUser, but it changes during calls to SECURITY DEFINER functions, as
+ * well as locally in some specialized commands.
  *
- * SecurityRestrictionContext holds flags indicating reason(s) for changing
- * CurrentUserId.  In some cases we need to lock down operations that are
- * not directly controlled by privilege settings, and this provides a
- * convenient way to do it.
+ *
+ * Operations that change only CurrentUser do so by adding entries to the
+ * TransientUserStack.  OuterUser points to the bottom of that stack, and
+ * CurrentUser points to the top.  The stack also records transitions into
+ * security-restricted and "FORCE ROW LEVEL SECURITY"-ignoring operations.
  * ----------------------------------------------------------------
  */
 static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
-static Oid	OuterUserId = InvalidOid;
-static Oid	CurrentUserId = InvalidOid;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
 static bool SessionUserIsSuperuser = false;
 
-static int	SecurityRestrictionContext = 0;
-
 /* We also remember if a SET ROLE is currently active */
 static bool SetRoleIsActive = false;
 
+/* The stack of transient user ID changes. */
+struct TransientUserEntry
+{
+	Oid			userid;
+	unsigned	security_restriction_depth;
+	bool		noforce_rls;
+	struct TransientUserStack *prev;
+};
+static struct TransientUserEntry *TransientUserStack;
+static int	TransientUserStack_cur;
+static int	TransientUserStack_max;
+
+#define OuterUser (&TransientUserStack[0])
+#define CurrentUser (&TransientUserStack[TransientUserStack_cur])
+
 /*
  * Initialize the basic environment for a postmaster child
  *
@@ -373,14 +385,12 @@ SwitchBackToLocalLatch(void)
 
 /*
  * GetUserId - get the current effective user ID.
- *
- * Note: there's no SetUserId() anymore; use SetUserIdAndSecContext().
  */
 Oid
 GetUserId(void)
 {
-	AssertState(OidIsValid(CurrentUserId));
-	return CurrentUserId;
+	AssertState(OidIsValid(CurrentUser->userid));
+	return CurrentUser->userid;
 }
 
 
@@ -390,20 +400,17 @@ GetUserId(void)
 Oid
 GetOuterUserId(void)
 {
-	AssertState(OidIsValid(OuterUserId));
-	return OuterUserId;
+	AssertState(OidIsValid(OuterUser->userid));
+	return OuterUser->userid;
 }
 
 
 static void
 SetOuterUserId(Oid userid)
 {
-	AssertState(SecurityRestrictionContext == 0);
+	AssertState(CurrentUser == OuterUser);
 	AssertArg(OidIsValid(userid));
-	OuterUserId = userid;
-
-	/* We force the effective user ID to match, too */
-	CurrentUserId = userid;
+	OuterUser->userid = userid;
 }
 
 
@@ -421,15 +428,14 @@ GetSessionUserId(void)
 static void
 SetSessionUserId(Oid userid, bool is_superuser)
 {
-	AssertState(SecurityRestrictionContext == 0);
+	AssertState(CurrentUser == OuterUser);
 	AssertArg(OidIsValid(userid));
 	SessionUserId = userid;
 	SessionUserIsSuperuser = is_superuser;
 	SetRoleIsActive = false;
 
 	/* We force the effective user IDs to match, too */
-	OuterUserId = userid;
-	CurrentUserId = userid;
+	OuterUser->userid = userid;
 }
 
 /*
@@ -444,74 +450,206 @@ GetAuthenticatedUserId(void)
 
 
 /*
- * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID
- * and the SecurityRestrictionContext flags.
+ * PushTransientUser/PopTransientUser - perform a temporary user ID change
  *
- * Currently there are three valid bits in SecurityRestrictionContext:
+ * (Sub)transaction abort will undo this change; otherwise, callers are
+ * responsible for pairing a pop with each push.  SET ROLE and SET SESSION
+ * AUTHORIZATION are forbidden until the last pop.
  *
- * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation
- * that is temporarily changing CurrentUserId via these functions.  This is
- * needed to indicate that the actual value of CurrentUserId is not in sync
- * with guc.c's internal state, so SET ROLE has to be disallowed.
+ * Specifying security_restricted=true signals the start of an operation that
+ * does not wish to trust called user-defined functions at all.  This prevents
+ * not only SET ROLE, but various other changes of session state that normally
+ * is unprotected but might possibly be used to subvert the calling session
+ * later.  An example is replacing an existing prepared statement with new
+ * code, which will then be executed with the outer session's permissions when
+ * the prepared statement is next used.  Since these restrictions are fairly
+ * draconian, we apply them only in contexts where the called functions are
+ * really supposed to be side-effect-free anyway, such as
+ * VACUUM/ANALYZE/REINDEX.  GUC changes are still permitted; the caller had
+ * better push a GUC nesting level, too.
  *
- * SECURITY_RESTRICTED_OPERATION indicates that we are inside an operation
- * that does not wish to trust called user-defined functions at all.  This
- * bit prevents not only SET ROLE, but various other changes of session state
- * that normally is unprotected but might possibly be used to subvert the
- * calling session later.  An example is replacing an existing prepared
- * statement with new code, which will then be executed with the outer
- * session's permissions when the prepared statement is next used.  Since
- * these restrictions are fairly draconian, we apply them only in contexts
- * where the called functions are really supposed to be side-effect-free
- * anyway, such as VACUUM/ANALYZE/REINDEX.
- *
- * SECURITY_NOFORCE_RLS indicates that we are inside an operation which should
- * ignore the FORCE ROW LEVEL SECURITY per-table indication.  This is used to
- * ensure that FORCE RLS does not mistakenly break referential integrity
- * checks.  Note that this is intentionally only checked when running as the
- * owner of the table (which should always be the case for referential
- * integrity checks).
- *
- * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current
- * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require
- * the new value to be valid.  In fact, these routines had better not
- * ever throw any kind of error.  This is because they are used by
- * StartTransaction and AbortTransaction to save/restore the settings,
- * and during the first transaction within a backend, the value to be saved
- * and perhaps restored is indeed invalid.  We have to be able to get
- * through AbortTransaction without asserting in case InitPostgres fails.
+ * Specifying noforce_rls=true indicates that we are inside an operation which
+ * should ignore the FORCE ROW LEVEL SECURITY per-table indication.  This is
+ * used to ensure that FORCE RLS does not mistakenly break referential
+ * integrity checks.  Note that this is intentionally only checked when
+ * running as the owner of the table (which should always be the case for
+ * referential integrity checks).
  */
 void
-GetUserIdAndSecContext(Oid *userid, int *sec_context)
+PushTransientUser(Oid userid,
+				  bool security_restricted, bool noforce_rls)
 {
-	*userid = CurrentUserId;
-	*sec_context = SecurityRestrictionContext;
+	struct TransientUserEntry *prev;
+
+	if (TransientUserStack_cur + 1 == TransientUserStack_max)
+	{
+		int			new_max = 2 * TransientUserStack_max;
+
+		TransientUserStack = repalloc(TransientUserStack,
+									  new_max * sizeof(*TransientUserStack));
+		TransientUserStack_max = new_max;
+	}
+
+	prev = CurrentUser;
+	TransientUserStack_cur++;
+	CurrentUser->userid = userid;
+	CurrentUser->security_restriction_depth = prev->security_restriction_depth;
+	if (security_restricted)
+		CurrentUser->security_restriction_depth++;
+	CurrentUser->noforce_rls = prev->noforce_rls || noforce_rls;
 }
 
 void
-SetUserIdAndSecContext(Oid userid, int sec_context)
+PopTransientUser(void)
 {
-	CurrentUserId = userid;
-	SecurityRestrictionContext = sec_context;
+	Assert(CurrentUser > OuterUser);
+	TransientUserStack_cur--;
 }
 
 
 /*
- * InLocalUserIdChange - are we inside a local change of CurrentUserId?
+ * GetTransientUser/RestoreTransientUser
+ *
+ * xact.c uses these functions to clean up transient user ID changes at
+ * (sub)transaction abort; they probably have no other use.  The TransientUser
+ * value should be considered opaque outside of this file; it is the stack
+ * offset of CurrentUser.
+ *
+ * Note that AtEOXact_GUC() takes responsibility for reverting changes to the
+ * session and outer user IDs.
  */
-bool
-InLocalUserIdChange(void)
+TransientUser
+GetTransientUser(void)
+{
+	return TransientUserStack_cur;
+}
+
+void
+RestoreTransientUser(TransientUser u)
 {
-	return (SecurityRestrictionContext & SECURITY_LOCAL_USERID_CHANGE) != 0;
+	Assert(u >= 0 && u <= TransientUserStack_cur);
+	TransientUserStack_cur = u;
 }
 
+
+Size
+EstimateTransientUserStackSpace(void)
+{
+	return mul_size(1 + 1 + TransientUserStack_cur,
+					sizeof(*TransientUserStack));
+}
+
+void
+SerializeTransientUserStack(Size maxsize, char *start_address)
+{
+	struct TransientUserEntry *state =
+		(struct TransientUserEntry *) start_address;
+
+	Assert(maxsize >= EstimateTransientUserStackSpace());
+
+	/* First entry stores the offset and capacity. */
+	state[0].userid = TransientUserStack_cur;
+	state[0].security_restriction_depth = TransientUserStack_max;
+	state[0].noforce_rls = false;
+
+	memcpy(state + 1, TransientUserStack,
+		   (TransientUserStack_cur + 1) * sizeof(*TransientUserStack));
+}
+
+void
+RestoreTransientUserStack(char *start_address)
+{
+	struct TransientUserEntry *state =
+		(struct TransientUserEntry *) start_address;
+	int			new_cur, new_max;
+
+	Assert(TransientUserStack_cur == 0);
+
+	new_cur = state[0].userid;
+	new_max = state[0].security_restriction_depth;
+	TransientUserStack = repalloc(TransientUserStack,
+								  new_max * sizeof(*TransientUserStack));
+	TransientUserStack_max = new_max;
+
+	TransientUserStack_cur = new_cur;
+	memcpy(TransientUserStack, state + 1,
+		   (TransientUserStack_cur + 1) * sizeof(*TransientUserStack));
+}
+
+
 /*
- * InSecurityRestrictedOperation - are we inside a security-restricted command?
+ * GetSecurityApplicableRoles - list roles that can veto code execution
+ *
+ * Of course, the current user is always concerned with the code that runs;
+ * such code uses its privileges.  Less obviously, roles that will be restored
+ * after popping a transient user change also have an interest.  Code that
+ * runs now can change GUCs, create temporary tables, and mutate the session
+ * in other ways; in doing so, it may entice code running in those higher
+ * stack frames to misbehave.  However, when a transient user change is done
+ * with the security_restricted flag, that change establishes a security
+ * partition; essential session-mutating actions are either forbidden therein
+ * or (GUCs) reverted on exit.  Therefore, roles on the stack outside the
+ * current security restriction depth need not be concerned with the code
+ * permitted to run.  Recognizing this exception is important; it allows, for
+ * example, superusers to run ANALYZE on user tables despite not trusting the
+ * functions used in index expressions on those tables.
+ *
+ * The session user and authenticated user are not considered directly;
+ * immediately after SET ROLE, only the new outer user qualifies.  SET ROLE is
+ * even less of a security partition than a SECURITY DEFINER call.  Not only
+ * can the new role mutate the session, it can issue RESET ROLE to regain the
+ * original credentials.  Issuing SET ROLE does not cap the potential damage
+ * from subsequently running arbitrary code, making it more a tool for users
+ * to run specifically-chosen commands with a different effective user ID.  In
+ * that light, it is marginally more useful to adopt the target role's trust
+ * relationships, suspending existing ones.  Similar arguments apply to SET
+ * SESSION AUTHORIZATION.
+ *
+ * The prepared list is sorted, free from duplicates, and palloc'd.
  */
-bool
-InSecurityRestrictedOperation(void)
+void
+GetSecurityApplicableRoles(Oid **roles, int *nroles)
 {
-	return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0;
+	struct TransientUserEntry *entry;
+	unsigned	depth = CurrentUser->security_restriction_depth;
+
+	/* Allocate worst-case space. */
+	*roles = palloc((TransientUserStack_cur + 1) * sizeof(**roles));
+
+	/*
+	 * Deduplicate and insertion-sort.  Typical stacks will be short, and
+	 * typical unique lists even shorter.
+	 */
+	*nroles = 0;
+	for (entry = CurrentUser; entry >= OuterUser; --entry)
+	{
+		int			i;
+
+		if (entry->security_restriction_depth != depth)
+			break;				/* cross into a different partition */
+
+		for (i = 0; i < *nroles; i++)
+		{
+			if (entry->userid == (*roles)[i])
+				goto next_stack_entry;
+			else if (entry->userid < (*roles)[i])
+			{
+				int			j;
+
+				for (j = *nroles; j > i; j--)
+					(*roles)[j] = (*roles)[j - 1];
+
+				break;
+			}
+		}
+
+		(*roles)[i] = entry->userid;
+		(*nroles)++;
+
+next_stack_entry:;
+	}
+
+	Assert(*nroles > 0);
 }
 
 /*
@@ -520,37 +658,26 @@ InSecurityRestrictedOperation(void)
 bool
 InNoForceRLSOperation(void)
 {
-	return (SecurityRestrictionContext & SECURITY_NOFORCE_RLS) != 0;
+	return CurrentUser->noforce_rls;;
 }
 
 
 /*
- * These are obsolete versions of Get/SetUserIdAndSecContext that are
- * only provided for bug-compatibility with some rather dubious code in
- * pljava.  We allow the userid to be set, but only when not inside a
- * security restriction context.
+ * InLocalUserIdChange - are we inside a local change of CurrentUser?
  */
-void
-GetUserIdAndContext(Oid *userid, bool *sec_def_context)
+bool
+InLocalUserIdChange(void)
 {
-	*userid = CurrentUserId;
-	*sec_def_context = InLocalUserIdChange();
+	return CurrentUser != OuterUser;
 }
 
-void
-SetUserIdAndContext(Oid userid, bool sec_def_context)
+/*
+ * InSecurityRestrictedOperation - are we inside a security-restricted command?
+ */
+bool
+InSecurityRestrictedOperation(void)
 {
-	/* We throw the same error SET ROLE would. */
-	if (InSecurityRestrictedOperation())
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("cannot set parameter \"%s\" within security-restricted operation",
-						"role")));
-	CurrentUserId = userid;
-	if (sec_def_context)
-		SecurityRestrictionContext |= SECURITY_LOCAL_USERID_CHANGE;
-	else
-		SecurityRestrictionContext &= ~SECURITY_LOCAL_USERID_CHANGE;
+	return CurrentUser->security_restriction_depth > 0;
 }
 
 
@@ -573,6 +700,23 @@ has_rolreplication(Oid roleid)
 }
 
 /*
+ * InitUserStack - call once, before any other user ID state function
+ */
+void
+InitUserStack(void)
+{
+	TransientUserStack_max = 8;
+	TransientUserStack =
+		MemoryContextAlloc(TopMemoryContext,
+						   TransientUserStack_max * sizeof(*OuterUser));
+
+	TransientUserStack_cur = 0;
+	OuterUser->userid = InvalidOid;
+	OuterUser->security_restriction_depth = 0;
+	OuterUser->noforce_rls = false;
+}
+
+/*
  * Initialize user identity during normal backend startup
  */
 void
@@ -622,7 +766,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
 	AuthenticatedUserId = roleid;
 	AuthenticatedUserIsSuperuser = rform->rolsuper;
 
-	/* This sets OuterUserId/CurrentUserId too */
+	/* This updates OuterUser/CurrentUser too */
 	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
 
 	/* Also mark our PGPROC entry with the authenticated user id */
@@ -739,7 +883,7 @@ Oid
 GetCurrentRoleId(void)
 {
 	if (SetRoleIsActive)
-		return OuterUserId;
+		return OuterUser->userid;
 	else
 		return InvalidOid;
 }
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b636b1e..1ca0f21 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -703,6 +703,12 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 	 */
 	before_shmem_exit(ShutdownPostgres, 0);
 
+	/*
+	 * Make the user stack physically-available, another prerequisite for
+	 * starting a transaction.  No valid user OIDs on record yet.
+	 */
+	InitUserStack();
+
 	/* The autovacuum launcher is done here */
 	if (IsAutoVacuumLauncherProcess())
 	{
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 5176626..3634359 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -556,6 +556,8 @@ main(int argc, char *argv[])
 				dumpRoleMembership(conn);
 			else
 				dumpGroups(conn);
+
+			/* FIXME pg_auth_trust */
 		}
 
 		/* Dump tablespaces */
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index ee88e1c..d70796c 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -806,6 +806,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 					if (pattern2)
 						free(pattern2);
 				}
+				else if (cmd[2] == 't')
+					success = listRoleTrustSettings(pattern);
 				else
 					status = PSQL_CMD_UNKNOWN;
 				break;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a181b0..195497b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3484,6 +3484,68 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
 	return true;
 }
 
+/*
+ * \drt
+ */
+bool
+listRoleTrustSettings(const char *pattern)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	initPQExpBuffer(&buf);
+
+	if (pset.sversion >= 90300)
+	{
+		printfPQExpBuffer(&buf, "WITH roles AS\n"
+						  "(SELECT oid, rolname FROM pg_roles "
+						  "UNION ALL VALUES (0, 'public'))\n"
+						  "SELECT "
+						  "gr.rolname AS \"%s\", tr.rolname AS \"%s\"\n"
+						  "FROM pg_auth_trust\n"
+						  "LEFT JOIN roles gr ON gr.oid = grantor\n"
+						  "LEFT JOIN roles tr ON tr.oid = trustee\n",
+						  gettext_noop("Granting Role"),
+						  gettext_noop("Trusted Role"));
+		processSQLNamePattern(pset.db, &buf, pattern, false, false,
+							  NULL, "gr.rolname", NULL, NULL);
+		appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
+	}
+	else
+	{
+		fprintf(pset.queryFout,
+				_("No role trust support in this server version.\n"));
+		return false;
+	}
+
+	res = PSQLexec(buf.data);
+	if (!res)
+		return false;
+
+	if (PQntuples(res) == 0 && !pset.quiet)
+	{
+		if (pattern)
+			fprintf(pset.queryFout,
+					_("No matching role trust relationships found.\n"));
+		else
+			fprintf(pset.queryFout,
+					_("No role trust relationships found.\n"));
+	}
+	else
+	{
+		myopt.nullPrint = NULL;
+		myopt.title = _("List of role trust relationships");
+		myopt.translate_header = true;
+
+		printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+	}
+
+	PQclear(res);
+	resetPQExpBuffer(&buf);
+	return true;
+}
+
 
 /*
  * listTables()
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index a4cc5ef..0f58c64 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -33,6 +33,9 @@ extern bool describeRoles(const char *pattern, bool verbose, bool showSystem);
 /* \drds */
 extern bool listDbRoleSettings(const char *pattern1, const char *pattern2);
 
+/* \drt */
+extern bool listRoleTrustSettings(const char *pattern);
+
 /* \z (or \dp) */
 extern bool permissionsList(const char *pattern);
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 586aebd..ab6307e 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -250,6 +250,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dO[S+] [PATTERN]      list collations\n"));
 	fprintf(output, _("  \\dp     [PATTERN]      list table, view, and sequence access privileges\n"));
 	fprintf(output, _("  \\drds [PATRN1 [PATRN2]] list per-database role settings\n"));
+	fprintf(output, _("  \\drt    [PATTERN]      list role trust relationships\n"));
 	fprintf(output, _("  \\dRp[+] [PATTERN]      list replication publications\n"));
 	fprintf(output, _("  \\dRs[+] [PATTERN]      list replication subscriptions\n"));
 	fprintf(output, _("  \\ds[S+] [PATTERN]      list sequences\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9dbd555..3ad96ab 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1333,7 +1333,7 @@ psql_completion(const char *text, int start, int end)
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dm", "\\dn", "\\do", "\\dO", "\\dp",
-		"\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS",
+		"\\drds", "\\drt", "\\dRs", "\\dRp", "\\ds", "\\dS",
 		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
 		"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
 		"\\endif", "\\errverbose", "\\ev",
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 2359b4c..e955378 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -102,6 +102,11 @@ DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, on pg_auth_members
 DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
 #define AuthMemMemRoleIndexId	2695
 
+DECLARE_UNIQUE_INDEX(pg_auth_trust_grantor_trustee_index, 2233, on pg_auth_trust using btree(grantor oid_ops, trustee oid_ops));
+#define AuthTrustGrantorTrusteeIndexId	2233
+DECLARE_UNIQUE_INDEX(pg_auth_trust_trustee_grantor_index, 2234, on pg_auth_trust using btree(trustee oid_ops, grantor oid_ops));
+#define AuthTrustTrusteeGrantorIndexId	2234
+
 DECLARE_UNIQUE_INDEX(pg_cast_oid_index, 2660, on pg_cast using btree(oid oid_ops));
 #define CastOidIndexId	2660
 DECLARE_UNIQUE_INDEX(pg_cast_source_target_index, 2661, on pg_cast using btree(castsource oid_ops, casttarget oid_ops));
diff --git a/src/include/catalog/pg_auth_trust.dat b/src/include/catalog/pg_auth_trust.dat
new file mode 100644
index 0000000..d16f3aa
--- /dev/null
+++ b/src/include/catalog/pg_auth_trust.dat
@@ -0,0 +1,18 @@
+#----------------------------------------------------------------------
+#
+# pg_auth_trust.dat
+#    Initial contents of the pg_auth_trust system catalog.
+#
+# Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/include/catalog/pg_auth_trust.dat
+#
+#----------------------------------------------------------------------
+
+[
+# For backward compatibility, PUBLIC trusts PUBLIC.
+{ grantor => 0, trustee => 0 },
+# Redundant with that, PUBLIC trusts BOOTSTRAP_SUPERUSERID.
+{ grantor => 0, trustee => 10 },
+]
diff --git a/src/include/catalog/pg_auth_trust.h b/src/include/catalog/pg_auth_trust.h
new file mode 100644
index 0000000..0271682a
--- /dev/null
+++ b/src/include/catalog/pg_auth_trust.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_trust.h
+ *	  definition of the "authorization identifier trust" system catalog
+ *	  (pg_auth_trust)
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_auth_trust.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_TRUST_H
+#define PG_AUTH_TRUST_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_auth_trust_d.h"
+
+/* ----------------
+ *		pg_auth_trust definition.  cpp turns this into
+ *		typedef struct FormData_pg_auth_trust
+ * ----------------
+ */
+CATALOG(pg_auth_trust,1364,AuthTrustRelationId) BKI_SHARED_RELATION
+{
+	Oid			grantor;		/* trusting role; InvalidOid is a wildcard */
+	Oid			trustee;		/* trusted role; InvalidOid is a wildcard */
+} FormData_pg_auth_trust;
+
+/* ----------------
+ *		Form_pg_auth_trust corresponds to a pointer to a tuple with
+ *		the format of pg_auth_trust relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_trust *Form_pg_auth_trust;
+
+#endif   /* PG_AUTH_TRUST_H */
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 028e0dd..cb083dc 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -32,6 +32,6 @@ extern void GrantRole(GrantRoleStmt *stmt);
 extern ObjectAddress RenameRole(const char *oldname, const char *newname);
 extern void DropOwnedObjects(DropOwnedStmt *stmt);
 extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
-extern List *roleSpecsToIds(List *memberNames);
+extern List *roleSpecsToIds(List *memberNames, bool public_ok);
 
 #endif							/* USER_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index d6b32c0..2a6b774 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -295,11 +295,6 @@ extern int	trace_recovery(int trace_level);
  *			POSTGRES directory path definitions.                             *
  *****************************************************************************/
 
-/* flags to be OR'd to form sec_context */
-#define SECURITY_LOCAL_USERID_CHANGE	0x0001
-#define SECURITY_RESTRICTED_OPERATION	0x0002
-#define SECURITY_NOFORCE_RLS			0x0004
-
 extern char *DatabasePath;
 
 /* now in utils/init/miscinit.c */
@@ -308,23 +303,32 @@ extern void InitStandaloneProcess(const char *argv0);
 
 extern void SetDatabasePath(const char *path);
 
+/* opaque cookie */
+typedef unsigned TransientUser;
+
 extern char *GetUserNameFromId(Oid roleid, bool noerr);
 extern Oid	GetUserId(void);
 extern Oid	GetOuterUserId(void);
 extern Oid	GetSessionUserId(void);
+extern void PushTransientUser(Oid userid,
+				  bool security_restricted, bool noforce_rls);
+extern void PopTransientUser(void);
+extern TransientUser GetTransientUser(void);
+extern void RestoreTransientUser(TransientUser u);
+extern Size EstimateTransientUserStackSpace(void);
+extern void SerializeTransientUserStack(Size maxsize, char *start_address);
+extern void RestoreTransientUserStack(char *start_address);
 extern Oid	GetAuthenticatedUserId(void);
-extern void GetUserIdAndSecContext(Oid *userid, int *sec_context);
-extern void SetUserIdAndSecContext(Oid userid, int sec_context);
 extern bool InLocalUserIdChange(void);
 extern bool InSecurityRestrictedOperation(void);
 extern bool InNoForceRLSOperation(void);
-extern void GetUserIdAndContext(Oid *userid, bool *sec_def_context);
-extern void SetUserIdAndContext(Oid userid, bool sec_def_context);
+extern void InitUserStack(void);
 extern void InitializeSessionUserId(const char *rolename, Oid useroid);
 extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void GetSecurityApplicableRoles(Oid **roles, int *nroles);
 
 extern void checkDataDir(void);
 extern void SetDataDir(const char *dir);
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index e6cd2cd..1910fd1 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -245,8 +245,9 @@ extern List *list_union_oid(const List *list1, const List *list2);
 
 extern List *list_intersection(const List *list1, const List *list2);
 extern List *list_intersection_int(const List *list1, const List *list2);
+extern List *list_intersection_oid(const List *list1, const List *list2);
 
-/* currently, there's no need for list_intersection_ptr etc */
+/* currently, there's no need for list_intersection_ptr */
 
 extern List *list_difference(const List *list1, const List *list2);
 extern List *list_difference_ptr(const List *list1, const List *list2);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 23db401..c8eca3a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -412,6 +412,7 @@ PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD)
 PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD)
 PG_KEYWORD("true", TRUE_P, RESERVED_KEYWORD)
 PG_KEYWORD("truncate", TRUNCATE, UNRESERVED_KEYWORD)
+PG_KEYWORD("trust", TRUST, UNRESERVED_KEYWORD)
 PG_KEYWORD("trusted", TRUSTED, UNRESERVED_KEYWORD)
 PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 72936ee..a1bae03 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -262,7 +262,12 @@ extern AclResult pg_attribute_aclcheck_all(Oid table_oid, Oid roleid,
 						  AclMode mode, AclMaskHow how);
 extern AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode);
 extern AclResult pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode);
+extern bool pg_oper_trustcheck_extended(Oid oper_oid, bool errorOK);
 extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode);
+extern bool pg_proc_trustcheck_extended(Oid proc_oid, bool errorOK);
+#define pg_proc_trustcheck(proc_oid) \
+	pg_proc_trustcheck_extended((proc_oid), false)
+extern void pg_proc_execcheck(Oid proc_oid);
 extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode);
 extern AclResult pg_largeobject_aclcheck_snapshot(Oid lang_oid, Oid roleid,
 								 AclMode mode, Snapshot snapshot);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 6f290c7..20dae44 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -41,6 +41,7 @@ enum SysCacheIdentifier
 	ATTNUM,
 	AUTHMEMMEMROLE,
 	AUTHMEMROLEMEM,
+	AUTHTRUSTGRANTORTRUSTEE,
 	AUTHNAME,
 	AUTHOID,
 	CASTSOURCETARGET,
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 3b1454f..afe2945 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -619,9 +619,7 @@ call_pltcl_start_proc(Oid prolang, bool pltrusted)
 	procOid = LookupFuncName(namelist, 0, fargtypes, false);
 
 	/* Current user must have permission to call function */
-	aclresult = pg_proc_aclcheck(procOid, GetUserId(), ACL_EXECUTE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, OBJECT_FUNCTION, start_proc);
+	pg_proc_execcheck(procOid);
 
 	/* Get the function's pg_proc entry */
 	procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procOid));
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 83b3196..f7c9b07 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -20,10 +20,10 @@ SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3
 RESET client_min_messages;
 -- test proper begins here
 CREATE USER regress_priv_user1;
-CREATE USER regress_priv_user2;
-CREATE USER regress_priv_user3;
-CREATE USER regress_priv_user4;
-CREATE USER regress_priv_user5;
+CREATE USER regress_priv_user2 TRUST regress_priv_user1;
+CREATE USER regress_priv_user3 TRUST regress_priv_user1;
+CREATE USER regress_priv_user4 TRUST regress_priv_user1;
+CREATE USER regress_priv_user5 TRUST regress_priv_user1;
 CREATE USER regress_priv_user5;	-- duplicate
 ERROR:  role "regress_priv_user5" already exists
 CREATE GROUP regress_priv_group1;
@@ -1909,6 +1909,94 @@ revoke select on dep_priv_test from regress_priv_user4 cascade;
 
 set session role regress_priv_user1;
 drop table dep_priv_test;
+-- Function Trust
+-- Think of regress_priv_user3 as the lead malefactor, regress_priv_user2 as
+-- the accomplice, and regress_priv_user1 as the innocent party.
+RESET ROLE;
+-- Change PUBLIC's function trust in a transaction we never COMMIT.  Other
+-- sessions will not see the change, and a failed "make installcheck" will not
+-- leave a durable change.
+BEGIN;
+-- Suppress WARNING when the installcheck cluster is already so-configured.
+SET client_min_messages TO 'error';
+ALTER USER public NO TRUST public;
+RESET client_min_messages;
+-- Prepare objects.
+CREATE FUNCTION regress_priv_user3_f() RETURNS int SECURITY DEFINER IMMUTABLE
+	LANGUAGE plpgsql AS $$
+BEGIN
+	RAISE NOTICE 'malefactor has control';
+	RETURN 1;
+END
+$$;
+ALTER FUNCTION regress_priv_user3_f() OWNER TO regress_priv_user3;
+CREATE FUNCTION regress_priv_user2_f() RETURNS int SECURITY DEFINER IMMUTABLE
+	LANGUAGE sql AS 'SELECT regress_priv_user3_f()';
+ALTER FUNCTION regress_priv_user2_f() OWNER TO regress_priv_user2;
+CREATE TABLE trojan (c int);
+INSERT INTO trojan SELECT 1 FROM generate_series(1, 20);
+INSERT INTO trojan SELECT 2 FROM generate_series(1, 60);
+ALTER TABLE trojan OWNER TO regress_priv_user1;
+-- One SECURITY DEFINER level.
+SET SESSION AUTHORIZATION regress_priv_user2;
+ALTER USER regress_priv_user2 TRUST regress_priv_user3;
+SAVEPOINT q;
+ALTER USER regress_priv_user1 TRUST regress_priv_user3;	-- fails: no admin option
+ERROR:  must have admin option on role "regress_priv_user1"
+ROLLBACK TO q;
+SELECT regress_priv_user2_f();			-- works
+NOTICE:  malefactor has control
+ regress_priv_user2_f 
+----------------------
+                    1
+(1 row)
+
+-- Two SECURITY DEFINER levels; bottom level lacks trust.
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER USER regress_priv_user1 TRUST regress_priv_user2;
+SAVEPOINT q;
+SELECT regress_priv_user2_f();			-- fails
+ERROR:  user regress_priv_user1 does not trust function regress_priv_user3_f
+CONTEXT:  SQL function "regress_priv_user2_f" statement 1
+ROLLBACK TO q;
+ALTER USER regress_priv_user1 TRUST regress_priv_user3;
+SELECT regress_priv_user2_f();			-- now works
+NOTICE:  malefactor has control
+ regress_priv_user2_f 
+----------------------
+                    1
+(1 row)
+
+-- Two SECURITY DEFINER levels; top level lacks trust.
+SAVEPOINT q;
+SET SESSION AUTHORIZATION regress_priv_user2;
+ALTER USER regress_priv_user2 NO TRUST regress_priv_user3;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT regress_priv_user2_f();			-- now fails again
+ERROR:  user regress_priv_user2 does not trust function regress_priv_user3_f
+CONTEXT:  SQL function "regress_priv_user2_f" statement 1
+ROLLBACK TO q;						-- revert TRUST change
+-- Laxness allowed by use of a security-restricted context.
+RESET SESSION AUTHORIZATION;
+ALTER ROLE regress_priv_user4 SUPERUSER;
+SET ROLE regress_priv_user4;
+CREATE INDEX ON trojan (c) WHERE regress_priv_user2_f() > 0;
+NOTICE:  malefactor has control
+SAVEPOINT q;
+INSERT INTO trojan VALUES (1);		-- fails
+ERROR:  user regress_priv_user4 does not trust function regress_priv_user2_f
+ROLLBACK TO q;
+ANALYZE trojan;
+NOTICE:  malefactor has control
+ALTER USER regress_priv_user1 NO TRUST regress_priv_user3;
+SAVEPOINT q;
+ANALYZE trojan;						-- fails: owner ceased trusting function
+ERROR:  user regress_priv_user1 does not trust function regress_priv_user3_f
+CONTEXT:  SQL function "regress_priv_user2_f" statement 1
+ROLLBACK TO q;
+RESET ROLE;
+ALTER ROLE regress_priv_user4 NOSUPERUSER;
+ROLLBACK;
 -- clean up
 \c
 drop sequence x_seq;
diff --git a/src/test/regress/expected/rolenames.out b/src/test/regress/expected/rolenames.out
index 68dacb7..7d6dba7 100644
--- a/src/test/regress/expected/rolenames.out
+++ b/src/test/regress/expected/rolenames.out
@@ -200,9 +200,9 @@ LINE 1: ALTER ROLE ALL WITH REPLICATION;
 ALTER ROLE SESSION_ROLE WITH NOREPLICATION; -- error
 ERROR:  role "session_role" does not exist
 ALTER ROLE PUBLIC WITH NOREPLICATION; -- error
-ERROR:  role "public" does not exist
+ERROR:  only [NO] TRUST allowed for PUBLIC
 ALTER ROLE "public" WITH NOREPLICATION; -- error
-ERROR:  role "public" does not exist
+ERROR:  only [NO] TRUST allowed for PUBLIC
 ALTER ROLE NONE WITH NOREPLICATION; -- error
 ERROR:  role name "none" is reserved
 LINE 1: ALTER ROLE NONE WITH NOREPLICATION;
@@ -316,9 +316,9 @@ LINE 1: ALTER USER ALL WITH REPLICATION;
 ALTER USER SESSION_ROLE WITH NOREPLICATION; -- error
 ERROR:  role "session_role" does not exist
 ALTER USER PUBLIC WITH NOREPLICATION; -- error
-ERROR:  role "public" does not exist
+ERROR:  only [NO] TRUST allowed for PUBLIC
 ALTER USER "public" WITH NOREPLICATION; -- error
-ERROR:  role "public" does not exist
+ERROR:  only [NO] TRUST allowed for PUBLIC
 ALTER USER NONE WITH NOREPLICATION; -- error
 ERROR:  role name "none" is reserved
 LINE 1: ALTER USER NONE WITH NOREPLICATION;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index c77060d..c77f017 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -108,6 +108,7 @@ pg_amproc|t
 pg_attrdef|t
 pg_attribute|t
 pg_auth_members|t
+pg_auth_trust|t
 pg_authid|t
 pg_cast|t
 pg_class|t
diff --git a/src/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
index bf003ad..9c359e7 100644
--- a/src/test/regress/expected/select_views.out
+++ b/src/test/regress/expected/select_views.out
@@ -1250,7 +1250,9 @@ SELECT * FROM toyemp WHERE name = 'sharon';
 --
 -- Test for Leaky view scenario
 --
-CREATE ROLE regress_alice;
+DO $$BEGIN
+	EXECUTE 'CREATE ROLE regress_alice TRUST ' || quote_ident(current_user);
+END$$;
 CREATE FUNCTION f_leak (text)
        RETURNS bool LANGUAGE 'plpgsql' COST 0.0000001
        AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index ac2c3df..b9fcc87 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -24,10 +24,10 @@ RESET client_min_messages;
 -- test proper begins here
 
 CREATE USER regress_priv_user1;
-CREATE USER regress_priv_user2;
-CREATE USER regress_priv_user3;
-CREATE USER regress_priv_user4;
-CREATE USER regress_priv_user5;
+CREATE USER regress_priv_user2 TRUST regress_priv_user1;
+CREATE USER regress_priv_user3 TRUST regress_priv_user1;
+CREATE USER regress_priv_user4 TRUST regress_priv_user1;
+CREATE USER regress_priv_user5 TRUST regress_priv_user1;
 CREATE USER regress_priv_user5;	-- duplicate
 
 CREATE GROUP regress_priv_group1;
@@ -1130,6 +1130,84 @@ set session role regress_priv_user1;
 drop table dep_priv_test;
 
 
+-- Function Trust
+
+-- Think of regress_priv_user3 as the lead malefactor, regress_priv_user2 as
+-- the accomplice, and regress_priv_user1 as the innocent party.
+
+RESET ROLE;
+
+-- Change PUBLIC's function trust in a transaction we never COMMIT.  Other
+-- sessions will not see the change, and a failed "make installcheck" will not
+-- leave a durable change.
+BEGIN;
+
+-- Suppress WARNING when the installcheck cluster is already so-configured.
+SET client_min_messages TO 'error';
+ALTER USER public NO TRUST public;
+RESET client_min_messages;
+
+-- Prepare objects.
+CREATE FUNCTION regress_priv_user3_f() RETURNS int SECURITY DEFINER IMMUTABLE
+	LANGUAGE plpgsql AS $$
+BEGIN
+	RAISE NOTICE 'malefactor has control';
+	RETURN 1;
+END
+$$;
+ALTER FUNCTION regress_priv_user3_f() OWNER TO regress_priv_user3;
+CREATE FUNCTION regress_priv_user2_f() RETURNS int SECURITY DEFINER IMMUTABLE
+	LANGUAGE sql AS 'SELECT regress_priv_user3_f()';
+ALTER FUNCTION regress_priv_user2_f() OWNER TO regress_priv_user2;
+CREATE TABLE trojan (c int);
+INSERT INTO trojan SELECT 1 FROM generate_series(1, 20);
+INSERT INTO trojan SELECT 2 FROM generate_series(1, 60);
+ALTER TABLE trojan OWNER TO regress_priv_user1;
+
+-- One SECURITY DEFINER level.
+SET SESSION AUTHORIZATION regress_priv_user2;
+ALTER USER regress_priv_user2 TRUST regress_priv_user3;
+SAVEPOINT q;
+ALTER USER regress_priv_user1 TRUST regress_priv_user3;	-- fails: no admin option
+ROLLBACK TO q;
+SELECT regress_priv_user2_f();			-- works
+
+-- Two SECURITY DEFINER levels; bottom level lacks trust.
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER USER regress_priv_user1 TRUST regress_priv_user2;
+SAVEPOINT q;
+SELECT regress_priv_user2_f();			-- fails
+ROLLBACK TO q;
+ALTER USER regress_priv_user1 TRUST regress_priv_user3;
+SELECT regress_priv_user2_f();			-- now works
+
+-- Two SECURITY DEFINER levels; top level lacks trust.
+SAVEPOINT q;
+SET SESSION AUTHORIZATION regress_priv_user2;
+ALTER USER regress_priv_user2 NO TRUST regress_priv_user3;
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT regress_priv_user2_f();			-- now fails again
+ROLLBACK TO q;						-- revert TRUST change
+
+-- Laxness allowed by use of a security-restricted context.
+RESET SESSION AUTHORIZATION;
+ALTER ROLE regress_priv_user4 SUPERUSER;
+SET ROLE regress_priv_user4;
+CREATE INDEX ON trojan (c) WHERE regress_priv_user2_f() > 0;
+SAVEPOINT q;
+INSERT INTO trojan VALUES (1);		-- fails
+ROLLBACK TO q;
+ANALYZE trojan;
+ALTER USER regress_priv_user1 NO TRUST regress_priv_user3;
+SAVEPOINT q;
+ANALYZE trojan;						-- fails: owner ceased trusting function
+ROLLBACK TO q;
+RESET ROLE;
+ALTER ROLE regress_priv_user4 NOSUPERUSER;
+
+ROLLBACK;
+
+
 -- clean up
 
 \c
diff --git a/src/test/regress/sql/select_views.sql b/src/test/regress/sql/select_views.sql
index e742f13..1caa50a 100644
--- a/src/test/regress/sql/select_views.sql
+++ b/src/test/regress/sql/select_views.sql
@@ -12,7 +12,9 @@ SELECT * FROM toyemp WHERE name = 'sharon';
 --
 -- Test for Leaky view scenario
 --
-CREATE ROLE regress_alice;
+DO $$BEGIN
+	EXECUTE 'CREATE ROLE regress_alice TRUST ' || quote_ident(current_user);
+END$$;
 
 CREATE FUNCTION f_leak (text)
        RETURNS bool LANGUAGE 'plpgsql' COST 0.0000001