From b0e55c4db4ec642a4a7ddc6dfc15591744d1d96e Mon Sep 17 00:00:00 2001
From: Andreas Lind <andreaslindpetersen@gmail.com>
Date: Wed, 19 Jun 2024 14:51:30 +0200
Subject: [PATCH v1 1/4] Add a BYPASSLEAKPROOF role attribute

---
 doc/src/sgml/catalogs.sgml           | 10 ++++
 doc/src/sgml/ref/alter_role.sgml     |  9 ++--
 doc/src/sgml/ref/createuser.sgml     | 20 ++++++++
 doc/src/sgml/system-views.sgml       | 31 +++++++++++++
 src/backend/catalog/aclchk.c         | 19 ++++++++
 src/backend/catalog/system_views.sql |  3 ++
 src/backend/commands/user.c          | 40 +++++++++++++++-
 src/backend/parser/gram.y            |  4 ++
 src/bin/pg_dump/pg_dumpall.c         | 23 ++++++++++
 src/bin/psql/describe.c              | 17 +++++++
 src/bin/scripts/createuser.c         | 18 +++++++-
 src/bin/scripts/t/040_createuser.pl  | 32 ++++++++-----
 src/include/catalog/pg_authid.dat    | 68 ++++++++++++++--------------
 src/include/catalog/pg_authid.h      |  1 +
 src/include/utils/acl.h              |  1 +
 15 files changed, 245 insertions(+), 51 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index cbd4e40a320..162430ee85d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1567,6 +1567,16 @@
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rolbypassleakproof</structfield> <type>bool</type>
+      </para>
+      <para>
+       Role bypasses LEAKPROOF requirement for functions evaluated in
+       security sensitive contexts.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rolconnlimit</structfield> <type>int4</type>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 7b0a04bc463..259e3927e33 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -32,6 +32,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
     | LOGIN | NOLOGIN
     | REPLICATION | NOREPLICATION
     | BYPASSRLS | NOBYPASSRLS
+    | BYPASSLEAKPROOF | NOBYPASSLEAKPROOF
     | CONNECTION LIMIT <replaceable class="parameter">connlimit</replaceable>
     | [ ENCRYPTED ] PASSWORD '<replaceable class="parameter">password</replaceable>' | PASSWORD NULL
     | VALID UNTIL '<replaceable class="parameter">timestamp</replaceable>'
@@ -77,9 +78,9 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
    non-replication roles for which they have been granted
    <literal>ADMIN OPTION</literal>. Non-superusers cannot change the
    <literal>SUPERUSER</literal> property and can change the
-   <literal>CREATEDB</literal>, <literal>REPLICATION</literal>, and
-   <literal>BYPASSRLS</literal> properties only if they possess the
-   corresponding property themselves.
+   <literal>CREATEDB</literal>, <literal>REPLICATION</literal>,
+   <literal>BYPASSRLS</literal>, and <literal>BYPASSLEAKPROOF</literal> properties
+   only if they possess the corresponding property themselves.
    Ordinary roles can only change their own password.
   </para>
 
@@ -178,6 +179,8 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
       <term><literal>NOREPLICATION</literal></term>
       <term><literal>BYPASSRLS</literal></term>
       <term><literal>NOBYPASSRLS</literal></term>
+      <term><literal>BYPASSLEAKPROOF</literal></term>
+      <term><literal>NOBYPASSLEAKPROOF</literal></term>
       <term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
       <term>[ <literal>ENCRYPTED</literal> ] <literal>PASSWORD</literal> '<replaceable class="parameter">password</replaceable>'</term>
       <term><literal>PASSWORD NULL</literal></term>
