Reserve the "pg_" namespace for roles

This will prevent users from creating roles which begin with "pg_" and
will check for those roles before allowing an upgrade using pg_upgrade.

This will allow for default roles to be provided at initdb time.

Reviews by José Luis Tallón and Robert Haas
This commit is contained in:
Stephen Frost 2016-04-08 16:56:27 -04:00
parent fa6075e551
commit 293007898d
21 changed files with 226 additions and 13 deletions

View File

@ -1365,13 +1365,15 @@ testdb=>
<varlistentry>
<term><literal>\dg[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<term><literal>\dg[S+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
<para>
Lists database roles.
(Since the concepts of <quote>users</> and <quote>groups</> have been
unified into <quote>roles</>, this command is now equivalent to
<literal>\du</literal>.)
By default, only user-created roles are shown; supply the
<literal>S</literal> modifier to include system roles.
If <replaceable class="parameter">pattern</replaceable> is specified,
only those roles whose names match the pattern are listed.
If the form <literal>\dg+</literal> is used, additional information
@ -1525,13 +1527,15 @@ testdb=&gt;
</varlistentry>
<varlistentry>
<term><literal>\du[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<term><literal>\du[S+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
<para>
Lists database roles.
(Since the concepts of <quote>users</> and <quote>groups</> have been
unified into <quote>roles</>, this command is now equivalent to
<literal>\dg</literal>.)
By default, only user-created roles are shown; supply the
<literal>S</literal> modifier to include system roles.
If <replaceable class="parameter">pattern</replaceable> is specified,
only those roles whose names match the pattern are listed.
If the form <literal>\du+</literal> is used, additional information

View File

@ -423,6 +423,9 @@ ExecuteGrantStmt(GrantStmt *stmt)
grantee_uid = ACL_ID_PUBLIC;
break;
default:
if (!IsBootstrapProcessingMode())
check_rolespec_name((Node *) grantee,
"Cannot GRANT or REVOKE privileges to or from a reserved role.");
grantee_uid = get_rolespec_oid((Node *) grantee, false);
break;
}
@ -918,6 +921,8 @@ ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt)
grantee_uid = ACL_ID_PUBLIC;
break;
default:
check_rolespec_name((Node *) grantee,
"Cannot GRANT or REVOKE default privileges to or from a reserved role.");
grantee_uid = get_rolespec_oid((Node *) grantee, false);
break;
}
@ -1008,6 +1013,8 @@ ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt)
{
RoleSpec *rolespec = lfirst(rolecell);
check_rolespec_name((Node *) rolespec,
"Cannot alter default privileges for reserved role.");
iacls.roleid = get_rolespec_oid((Node *) rolespec, false);
/*

View File

@ -184,8 +184,9 @@ IsToastNamespace(Oid namespaceId)
* True iff name starts with the pg_ prefix.
*
* For some classes of objects, the prefix pg_ is reserved for
* system objects only. As of 8.0, this is only true for
* schema and tablespace names.
* system objects only. As of 8.0, this was only true for
* schema and tablespace names. With 9.6, this is also true
* for roles.
*/
bool
IsReservedName(const char *name)

View File

@ -747,6 +747,9 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
{
Oid newowner = get_rolespec_oid(stmt->newowner, false);
check_rolespec_name(stmt->newowner,
"Cannot make reserved roles owners of objects.");
switch (stmt->objectType)
{
case OBJECT_DATABASE:

View File

@ -1148,6 +1148,10 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
else
useId = get_rolespec_oid(stmt->user, false);
/* Additional check to protect reserved role names */
check_rolespec_name(stmt->user,
"Cannot specify reserved role as mapping user.");
/* Check that the server exists. */
srv = GetForeignServerByName(stmt->servername, false);
@ -1248,6 +1252,10 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
else
useId = get_rolespec_oid(stmt->user, false);
/* Additional check to protect reserved role names */
check_rolespec_name(stmt->user,
"Cannot alter reserved role mapping user.");
srv = GetForeignServerByName(stmt->servername, false);
umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
@ -1337,6 +1345,11 @@ RemoveUserMapping(DropUserMappingStmt *stmt)
else
{
useId = get_rolespec_oid(stmt->user, stmt->missing_ok);
/* Additional check to protect reserved role names */
check_rolespec_name(stmt->user,
"Cannot remove reserved role mapping user.");
if (!OidIsValid(useId))
{
/*

View File

@ -176,8 +176,13 @@ policy_role_list_to_array(List *roles, int *num_roles)
return role_oids;
}
else
{
/* Additional check to protect reserved role names */
check_rolespec_name((Node *) spec,
"Cannot specify reserved role as policy target");
role_oids[i++] =
ObjectIdGetDatum(get_rolespec_oid((Node *) spec, false));
}
}
return role_oids;

View File

@ -65,6 +65,10 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
else
owner_uid = saved_uid;
/* Additional check to protect reserved role names */
check_rolespec_name(stmt->authrole,
"Cannot specify reserved role as owner.");
/* fill schema name with the user name if not specified */
if (!schemaName)
{

View File

@ -3566,6 +3566,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
(List *) cmd->def, lockmode);
break;
case AT_ChangeOwner: /* ALTER OWNER */
check_rolespec_name(cmd->newowner,
"Cannot specify reserved role as owner.");
ATExecChangeOwner(RelationGetRelid(rel),
get_rolespec_oid(cmd->newowner, false),
false, lockmode);

View File

@ -256,6 +256,10 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
else
ownerId = GetUserId();
/* Additional check to protect reserved role names */
check_rolespec_name(stmt->owner,
"Cannot specify reserved role as owner.");
/* Unix-ify the offered path, and strip any trailing slashes */
location = pstrdup(stmt->location);
canonicalize_path(location);

View File

@ -17,6 +17,7 @@
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
@ -311,6 +312,17 @@ CreateRole(CreateRoleStmt *stmt)
errmsg("permission denied to create role")));
}
/*
* Check that the user is not trying to create a role in the reserved
* "pg_" namespace.
*/
if (IsReservedName(stmt->role))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
stmt->role),
errdetail("Role names starting with \"pg_\" are reserved.")));
/*
* Check the pg_authid relation to be certain the role doesn't already
* exist.
@ -507,6 +519,9 @@ AlterRole(AlterRoleStmt *stmt)
DefElem *dbypassRLS = NULL;
Oid roleid;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
@ -857,6 +872,9 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
if (stmt->role)
{
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
roletuple = get_rolespec_tuple(stmt->role);
roleid = HeapTupleGetOid(roletuple);
@ -1117,6 +1135,7 @@ RenameRole(const char *oldname, const char *newname)
int i;
Oid roleid;
ObjectAddress address;
Form_pg_authid authform;
rel = heap_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
@ -1136,6 +1155,7 @@ RenameRole(const char *oldname, const char *newname)
*/
roleid = HeapTupleGetOid(oldtuple);
authform = (Form_pg_authid) GETSTRUCT(oldtuple);
if (roleid == GetSessionUserId())
ereport(ERROR,
@ -1146,6 +1166,24 @@ RenameRole(const char *oldname, const char *newname)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("current user cannot be renamed")));
/*
* Check that the user is not trying to rename a system role and
* not trying to rename a role into the reserved "pg_" namespace.
*/
if (IsReservedName(NameStr(authform->rolname)))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
NameStr(authform->rolname)),
errdetail("Role names starting with \"pg_\" are reserved.")));
if (IsReservedName(newname))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
newname),
errdetail("Role names starting with \"pg_\" are reserved.")));
/* make sure the new name doesn't exist */
if (SearchSysCacheExists1(AUTHNAME, CStringGetDatum(newname)))
ereport(ERROR,
@ -1224,10 +1262,18 @@ GrantRole(GrantRoleStmt *stmt)
ListCell *item;
if (stmt->grantor)
{
check_rolespec_name(stmt->grantor,
"Cannot specify reserved role as grantor.");
grantor = get_rolespec_oid(stmt->grantor, false);
}
else
grantor = GetUserId();
foreach(item, stmt->grantee_roles)
check_rolespec_name(lfirst(item),
"Cannot GRANT roles to a reserved role.");
grantee_ids = roleSpecsToIds(stmt->grantee_roles);
/* AccessShareLock is enough since we aren't modifying pg_authid */
@ -1318,6 +1364,9 @@ ReassignOwnedObjects(ReassignOwnedStmt *stmt)
errmsg("permission denied to reassign objects")));
}
check_rolespec_name(stmt->newrole,
"Cannot specify reserved role as owner.");
/* Must have privileges on the receiving side too */
newrole = get_rolespec_oid(stmt->newrole, false);

View File

@ -854,6 +854,9 @@ check_role(char **newval, void **extra, GucSource source)
roleid = InvalidOid;
is_superuser = false;
}
/* Do not allow setting role to a reserved role. */
else if (strncmp(*newval, "pg_", 3) == 0)
return false;
else
{
if (!IsTransactionState())

View File

@ -17,6 +17,7 @@
#include <ctype.h>
#include "access/htup_details.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
@ -5247,3 +5248,41 @@ get_rolespec_name(const Node *node)
return rolename;
}
/*
* Given a RoleSpec, throw an error if the name is reserved, using detail_msg,
* if provided.
*
* If node is NULL, no error is thrown. If detail_msg is NULL then no detail
* message is provided.
*/
void
check_rolespec_name(const Node *node, const char *detail_msg)
{
RoleSpec *role;
if (!node)
return;
role = (RoleSpec *) node;
Assert(IsA(node, RoleSpec));
if (role->roletype != ROLESPEC_CSTRING)
return;
if (IsReservedName(role->rolename))
{
if (detail_msg)
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role \"%s\" is reserved",
role->rolename),
errdetail("%s", detail_msg)));
else
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role \"%s\" is reserved",
role->rolename)));
}
}

View File

@ -665,7 +665,7 @@ dumpRoles(PGconn *conn)
int i;
/* note: rolconfig is dumped later */
if (server_version >= 90500)
if (server_version >= 90600)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
@ -674,6 +674,7 @@ dumpRoles(PGconn *conn)
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
"WHERE rolname !~ '^pg_' "
"ORDER BY 2");
else if (server_version >= 90100)
printfPQExpBuffer(buf,
@ -771,6 +772,13 @@ dumpRoles(PGconn *conn)
auth_oid = atooid(PQgetvalue(res, i, i_oid));
rolename = PQgetvalue(res, i, i_rolname);
if (strncmp(rolename,"pg_",3) == 0)
{
fprintf(stderr, _("%s: role name starting with 'pg_' skipped (%s)\n"),
progname, rolename);
continue;
}
resetPQExpBuffer(buf);
if (binary_upgrade)
@ -896,6 +904,7 @@ dumpRoleMembership(PGconn *conn)
"LEFT JOIN pg_authid ur on ur.oid = a.roleid "
"LEFT JOIN pg_authid um on um.oid = a.member "
"LEFT JOIN pg_authid ug on ug.oid = a.grantor "
"WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')"
"ORDER BY 1,2,3");
if (PQntuples(res) > 0)

