Skip to content

Commit 79de984

Browse files
committed
Remove the ability of a role to administer itself.
Commit f9fd176 effectively gave every role ADMIN OPTION on itself. However, this appears to be something that happened accidentally as a result of refactoring work rather than an intentional decision. Almost a decade later, it was discovered that this was a security vulnerability. As a result, commit fea164a restricted this implicit ADMIN OPTION privilege to be exercisable only when the role being administered is the same as the session user and when no security-restricted operation is in progress. That commit also documented the existence of this implicit privilege for what seems to be the first time. The effect of the privilege is to allow a login role to grant the privileges of that role, and optionally ADMIN OPTION on it, to some other role. That's an unusual thing to do, because generally membership is granted in roles used as groups, rather than roles used as users. Therefore, it does not seem likely that removing the privilege will break things for many PostgreSQL users. However, it will make it easier to reason about the permissions system. This is the only case where a user who has not been given any special permission (superuser, or ADMIN OPTION on some role) can modify role membership, so removing it makes things more consistent. For example, if a superuser sets up role A and B and grants A to B but no other privileges to anyone, she can now be sure that no one else will be able to revoke that grant. Without this change, that would have been true only if A was a non-login role. Patch by me. Reviewed by Tom Lane and Stephen Frost. Discussion: http://postgr.es/m/CA+Tgmoawdt03kbA+dNyBcNWJpRxu0f4X=69Y3+DkXXZqmwMDLg@mail.gmail.com
1 parent 6176242 commit 79de984

File tree

5 files changed

+8
-58
lines changed

5 files changed

+8
-58
lines changed

doc/src/sgml/ref/grant.sgml

+4-5
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,10 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
251251
in turn grant membership in the role to others, and revoke membership
252252
in the role as well. Without the admin option, ordinary users cannot
253253
do that. A role is not considered to hold <literal>WITH ADMIN
254-
OPTION</literal> on itself, but it may grant or revoke membership in
255-
itself from a database session where the session user matches the
256-
role. Database superusers can grant or revoke membership in any role
257-
to anyone. Roles having <literal>CREATEROLE</literal> privilege can grant
258-
or revoke membership in any role that is not a superuser.
254+
OPTION</literal> on itself. Database superusers can grant or revoke
255+
membership in any role to anyone. Roles having
256+
<literal>CREATEROLE</literal> privilege can grant or revoke membership
257+
in any role that is not a superuser.
259258
</para>
260259

261260
<para>

src/backend/commands/user.c

-5
Original file line numberDiff line numberDiff line change
@@ -1425,11 +1425,6 @@ AddRoleMems(const char *rolename, Oid roleid,
14251425
* The role membership grantor of record has little significance at
14261426
* present. Nonetheless, inasmuch as users might look to it for a crude
14271427
* audit trail, let only superusers impute the grant to a third party.
1428-
*
1429-
* Before lifting this restriction, give the member == role case of
1430-
* is_admin_of_role() a fresh look. Ensure that the current role cannot
1431-
* use an explicit grantor specification to take advantage of the session
1432-
* user's self-admin right.
14331428
*/
14341429
if (grantorId != GetUserId() && !superuser())
14351430
ereport(ERROR,

src/backend/utils/adt/acl.c

+2-36
Original file line numberDiff line numberDiff line change
@@ -4619,11 +4619,6 @@ pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
46194619
{
46204620
if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE))
46214621
{
4622-
/*
4623-
* XXX For roleid == role_oid, is_admin_of_role() also examines the
4624-
* session and call stack. That suits two-argument pg_has_role(), but
4625-
* it gives the three-argument version a lamentable whimsy.
4626-
*/
46274622
if (is_admin_of_role(roleid, role_oid))
46284623
return ACLCHECK_OK;
46294624
}
@@ -4935,38 +4930,9 @@ is_admin_of_role(Oid member, Oid role)
49354930
if (superuser_arg(member))
49364931
return true;
49374932

4933+
/* By policy, a role cannot have WITH ADMIN OPTION on itself. */
49384934
if (member == role)
4939-
4940-
/*
4941-
* A role can admin itself when it matches the session user and we're
4942-
* outside any security-restricted operation, SECURITY DEFINER or
4943-
* similar context. SQL-standard roles cannot self-admin. However,
4944-
* SQL-standard users are distinct from roles, and they are not
4945-
* grantable like roles: PostgreSQL's role-user duality extends the
4946-
* standard. Checking for a session user match has the effect of
4947-
* letting a role self-admin only when it's conspicuously behaving
4948-
* like a user. Note that allowing self-admin under a mere SET ROLE
4949-
* would make WITH ADMIN OPTION largely irrelevant; any member could
4950-
* SET ROLE to issue the otherwise-forbidden command.
4951-
*
4952-
* Withholding self-admin in a security-restricted operation prevents
4953-
* object owners from harnessing the session user identity during
4954-
* administrative maintenance. Suppose Alice owns a database, has
4955-
* issued "GRANT alice TO bob", and runs a daily ANALYZE. Bob creates
4956-
* an alice-owned SECURITY DEFINER function that issues "REVOKE alice
4957-
* FROM carol". If he creates an expression index calling that
4958-
* function, Alice will attempt the REVOKE during each ANALYZE.
4959-
* Checking InSecurityRestrictedOperation() thwarts that attack.
4960-
*
4961-
* Withholding self-admin in SECURITY DEFINER functions makes their
4962-
* behavior independent of the calling user. There's no security or
4963-
* SQL-standard-conformance need for that restriction, though.
4964-
*
4965-
* A role cannot have actual WITH ADMIN OPTION on itself, because that
4966-
* would imply a membership loop. Therefore, we're done either way.
4967-
*/
4968-
return member == GetSessionUserId() &&
4969-
!InLocalUserIdChange() && !InSecurityRestrictedOperation();
4935+
return false;
49704936

49714937
(void) roles_is_member_of(member, ROLERECURSE_MEMBERS, role, &result);
49724938
return result;

src/test/regress/expected/privileges.out

+1-7
Original file line numberDiff line numberDiff line change
@@ -1653,14 +1653,8 @@ SET ROLE regress_priv_group2;
16531653
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
16541654
ERROR: must have admin option on role "regress_priv_group2"
16551655
SET SESSION AUTHORIZATION regress_priv_group2;
1656-
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: a role can self-admin
1657-
NOTICE: role "regress_priv_user5" is already a member of role "regress_priv_group2"
1658-
CREATE FUNCTION dogrant_fails() RETURNS void LANGUAGE sql SECURITY DEFINER AS
1659-
'GRANT regress_priv_group2 TO regress_priv_user5';
1660-
SELECT dogrant_fails(); -- fails: no self-admin in SECURITY DEFINER
1656+
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
16611657
ERROR: must have admin option on role "regress_priv_group2"
1662-
CONTEXT: SQL function "dogrant_fails" statement 1
1663-
DROP FUNCTION dogrant_fails();
16641658
SET SESSION AUTHORIZATION regress_priv_user4;
16651659
DROP FUNCTION dogrant_ok();
16661660
REVOKE regress_priv_group2 FROM regress_priv_user5;

src/test/regress/sql/privileges.sql

+1-5
Original file line numberDiff line numberDiff line change
@@ -1089,11 +1089,7 @@ SET ROLE regress_priv_group2;
10891089
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help
10901090

10911091
SET SESSION AUTHORIZATION regress_priv_group2;
1092-
GRANT regress_priv_group2 TO regress_priv_user5; -- ok: a role can self-admin
1093-
CREATE FUNCTION dogrant_fails() RETURNS void LANGUAGE sql SECURITY DEFINER AS
1094-
'GRANT regress_priv_group2 TO regress_priv_user5';
1095-
SELECT dogrant_fails(); -- fails: no self-admin in SECURITY DEFINER
1096-
DROP FUNCTION dogrant_fails();
1092+
GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin
10971093

10981094
SET SESSION AUTHORIZATION regress_priv_user4;
10991095
DROP FUNCTION dogrant_ok();

0 commit comments

Comments
 (0)