diff --git a/doc/src/sgml/ref/createuser.sgml b/doc/src/sgml/ref/createuser.sgml
index 5c34c623423..af26f1daf72 100644
--- a/doc/src/sgml/ref/createuser.sgml
+++ b/doc/src/sgml/ref/createuser.sgml
@@ -330,6 +330,26 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--bypassleakproof</option></term>
+      <listitem>
+       <para>
+        The new user will bypass the LEAKPROOF requirement for functions evaluated
+        in security sensitive contexts.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--no-bypassleakproof</option></term>
+      <listitem>
+       <para>
+        The new user will not bypass the LEAKPROOF requirement for functions evaluated
+        in security sensitive contexts.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--replication</option></term>
       <listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index b58c52ea50f..b67c657c0c7 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -3105,6 +3105,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
       </para></entry>
      </row>
 
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rolbypassleakproof</structfield> <type>bool</type>
+      </para>
+      <para>
+       Role bypasses the LEAKPROOF requirement for functions evaluated in
+       security sensitive contexts.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rolconfig</structfield> <type>text[]</type>
@@ -3927,6 +3938,16 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>usebypassleakproof</structfield> <type>bool</type>
+      </para>
+      <para>
+       Role bypasses the LEAKPROOF requirement for functions evaluated in
+       security sensitive contexts.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>passwd</structfield> <type>text</type>
@@ -5194,6 +5215,16 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>usebypassleakproof</structfield> <type>bool</type>
+      </para>
+      <para>
+       Role bypasses the LEAKPROOF requirement for functions evaluated in
+       security sensitive contexts.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>passwd</structfield> <type>text</type>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 9ca8a88dc91..f0d5ff17b18 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -4188,6 +4188,25 @@ has_bypassrls_privilege(Oid roleid)
 	return result;
 }
 
+bool
+has_bypassleakproof_privilege(Oid roleid)
+{
+	bool		result = false;
+	HeapTuple	utup;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+	if (HeapTupleIsValid(utup))
+	{
+		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassleakproof;
+		ReleaseSysCache(utup);
+	}
+	return result;
+}
+
 /*
  * Fetch pg_default_acl entry for given role, namespace and object type
  * (object type must be given in pg_default_acl's encoding).
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 15efb02badb..8b981595907 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -27,6 +27,7 @@ CREATE VIEW pg_roles AS
         '********'::text as rolpassword,
         rolvaliduntil,
         rolbypassrls,
+        rolbypassleakproof,
         setconfig as rolconfig,
         pg_authid.oid
     FROM pg_authid LEFT JOIN pg_db_role_setting s
@@ -40,6 +41,7 @@ CREATE VIEW pg_shadow AS
         rolsuper AS usesuper,
         rolreplication AS userepl,
         rolbypassrls AS usebypassrls,
+        rolbypassleakproof AS usebypassleakproof,
         rolpassword AS passwd,
         rolvaliduntil AS valuntil,
         setconfig AS useconfig
@@ -65,6 +67,7 @@ CREATE VIEW pg_user AS
         usesuper,
         userepl,
         usebypassrls,
+        usebypassleakproof,
         '********'::text as passwd,
         valuntil,
         useconfig
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 0d638e29d00..efffec6fc0f 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -149,6 +149,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	bool		canlogin = false;	/* Can this user login? */
 	bool		isreplication = false;	/* Is this a replication role? */
 	bool		bypassrls = false;	/* Is this a row security enabled role? */
+	bool		bypassleakproof = false;	/* Does it bypass leakproof
+											 * checks? */
 	int			connlimit = -1; /* maximum connections allowed */
 	List	   *addroleto = NIL;	/* roles to make this a member of */
 	List	   *rolemembers = NIL;	/* roles to be members of this role */
@@ -169,6 +171,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	DefElem    *dadminmembers = NULL;
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
+	DefElem    *dbypassLeakproof = NULL;
 	GrantRoleOptions popt;
 
 	/* The defaults can vary depending on the original statement type */
@@ -272,6 +275,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			dbypassRLS = defel;
 		}
+		else if (strcmp(defel->defname, "bypassleakproof") == 0)
+		{
+			if (dbypassLeakproof)
+				errorConflictingDefElem(defel, pstate);
+			dbypassLeakproof = defel;
+		}
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
@@ -309,6 +318,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 		validUntil = strVal(dvalidUntil->arg);
 	if (dbypassRLS)
 		bypassrls = boolVal(dbypassRLS->arg);
+	if (dbypassLeakproof)
+		bypassleakproof = boolVal(dbypassLeakproof->arg);
 
 	/* Check some permissions first */
 	if (!superuser_arg(currentUserId))
@@ -343,6 +354,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 					 errmsg("permission denied to create role"),
 					 errdetail("Only roles with the %s attribute may create roles with the %s attribute.",
 							   "BYPASSRLS", "BYPASSRLS")));
+
+		if (bypassleakproof && !has_bypassleakproof_privilege(currentUserId))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied to create role"),
+					 errdetail("Only roles with the %s attribute may create roles with the %s attribute.",
+							   "BYPASSLEAKPROOF", "BYPASSLEAKPROOF")));
 	}
 
 	/*
@@ -456,6 +474,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
 	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
+	new_record[Anum_pg_authid_rolbypassleakproof - 1] = BoolGetDatum(bypassleakproof);
 
 	/*
 	 * pg_largeobject_metadata contains pg_authid.oid's, so we use the
@@ -644,6 +663,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 	DefElem    *drolemembers = NULL;
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
+	DefElem    *dbypassLeakproof = NULL;
 	Oid			roleid;
 	Oid			currentUserId = GetUserId();
 	GrantRoleOptions popt;
@@ -723,6 +743,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			dbypassRLS = defel;
 		}
+		else if (strcmp(defel->defname, "bypassleakproof") == 0)
+		{
+			if (dbypassLeakproof)
+				errorConflictingDefElem(defel, pstate);
+			dbypassLeakproof = defel;
+		}
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
@@ -775,7 +801,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 	{
 		/* things an unprivileged user certainly can't do */
 		if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
-			dvalidUntil || disreplication || dbypassRLS)
+			dvalidUntil || disreplication || dbypassRLS || dbypassLeakproof)
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("permission denied to alter role"),
@@ -815,6 +841,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 					 errmsg("permission denied to alter role"),
 					 errdetail("Only roles with the %s attribute may change the %s attribute.",
 							   "BYPASSRLS", "BYPASSRLS")));
+		if (dbypassLeakproof && !has_bypassleakproof_privilege(currentUserId))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied to alter role"),
+					 errdetail("Only roles with the %s attribute may change the %s attribute.",
+							   "BYPASSLEAKPROOF", "BYPASSLEAKPROOF")));
 	}
 
 	/* To add or drop members, you need ADMIN OPTION. */
@@ -952,6 +984,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(boolVal(dbypassRLS->arg));
 		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
 	}
+	if (dbypassLeakproof)
+	{
+		new_record[Anum_pg_authid_rolbypassleakproof - 1] = BoolGetDatum(boolVal(dbypassLeakproof->arg));
+		new_record_repl[Anum_pg_authid_rolbypassleakproof - 1] = true;
+	}
+
 
 	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
 								  new_record_nulls, new_record_repl);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3c4268b271a..94412edd1f6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1272,6 +1272,10 @@ AlterOptRoleElem:
 						$$ = makeDefElem("bypassrls", (Node *) makeBoolean(true), @1);
 					else if (strcmp($1, "nobypassrls") == 0)
 						$$ = makeDefElem("bypassrls", (Node *) makeBoolean(false), @1);
+					else if (strcmp($1, "bypassleakproof") == 0)
+						$$ = makeDefElem("bypassleakproof", (Node *) makeBoolean(true), @1);
+					else if (strcmp($1, "nobypassleakproof") == 0)
+						$$ = makeDefElem("bypassleakproof", (Node *) makeBoolean(false), @1);
 					else if (strcmp($1, "noinherit") == 0)
 					{
 						/*
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 946a6d0fafc..d84f47a881a 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -837,6 +837,7 @@ dumpRoles(PGconn *conn)
 				i_rolvaliduntil,
 				i_rolreplication,
 				i_rolbypassrls,
+				i_rolbypassleakproof,
 				i_rolcomment,
 				i_is_current_user;
 	int			i;
@@ -845,6 +846,22 @@ dumpRoles(PGconn *conn)
 	 * Notes: rolconfig is dumped later, and pg_authid must be used for
 	 * extracting rolcomment regardless of role_catalog.
 	 */
+	if (server_version >= 99999)
+
+		/*
+		 * FIXME: When it has been assigned, set the right server_version
+		 * where rolbypassleakproof is available
+		 */
+		printfPQExpBuffer(buf,
+						  "SELECT oid, rolname, rolsuper, rolinherit, "
+						  "rolcreaterole, rolcreatedb, "
+						  "rolcanlogin, rolconnlimit, rolpassword, "
+						  "rolvaliduntil, rolreplication, rolbypassrls, rolbypassleakproof, "
+						  "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+						  "rolname = current_user AS is_current_user "
+						  "FROM %s "
+						  "WHERE rolname !~ '^pg_' "
+						  "ORDER BY 2", role_catalog);
 	if (server_version >= 90600)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
@@ -892,6 +909,7 @@ dumpRoles(PGconn *conn)
 	i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
 	i_rolreplication = PQfnumber(res, "rolreplication");
 	i_rolbypassrls = PQfnumber(res, "rolbypassrls");
+	i_rolbypassleakproof = PQfnumber(res, "rolbypassleakproof");
 	i_rolcomment = PQfnumber(res, "rolcomment");
 	i_is_current_user = PQfnumber(res, "is_current_user");
 
@@ -971,6 +989,11 @@ dumpRoles(PGconn *conn)
 		else
 			appendPQExpBufferStr(buf, " NOBYPASSRLS");
 
+		if (strcmp(PQgetvalue(res, i, i_rolbypassleakproof), "t") == 0)
+			appendPQExpBufferStr(buf, " BYPASSLEAKPROOF");
+		else
+			appendPQExpBufferStr(buf, " NOBYPASSLEAKPROOF");
+
 		if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0)
 			appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
 							  PQgetvalue(res, i, i_rolconnlimit));
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1d08268393e..fc5a00f3e79 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3742,6 +3742,15 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
 		appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
 	}
 
+	if (pset.sversion >= 99999)
+	{
+		/*
+		 * FIXME: When it has been assigned, set the right server_version
+		 * where rolbypassleakproof is available
+		 */
+		appendPQExpBufferStr(&buf, "\n, r.rolbypassleakproof");
+	}
+
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
 
 	if (!showSystem && !pattern)
@@ -3799,6 +3808,14 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
 			if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
 				add_role_attribute(&buf, _("Bypass RLS"));
 
+		/*
+		 * FIXME: When it has been assigned, set the right server_version
+		 * where rolbypassleakproof is available
+		 */
+		if (pset.sversion >= 99999)
+			if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
+				add_role_attribute(&buf, _("Bypass LEAKPROOF"));
+
 		conns = atoi(PQgetvalue(res, i, 6));
 		if (conns >= 0)
 		{
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index 81e6abfc46e..4c1246aa181 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -57,6 +57,8 @@ main(int argc, char *argv[])
 		{"interactive", no_argument, NULL, 3},
 		{"bypassrls", no_argument, NULL, 4},
 		{"no-bypassrls", no_argument, NULL, 5},
+		{"bypassleakproof", no_argument, NULL, 6},
+		{"no-bypassleakproof", no_argument, NULL, 7},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -86,7 +88,8 @@ main(int argc, char *argv[])
 				inherit = TRI_DEFAULT,
 				login = TRI_DEFAULT,
 				replication = TRI_DEFAULT,
-				bypassrls = TRI_DEFAULT;
+				bypassrls = TRI_DEFAULT,
+				bypassleakproof = TRI_DEFAULT;
 
 	PQExpBufferData sql;
 
@@ -190,6 +193,12 @@ main(int argc, char *argv[])
 			case 5:
 				bypassrls = TRI_NO;
 				break;
+			case 6:
+				bypassleakproof = TRI_YES;
+				break;
+			case 7:
+				bypassleakproof = TRI_NO;
+				break;
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -341,6 +350,10 @@ main(int argc, char *argv[])
 		appendPQExpBufferStr(&sql, " BYPASSRLS");
 	if (bypassrls == TRI_NO)
 		appendPQExpBufferStr(&sql, " NOBYPASSRLS");
+	if (bypassleakproof == TRI_YES)
+		appendPQExpBufferStr(&sql, " BYPASSLEAKPROOF");
+	if (bypassleakproof == TRI_NO)
+		appendPQExpBufferStr(&sql, " NOBYPASSLEAKPROOF");
 	if (conn_limit >= -1)
 		appendPQExpBuffer(&sql, " CONNECTION LIMIT %d", conn_limit);
 	if (pwexpiry != NULL)
@@ -444,6 +457,9 @@ help(const char *progname)
 	printf(_("  --bypassrls               role can bypass row-level security (RLS) policy\n"));
 	printf(_("  --no-bypassrls            role cannot bypass row-level security (RLS) policy\n"
 			 "                            (default)\n"));
+	printf(_("  --bypassleakproof         role can bypass leakproof security requirements\n"));
+	printf(_("  --no-bypassleakproof      role cannot bypass leakproof security requirements\n"
+			 "                            (default)\n"));
 	printf(_("  --replication             role can initiate replication\n"));
 	printf(_("  --no-replication          role cannot initiate replication (default)\n"));
 	printf(_("  -?, --help                show this help, then exit\n"));
diff --git a/src/bin/scripts/t/040_createuser.pl b/src/bin/scripts/t/040_createuser.pl
index 54af43401bb..495a19c099a 100644
--- a/src/bin/scripts/t/040_createuser.pl
+++ b/src/bin/scripts/t/040_createuser.pl
@@ -18,19 +18,19 @@ $node->start;
 
 $node->issues_sql_like(
 	[ 'createuser', 'regress_user1' ],
-	qr/statement: CREATE ROLE regress_user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS;/,
+	qr/statement: CREATE ROLE regress_user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF;/,
 	'SQL CREATE USER run');
 $node->issues_sql_like(
 	[ 'createuser', '--no-login', 'regress_role1' ],
-	qr/statement: CREATE ROLE regress_role1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN NOREPLICATION NOBYPASSRLS;/,
+	qr/statement: CREATE ROLE regress_role1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF;/,
 	'create a non-login role');
 $node->issues_sql_like(
 	[ 'createuser', '--createrole', 'regress user2' ],
-	qr/statement: CREATE ROLE "regress user2" NOSUPERUSER NOCREATEDB CREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS;/,
+	qr/statement: CREATE ROLE "regress user2" NOSUPERUSER NOCREATEDB CREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF;/,
 	'create a CREATEROLE user');
 $node->issues_sql_like(
 	[ 'createuser', '--superuser', 'regress_user3' ],
-	qr/statement: CREATE ROLE regress_user3 SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS;/,
+	qr/statement: CREATE ROLE regress_user3 SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF;/,
 	'create a superuser');
 $node->issues_sql_like(
 	[
@@ -39,7 +39,7 @@ $node->issues_sql_like(
 		'--with-admin' => 'regress user2',
 		'regress user #4'
 	],
-	qr/statement: CREATE ROLE "regress user #4" NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS ADMIN regress_user1,"regress user2";/,
+	qr/statement: CREATE ROLE "regress user #4" NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF ADMIN regress_user1,"regress user2";/,
 	'add a role as a member with admin option of the newly created role');
 $node->issues_sql_like(
 	[
@@ -48,11 +48,11 @@ $node->issues_sql_like(
 		'--with-member' => 'regress_user3',
 		'--with-member' => 'regress user #4'
 	],
-	qr/statement: CREATE ROLE "REGRESS_USER5" NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS ROLE regress_user3,"regress user #4";/,
+	qr/statement: CREATE ROLE "REGRESS_USER5" NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF ROLE regress_user3,"regress user #4";/,
 	'add a role as a member of the newly created role');
 $node->issues_sql_like(
 	[ 'createuser', '--valid-until' => '2029 12 31', 'regress_user6' ],
-	qr/statement: CREATE ROLE regress_user6 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS VALID UNTIL \'2029 12 31\';/,
+	qr/statement: CREATE ROLE regress_user6 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF VALID UNTIL \'2029 12 31\';/,
 	'create a role with a password expiration date');
 $node->issues_sql_like(
 	[ 'createuser', '--bypassrls', 'regress_user7' ],
@@ -60,24 +60,32 @@ $node->issues_sql_like(
 	'create a BYPASSRLS role');
 $node->issues_sql_like(
 	[ 'createuser', '--no-bypassrls', 'regress_user8' ],
-	qr/statement: CREATE ROLE regress_user8 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS;/,
+	qr/statement: CREATE ROLE regress_user8 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF;/,
 	'create a role without BYPASSRLS');
 $node->issues_sql_like(
 	[ 'createuser', '--with-admin' => 'regress_user1', 'regress_user9' ],
-	qr/statement: CREATE ROLE regress_user9 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS ADMIN regress_user1;/,
+	qr/statement: CREATE ROLE regress_user9 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF ADMIN regress_user1;/,
 	'--with-admin');
 $node->issues_sql_like(
 	[ 'createuser', '--with-member' => 'regress_user1', 'regress_user10' ],
-	qr/statement: CREATE ROLE regress_user10 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS ROLE regress_user1;/,
+	qr/statement: CREATE ROLE regress_user10 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF ROLE regress_user1;/,
 	'--with-member');
 $node->issues_sql_like(
 	[ 'createuser', '--role' => 'regress_user1', 'regress_user11' ],
-	qr/statement: CREATE ROLE regress_user11 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS IN ROLE regress_user1;/,
+	qr/statement: CREATE ROLE regress_user11 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF IN ROLE regress_user1;/,
 	'--role');
 $node->issues_sql_like(
 	[ 'createuser', 'regress_user12', '--member-of' => 'regress_user1' ],
-	qr/statement: CREATE ROLE regress_user12 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS IN ROLE regress_user1;/,
+	qr/statement: CREATE ROLE regress_user12 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF IN ROLE regress_user1;/,
 	'--member-of');
+$node->issues_sql_like(
+	[ 'createuser', '--bypassleakproof', 'regress_user13' ],
+	qr/statement: CREATE ROLE regress_user7 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS BYPASSLEAKPROOF;/,
+	'create a BYPASSLEAKPROOF role');
+$node->issues_sql_like(
+	[ 'createuser', '--no-bypassleakproof', 'regress_user14' ],
+	qr/statement: CREATE ROLE regress_user8 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN NOREPLICATION NOBYPASSRLS NOBYPASSLEAKPROOF;/,
+	'create a role without BYPASSLEAKPROOF');
 
 $node->command_fails([ 'createuser', 'regress_user1' ],
 	'fails if role already exists');
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index eb4dab5c6aa..7461e6e625a 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -22,87 +22,87 @@
 { oid => '10', oid_symbol => 'BOOTSTRAP_SUPERUSERID',
   rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
   rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
-  rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 't', rolbypassrls => 't', rolbypassleakproof => 't',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6171', oid_symbol => 'ROLE_PG_DATABASE_OWNER',
   rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6181', oid_symbol => 'ROLE_PG_READ_ALL_DATA',
   rolname => 'pg_read_all_data', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6182', oid_symbol => 'ROLE_PG_WRITE_ALL_DATA',
   rolname => 'pg_write_all_data', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3373', oid_symbol => 'ROLE_PG_MONITOR',
   rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3374', oid_symbol => 'ROLE_PG_READ_ALL_SETTINGS',
   rolname => 'pg_read_all_settings', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3375', oid_symbol => 'ROLE_PG_READ_ALL_STATS',
   rolname => 'pg_read_all_stats', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3377', oid_symbol => 'ROLE_PG_STAT_SCAN_TABLES',
   rolname => 'pg_stat_scan_tables', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4569', oid_symbol => 'ROLE_PG_READ_SERVER_FILES',
   rolname => 'pg_read_server_files', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4570', oid_symbol => 'ROLE_PG_WRITE_SERVER_FILES',
   rolname => 'pg_write_server_files', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4571', oid_symbol => 'ROLE_PG_EXECUTE_SERVER_PROGRAM',
   rolname => 'pg_execute_server_program', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4200', oid_symbol => 'ROLE_PG_SIGNAL_BACKEND',
   rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4544', oid_symbol => 'ROLE_PG_CHECKPOINT',
   rolname => 'pg_checkpoint', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6337', oid_symbol => 'ROLE_PG_MAINTAIN',
   rolname => 'pg_maintain', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
   rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6304', oid_symbol => 'ROLE_PG_CREATE_SUBSCRIPTION',
   rolname => 'pg_create_subscription', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '8916', oid_symbol => 'ROLE_PG_SIGNAL_AUTOVACUUM_WORKER',
   rolname => 'pg_signal_autovacuum_worker', rolsuper => 'f', rolinherit => 't',
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
-  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
-  rolpassword => '_null_', rolvaliduntil => '_null_' },
+  rolreplication => 'f', rolbypassrls => 'f', rolbypassleakproof => 'f',
+  rolconnlimit => '-1', rolpassword => '_null_', rolvaliduntil => '_null_' },
 
 ]
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index b2f3e9d01ee..e565ef88745 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -39,6 +39,7 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
 	bool		rolcanlogin;	/* allowed to log in as session user? */
 	bool		rolreplication; /* role used for streaming replication */
 	bool		rolbypassrls;	/* bypasses row-level security? */
+	bool		rolbypassleakproof; /* bypasses leakproof checks? */
 	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
 
 	/* remaining fields may be null; use heap_getattr to read them! */
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 01ae5b719fd..68f5a4a76e6 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -286,5 +286,6 @@ extern void RemoveRoleFromInitPriv(Oid roleid,
 extern bool object_ownercheck(Oid classid, Oid objectid, Oid roleid);
 extern bool has_createrole_privilege(Oid roleid);
 extern bool has_bypassrls_privilege(Oid roleid);
+extern bool has_bypassleakproof_privilege(Oid roleid);
 
 #endif							/* ACL_H */
-- 
2.39.2

