Skip to content

Commit ff35425

Browse files
committed
Shore up ADMIN OPTION restrictions.
Granting a role without ADMIN OPTION is supposed to prevent the grantee from adding or removing members from the granted role. Issuing SET ROLE before the GRANT bypassed that, because the role itself had an implicit right to add or remove members. Plug that hole by recognizing that implicit right only when the session user matches the current role. Additionally, do not recognize it during a security-restricted operation or during execution of a SECURITY DEFINER function. The restriction on SECURITY DEFINER is not security-critical. However, it seems best for a user testing his own SECURITY DEFINER function to see the same behavior others will see. Back-patch to 8.4 (all supported versions). The SQL standards do not conflate roles and users as PostgreSQL does; only SQL roles have members, and only SQL users initiate sessions. An application using PostgreSQL users and roles as SQL users and roles will never attempt to grant membership in the role that is the session user, so the implicit right to add or remove members will never arise. The security impact was mostly that a role member could revoke access from others, contrary to the wishes of his own grantor. Unapproved role member additions are less notable, because the member can still largely achieve that by creating a view or a SECURITY DEFINER function. Reviewed by Andres Freund and Tom Lane. Reported, independently, by Jonas Sundman and Noah Misch. Security: CVE-2014-0060
1 parent bde3307 commit ff35425

File tree

5 files changed

+120
-18
lines changed

5 files changed

+120
-18
lines changed

doc/src/sgml/ref/grant.sgml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,13 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
361361
<para>
362362
If <literal>WITH ADMIN OPTION</literal> is specified, the member can
363363
in turn grant membership in the role to others, and revoke membership
364-
in the role as well. Without the admin option, ordinary users cannot do
365-
that. However,
366-
database superusers can grant or revoke membership in any role to anyone.
367-
Roles having <literal>CREATEROLE</> privilege can grant or revoke
368-
membership in any role that is not a superuser.
364+
in the role as well. Without the admin option, ordinary users cannot
365+
do that. A role is not considered to hold <literal>WITH ADMIN
366+
OPTION</literal> on itself, but it may grant or revoke membership in
367+
itself from a database session where the session user matches the
368+
role. Database superusers can grant or revoke membership in any role
369+
to anyone. Roles having <literal>CREATEROLE</> privilege can grant
370+
or revoke membership in any role that is not a superuser.
369371
</para>
370372

371373
<para>

src/backend/commands/user.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,16 @@ AddRoleMems(const char *rolename, Oid roleid,
13211321
rolename)));
13221322
}
13231323

1324-
/* XXX not sure about this check */
1324+
/*
1325+
* The role membership grantor of record has little significance at
1326+
* present. Nonetheless, inasmuch as users might look to it for a crude
1327+
* audit trail, let only superusers impute the grant to a third party.
1328+
*
1329+
* Before lifting this restriction, give the member == role case of
1330+
* is_admin_of_role() a fresh look. Ensure that the current role cannot
1331+
* use an explicit grantor specification to take advantage of the session
1332+
* user's self-admin right.
1333+
*/
13251334
if (grantorId != GetUserId() && !superuser())
13261335
ereport(ERROR,
13271336
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),

src/backend/utils/adt/acl.c

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3902,6 +3902,11 @@ pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
39023902
{
39033903
if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE))
39043904
{
3905+
/*
3906+
* XXX For roleid == role_oid, is_admin_of_role() also examines the
3907+
* session and call stack. That suits two-argument pg_has_role(), but
3908+
* it gives the three-argument version a lamentable whimsy.
3909+
*/
39053910
if (is_admin_of_role(roleid, role_oid))
39063911
return ACLCHECK_OK;
39073912
}
@@ -4223,11 +4228,9 @@ is_member_of_role_nosuper(Oid member, Oid role)
42234228

42244229

