Skip to content

Commit 324bcff

Browse files
tglsfdcCommitfest Bot
authored andcommitted
Avoid race condition between "GRANT role" and "DROP ROLE".
Concurrently dropping either the granted role or the grantee does not stop GRANT from completing, instead resulting in a dangling role reference in pg_auth_members. That's relatively harmless in the short run, but it does result in garbage output from pg_dumpall and therefore problems for pg_upgrade. This patch deals with the problem by adding the granted and grantee roles as explicit shared dependencies of the pg_auth_members entry. That's a bit indirect, but it works because the pg_shdepend code applies the necessary locking and rechecking. Commit 6566133 previously established similar handling for the grantor column of pg_auth_members; it's not clear why it didn't cover the other two role OID columns. A side-effect of this approach is that DROP OWNED BY will now drop pg_auth_members entries that mention the target role as either the granted or grantee role. That's clearly appropriate for the grantee, and it doesn't seem too far out of line for the granted role. (One could argue that this makes DropRole's code to auto-drop pg_auth_members entries unnecessary, but I chose to leave it in place since perhaps some people's workflows expect that to work without a DROP OWNED BY.) Reported-by: Virender Singla <[email protected]> Discussion: https://postgr.es/m/CAM6Zo8woa62ZFHtMKox6a4jb8qQ=w87R2L0K8347iE-juQL2EA@mail.gmail.com Backpatch-through: TBD
1 parent 2c53dec commit 324bcff

File tree

4 files changed

+63
-6
lines changed

4 files changed

+63
-6
lines changed

doc/src/sgml/ref/drop_owned.sgml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ DROP OWNED BY { <replaceable class="parameter">name</replaceable> | CURRENT_ROLE
3333
database that are owned by one of the specified roles. Any
3434
privileges granted to the given roles on objects in the current
3535
database or on shared objects (databases, tablespaces, configuration
36-
parameters) will also be revoked.
36+
parameters, or other roles) will also be revoked.
3737
</para>
3838
</refsect1>
3939

src/backend/commands/user.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "commands/defrem.h"
3131
#include "commands/seclabel.h"
3232
#include "commands/user.h"
33+
#include "lib/qunique.h"
3334
#include "libpq/crypt.h"
3435
#include "miscadmin.h"
3536
#include "storage/lmgr.h"
@@ -489,7 +490,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
489490
* Advance command counter so we can see new record; else tests in
490491
* AddRoleMems may fail.
491492
*/
492-
if (addroleto || adminmembers || rolemembers)
493+
if (addroleto || adminmembers || rolemembers || !superuser())
493494
CommandCounterIncrement();
494495

495496
/* Default grant. */
@@ -1904,7 +1905,8 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
19041905
else
19051906
{
19061907
Oid objectId;
1907-
Oid *newmembers = palloc(sizeof(Oid));
1908+
Oid *newmembers = (Oid *) palloc(3 * sizeof(Oid));
1909+
int nnewmembers;
19081910

19091911
/*
19101912
* The values for these options can be taken directly from 'popt'.
@@ -1946,12 +1948,22 @@ AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
19461948
new_record, new_record_nulls);
19471949
CatalogTupleInsert(pg_authmem_rel, tuple);
19481950

1949-
/* updateAclDependencies wants to pfree array inputs */
1950-
newmembers[0] = grantorId;
1951+
/*
1952+
* Record dependencies on the roleid, member, and grantor, as if a
1953+
* pg_auth_members entry were an object ACL.
1954+
* updateAclDependencies() requires an input array that is
1955+
* palloc'd (it will free it), sorted, and de-duped.
1956+
*/
1957+
newmembers[0] = roleid;
1958+
newmembers[1] = memberid;
1959+
newmembers[2] = grantorId;
1960+
qsort(newmembers, 3, sizeof(Oid), oid_cmp);
1961+
nnewmembers = qunique(newmembers, 3, sizeof(Oid), oid_cmp);
1962+
19511963
updateAclDependencies(AuthMemRelationId, objectId,
19521964
0, InvalidOid,
19531965
0, NULL,
1954-
1, newmembers);
1966+
nnewmembers, newmembers);
19551967
}
19561968

19571969
/* CCI after each change, in case there are duplicates in list */

src/test/regress/expected/privileges.out

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,36 @@ CREATE USER regress_priv_user2;
113113
CREATE USER regress_priv_user3;
114114
CREATE USER regress_priv_user4;
115115
CREATE USER regress_priv_user5;
116+
-- DROP OWNED should also act on granted and granted-to roles
117+
GRANT regress_priv_user1 TO regress_priv_user2;
118+
GRANT regress_priv_user2 TO regress_priv_user3;
119+
SELECT roleid::regrole, member::regrole FROM pg_auth_members
120+
WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole)
121+
ORDER BY roleid::regrole::text;
122+
roleid | member
123+
--------------------+--------------------
124+
regress_priv_user1 | regress_priv_user2
125+
regress_priv_user2 | regress_priv_user3
126+
(2 rows)
127+
128+
REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4; -- no effect
129+
SELECT roleid::regrole, member::regrole FROM pg_auth_members
130+
WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole)
131+
ORDER BY roleid::regrole::text;
132+
roleid | member
133+
--------------------+--------------------
134+
regress_priv_user1 | regress_priv_user2
135+
regress_priv_user2 | regress_priv_user3
136+
(2 rows)
137+
138+
DROP OWNED BY regress_priv_user2; -- removes both grants
139+
SELECT roleid::regrole, member::regrole FROM pg_auth_members
140+
WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole)
141+
ORDER BY roleid::regrole::text;
142+
roleid | member
143+
--------+--------
144+
(0 rows)
145+
116146
GRANT pg_read_all_data TO regress_priv_user6;
117147
GRANT pg_write_all_data TO regress_priv_user7;
118148
GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION;

src/test/regress/sql/privileges.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ CREATE USER regress_priv_user3;
9090
CREATE USER regress_priv_user4;
9191
CREATE USER regress_priv_user5;
9292

93+
-- DROP OWNED should also act on granted and granted-to roles
94+
GRANT regress_priv_user1 TO regress_priv_user2;
95+
GRANT regress_priv_user2 TO regress_priv_user3;
96+
SELECT roleid::regrole, member::regrole FROM pg_auth_members
97+
WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole)
98+
ORDER BY roleid::regrole::text;
99+
REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4; -- no effect
100+
SELECT roleid::regrole, member::regrole FROM pg_auth_members
101+
WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole)
102+
ORDER BY roleid::regrole::text;
103+
DROP OWNED BY regress_priv_user2; -- removes both grants
104+
SELECT roleid::regrole, member::regrole FROM pg_auth_members
105+
WHERE roleid IN ('regress_priv_user1'::regrole,'regress_priv_user2'::regrole)
106+
ORDER BY roleid::regrole::text;
107+
93108
GRANT pg_read_all_data TO regress_priv_user6;
94109
GRANT pg_write_all_data TO regress_priv_user7;
95110
GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION;

0 commit comments

Comments
 (0)