View File

@ -24,6 +24,7 @@ static void check_for_prepared_transactions(ClusterInfo *cluster);
static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster);
static void check_for_reg_data_type_usage(ClusterInfo *cluster);
static void check_for_jsonb_9_4_usage(ClusterInfo *cluster);
static void check_for_pg_role_prefix(ClusterInfo *cluster);
static void get_bin_version(ClusterInfo *cluster);
static char *get_canonical_locale_name(int category, const char *locale);
@ -96,6 +97,11 @@ check_and_dump_old_cluster(bool live_check)
check_for_prepared_transactions(&old_cluster);
check_for_reg_data_type_usage(&old_cluster);
check_for_isn_and_int8_passing_mismatch(&old_cluster);
/* 9.5 and below should not have roles starting with pg_ */
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 905)
check_for_pg_role_prefix(&old_cluster);
if (GET_MAJOR_VERSION(old_cluster.major_version) == 904 &&
old_cluster.controldata.cat_ver < JSONB_FORMAT_CHANGE_CAT_VER)
check_for_jsonb_9_4_usage(&old_cluster);
@ -629,7 +635,8 @@ check_is_install_user(ClusterInfo *cluster)
res = executeQueryOrDie(conn,
"SELECT rolsuper, oid "
"FROM pg_catalog.pg_roles "
"WHERE rolname = current_user");
"WHERE rolname = current_user "
"AND rolname !~ '^pg_'");
/*
* We only allow the install user in the new cluster (see comment below)
@ -645,7 +652,8 @@ check_is_install_user(ClusterInfo *cluster)
res = executeQueryOrDie(conn,
"SELECT COUNT(*) "
"FROM pg_catalog.pg_roles ");
"FROM pg_catalog.pg_roles "
"WHERE rolname !~ '^pg_'");
if (PQntuples(res) != 1)
pg_fatal("could not determine the number of users\n");
@ -1033,6 +1041,34 @@ check_for_jsonb_9_4_usage(ClusterInfo *cluster)
check_ok();
}
/*
* check_for_pg_role_prefix()
*
* Versions older than 9.6 should not have any pg_* roles
*/
static void
check_for_pg_role_prefix(ClusterInfo *cluster)
{
PGresult *res;
PGconn *conn = connectToServer(cluster, "template1");
prep_status("Checking for roles starting with 'pg_'");
res = executeQueryOrDie(conn,
"SELECT * "
"FROM pg_catalog.pg_roles "
"WHERE rolname ~ '^pg_'");
if (PQntuples(res) != 0)
pg_fatal("The %s cluster contains roles starting with 'pg_'\n",
CLUSTER_NAME(cluster));
PQclear(res);
PQfinish(conn);
check_ok();
}
static void
get_bin_version(ClusterInfo *cluster)

View File

@ -429,7 +429,7 @@ exec_command(const char *cmd,
break;
case 'g':
/* no longer distinct from \du */
success = describeRoles(pattern, show_verbose);
success = describeRoles(pattern, show_verbose, show_system);
break;
case 'l':
success = do_lo_list();
@ -474,7 +474,7 @@ exec_command(const char *cmd,
success = PSQL_CMD_UNKNOWN;
break;
case 'u':
success = describeRoles(pattern, show_verbose);
success = describeRoles(pattern, show_verbose, show_system);
break;
case 'F': /* text search subsystem */
switch (cmd[2])

View File

@ -2646,7 +2646,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
* Describes roles. Any schema portion of the pattern is ignored.
*/
bool
describeRoles(const char *pattern, bool verbose)
describeRoles(const char *pattern, bool verbose, bool showSystem)
{
PQExpBufferData buf;
PGresult *res;
@ -2691,6 +2691,9 @@ describeRoles(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
processSQLNamePattern(pset.db, &buf, pattern, false, false,
NULL, "r.rolname", NULL, NULL);
}

View File

@ -25,7 +25,7 @@ extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
extern bool describeOperators(const char *pattern, bool verbose, bool showSystem);
/* \du, \dg */
extern bool describeRoles(const char *pattern, bool verbose);
extern bool describeRoles(const char *pattern, bool verbose, bool showSystem);
/* \drds */
extern bool listDbRoleSettings(const char *pattern1, const char *pattern2);

View File

@ -229,7 +229,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\dFd[+] [PATTERN] list text search dictionaries\n"));
fprintf(output, _(" \\dFp[+] [PATTERN] list text search parsers\n"));
fprintf(output, _(" \\dFt[+] [PATTERN] list text search templates\n"));
fprintf(output, _(" \\dg[+] [PATTERN] list roles\n"));
fprintf(output, _(" \\dg[S+] [PATTERN] list roles\n"));
fprintf(output, _(" \\di[S+] [PATTERN] list indexes\n"));
fprintf(output, _(" \\dl list large objects, same as \\lo_list\n"));
fprintf(output, _(" \\dL[S+] [PATTERN] list procedural languages\n"));
@ -242,7 +242,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\ds[S+] [PATTERN] list sequences\n"));
fprintf(output, _(" \\dt[S+] [PATTERN] list tables\n"));
fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n"));
fprintf(output, _(" \\du[+] [PATTERN] list roles\n"));
fprintf(output, _(" \\du[S+] [PATTERN] list roles\n"));
fprintf(output, _(" \\dv[S+] [PATTERN] list views\n"));
fprintf(output, _(" \\dE[S+] [PATTERN] list foreign tables\n"));
fprintf(output, _(" \\dx[+] [PATTERN] list extensions\n"));

View File

@ -231,6 +231,7 @@ extern void check_is_member_of_role(Oid member, Oid role);
extern Oid get_role_oid(const char *rolename, bool missing_ok);
extern Oid get_role_oid_or_public(const char *rolename);
extern Oid get_rolespec_oid(const Node *node, bool missing_ok);
extern void check_rolespec_name(const Node *node, const char *detail_msg);
extern HeapTuple get_rolespec_tuple(const Node *node);
extern char *get_rolespec_name(const Node *node);

View File

@ -78,6 +78,18 @@ CREATE ROLE "none"; -- error
ERROR: role name "none" is reserved
LINE 1: CREATE ROLE "none";
^
CREATE ROLE pg_abc; -- error
ERROR: role name "pg_abc" is reserved
DETAIL: Role names starting with "pg_" are reserved.
CREATE ROLE "pg_abc"; -- error
ERROR: role name "pg_abc" is reserved
DETAIL: Role names starting with "pg_" are reserved.
CREATE ROLE pg_abcdef; -- error
ERROR: role name "pg_abcdef" is reserved
DETAIL: Role names starting with "pg_" are reserved.
CREATE ROLE "pg_abcdef"; -- error
ERROR: role name "pg_abcdef" is reserved
DETAIL: Role names starting with "pg_" are reserved.
CREATE ROLE testrol0 SUPERUSER LOGIN;
CREATE ROLE testrolx SUPERUSER LOGIN;
CREATE ROLE testrol2 SUPERUSER;
@ -804,6 +816,14 @@ LINE 1: DROP USER MAPPING IF EXISTS FOR CURRENT_ROLE SERVER sv9;
DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv9; -- error
NOTICE: role "nonexistent" does not exist, skipping
-- GRANT/REVOKE
GRANT testrol0 TO pg_abc; -- error
ERROR: role "pg_abc" is reserved
DETAIL: Cannot GRANT roles to a reserved role.
GRANT pg_abc TO pg_abcdef; -- error
ERROR: role "pg_abcdef" is reserved
DETAIL: Cannot GRANT roles to a reserved role.
SET ROLE pg_testrole; -- error
ERROR: invalid value for parameter "role": "pg_testrole"
UPDATE pg_proc SET proacl = null WHERE proname LIKE 'testagg_';
SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';
proname | proacl

View File

@ -57,6 +57,11 @@ CREATE ROLE "public"; -- error
CREATE ROLE none; -- error
CREATE ROLE "none"; -- error
CREATE ROLE pg_abc; -- error
CREATE ROLE "pg_abc"; -- error
CREATE ROLE pg_abcdef; -- error
CREATE ROLE "pg_abcdef"; -- error
CREATE ROLE testrol0 SUPERUSER LOGIN;
CREATE ROLE testrolx SUPERUSER LOGIN;
CREATE ROLE testrol2 SUPERUSER;
@ -376,6 +381,11 @@ DROP USER MAPPING IF EXISTS FOR CURRENT_ROLE SERVER sv9; --error
DROP USER MAPPING IF EXISTS FOR nonexistent SERVER sv9; -- error
-- GRANT/REVOKE
GRANT testrol0 TO pg_abc; -- error
GRANT pg_abc TO pg_abcdef; -- error
SET ROLE pg_testrole; -- error
UPDATE pg_proc SET proacl = null WHERE proname LIKE 'testagg_';
SELECT proname, proacl FROM pg_proc WHERE proname LIKE 'testagg_';