42254230
/*
4226-
* Is member an admin of role (directly or indirectly)? That is, is it
4227-
* a member WITH ADMIN OPTION?
4228-
*
4229-
* We could cache the result as for is_member_of_role, but currently this
4230-
* is not used in any performance-critical paths, so we don't.
4231+
* Is member an admin of role? That is, is member the role itself (subject to
4232+
* restrictions below), a member (directly or indirectly) WITH ADMIN OPTION,
4233+
* or a superuser?
42314234
*/
42324235
bool
42334236
is_admin_of_role(Oid member, Oid role)
@@ -4236,14 +4239,41 @@ is_admin_of_role(Oid member, Oid role)
42364239
List *roles_list;
42374240
ListCell *l;
42384241

4239-
/* Fast path for simple case */
4240-
if (member == role)
4241-
return true;
4242-
4243-
/* Superusers have every privilege, so are part of every role */
42444242
if (superuser_arg(member))
42454243
return true;
42464244

4245+
if (member == role)
4246+
/*
4247+
* A role can admin itself when it matches the session user and we're
4248+
* outside any security-restricted operation, SECURITY DEFINER or
4249+
* similar context. SQL-standard roles cannot self-admin. However,
4250+
* SQL-standard users are distinct from roles, and they are not
4251+
* grantable like roles: PostgreSQL's role-user duality extends the
4252+
* standard. Checking for a session user match has the effect of
4253+
* letting a role self-admin only when it's conspicuously behaving
4254+
* like a user. Note that allowing self-admin under a mere SET ROLE
4255+
* would make WITH ADMIN OPTION largely irrelevant; any member could
4256+
* SET ROLE to issue the otherwise-forbidden command.
4257+
*
4258+
* Withholding self-admin in a security-restricted operation prevents
4259+
* object owners from harnessing the session user identity during
4260+
* administrative maintenance. Suppose Alice owns a database, has
4261+
* issued "GRANT alice TO bob", and runs a daily ANALYZE. Bob creates
4262+
* an alice-owned SECURITY DEFINER function that issues "REVOKE alice
4263+
* FROM carol". If he creates an expression index calling that
4264+
* function, Alice will attempt the REVOKE during each ANALYZE.
4265+
* Checking InSecurityRestrictedOperation() thwarts that attack.
4266+
*
4267+
* Withholding self-admin in SECURITY DEFINER functions makes their
4268+
* behavior independent of the calling user. There's no security or
4269+
* SQL-standard-conformance need for that restriction, though.
4270+
*
4271+
* A role cannot have actual WITH ADMIN OPTION on itself, because that
4272+
* would imply a membership loop. Therefore, we're done either way.
4273+
*/
4274+
return member == GetSessionUserId() &&
4275+
!InLocalUserIdChange() && !InSecurityRestrictedOperation();
4276+
42474277
/*
42484278
* Find all the roles that member is a member of, including multi-level
42494279
* recursion. We build a list in the same way that is_member_of_role does

src/test/regress/expected/privileges.out

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ ALTER GROUP regressgroup1 ADD USER regressuser4;
2626
ALTER GROUP regressgroup2 ADD USER regressuser2; -- duplicate
2727
NOTICE: role "regressuser2" is already a member of role "regressgroup2"
2828
ALTER GROUP regressgroup2 DROP USER regressuser2;
29-
ALTER GROUP regressgroup2 ADD USER regressuser4;
29+
GRANT regressgroup2 TO regressuser4 WITH ADMIN OPTION;
3030
-- test owner privileges
3131
SET SESSION AUTHORIZATION regressuser1;
3232
SELECT session_user, current_user;
@@ -815,6 +815,40 @@ SELECT has_table_privilege('regressuser1', 'atest4', 'SELECT WITH GRANT OPTION')
815815
t
816816
(1 row)
817817

818+
-- Admin options
819+
SET SESSION AUTHORIZATION regressuser4;
820+
CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
821+
'GRANT regressgroup2 TO regressuser5';
822+
GRANT regressgroup2 TO regressuser5; -- ok: had ADMIN OPTION
823+
SET ROLE regressgroup2;
824+
GRANT regressgroup2 TO regressuser5; -- fails: SET ROLE suspended privilege
825+
ERROR: must have admin option on role "regressgroup2"
826+
SET SESSION AUTHORIZATION regressuser1;
827+
GRANT regressgroup2 TO regressuser5; -- fails: no ADMIN OPTION
828+
ERROR: must have admin option on role "regressgroup2"
829+
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
830+
NOTICE: role "regressuser5" is already a member of role "regressgroup2"
831+
CONTEXT: SQL function "dogrant_ok" statement 1
832+
dogrant_ok
833+
------------
834+
835+
(1 row)
836+
837+
SET ROLE regressgroup2;
838+
GRANT regressgroup2 TO regressuser5; -- fails: SET ROLE did not help
839+
ERROR: must have admin option on role "regressgroup2"
840+
SET SESSION AUTHORIZATION regressgroup2;
841+
GRANT regressgroup2 TO regressuser5; -- ok: a role can self-admin
842+
NOTICE: role "regressuser5" is already a member of role "regressgroup2"
843+
CREATE FUNCTION dogrant_fails() RETURNS void LANGUAGE sql SECURITY DEFINER AS
844+
'GRANT regressgroup2 TO regressuser5';
845+
SELECT dogrant_fails(); -- fails: no self-admin in SECURITY DEFINER
846+
ERROR: must have admin option on role "regressgroup2"
847+
CONTEXT: SQL function "dogrant_fails" statement 1
848+
DROP FUNCTION dogrant_fails();
849+
SET SESSION AUTHORIZATION regressuser4;
850+
DROP FUNCTION dogrant_ok();
851+
REVOKE regressgroup2 FROM regressuser5;
818852
-- test that dependent privileges are revoked (or not) properly
819853
\c -
820854
set session role regressuser1;

src/test/regress/sql/privileges.sql

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ALTER GROUP regressgroup1 ADD USER regressuser4;
3434

3535
ALTER GROUP regressgroup2 ADD USER regressuser2; -- duplicate
3636
ALTER GROUP regressgroup2 DROP USER regressuser2;
37-
ALTER GROUP regressgroup2 ADD USER regressuser4;
37+
GRANT regressgroup2 TO regressuser4 WITH ADMIN OPTION;
3838

3939

4040
-- test owner privileges
@@ -469,6 +469,33 @@ SELECT has_table_privilege('regressuser3', 'atest4', 'SELECT'); -- false
469469
SELECT has_table_privilege('regressuser1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true
470470

471471

472+
-- Admin options
473+
474+
SET SESSION AUTHORIZATION regressuser4;
475+
CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS
476+
'GRANT regressgroup2 TO regressuser5';
477+
GRANT regressgroup2 TO regressuser5; -- ok: had ADMIN OPTION
478+
SET ROLE regressgroup2;
479+
GRANT regressgroup2 TO regressuser5; -- fails: SET ROLE suspended privilege
480+
481+
SET SESSION AUTHORIZATION regressuser1;
482+
GRANT regressgroup2 TO regressuser5; -- fails: no ADMIN OPTION
483+
SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN
484+
SET ROLE regressgroup2;
485+
GRANT regressgroup2 TO regressuser5; -- fails: SET ROLE did not help
486+
487+
SET SESSION AUTHORIZATION regressgroup2;
488+
GRANT regressgroup2 TO regressuser5; -- ok: a role can self-admin
489+
CREATE FUNCTION dogrant_fails() RETURNS void LANGUAGE sql SECURITY DEFINER AS
490+
'GRANT regressgroup2 TO regressuser5';
491+
SELECT dogrant_fails(); -- fails: no self-admin in SECURITY DEFINER
492+
DROP FUNCTION dogrant_fails();
493+
494+
SET SESSION AUTHORIZATION regressuser4;
495+
DROP FUNCTION dogrant_ok();
496+
REVOKE regressgroup2 FROM regressuser5;
497+
498+
472499
-- test that dependent privileges are revoked (or not) properly
473500
\c -
474501

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy