Skip to content

Commit 993bd51

Browse files
craig[bot]spilchen
andcommitted
Merge #151472
151472: sql: fix DROP USER to detect default privilege dependencies r=rafiss a=spilchen Prior to this fix, DROP USER would succeed even when a role had created default privilege rules, leaving orphaned default privilege entries that referenced non-existent roles. The issue occurred because the dependency detection logic only checked for roles that had privileges granted to them, but missed the case where a role was the creator of default privileges through REVOKE operations. When default privileges are created with REVOKE (e.g., "ALTER DEFAULT PRIVILEGES FOR ROLE user REVOKE EXECUTE ON ROUTINES FROM public"), the grantee list becomes empty, so the original logic didn't detect the dependency. This fix adds explicit detection of creator dependencies for default privileges, ensuring that roles cannot be dropped if they own default privilege rules. Fixes #150543. Release note (bug fix): Fixed DROP USER succeeding when a role owned default privileges, which could leave invalid privilege entries in the system. Epic: None Co-authored-by: Matt Spilchen <[email protected]>
2 parents 1e5c41d + 8f8d482 commit 993bd51

File tree

6 files changed

+163
-15
lines changed

6 files changed

+163
-15
lines changed

pkg/cmd/roachtest/testdata/pg_regress/privileges.diffs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4497,7 +4497,7 @@ diff -U3 --label=/mnt/data1/postgres/src/test/regress/expected/privileges.out --
44974497
DROP TABLE atest1;
44984498
DROP TABLE atest2;
44994499
DROP TABLE atest3;
4500-
@@ -2680,108 +4209,214 @@
4500+
@@ -2680,108 +4209,219 @@
45014501
DROP TABLE atest5;
45024502
DROP TABLE atest6;
45034503
DROP TABLE atestc;
@@ -4527,15 +4527,20 @@ diff -U3 --label=/mnt/data1/postgres/src/test/regress/expected/privileges.out --
45274527
+HINT: try \h REVOKE
45284528
DROP OWNED BY regress_priv_user1;
45294529
DROP USER regress_priv_user1;
4530+
+ERROR: role regress_priv_user1 cannot be dropped because some objects depend on it
4531+
+owner of default privileges that revokes privileges of routines from public in database root
4532+
+owner of default privileges that revokes privileges of types from public in database root
4533+
+HINT: USE root; ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 GRANT EXECUTE ON ROUTINES TO public;
4534+
+USE root; ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 GRANT USAGE ON TYPES TO public;
45304535
DROP USER regress_priv_user2;
45314536
+ERROR: role regress_priv_user2 cannot be dropped because some objects depend on it
4532-
+privileges for default privileges on new belonging to role test_admin in database root
45334537
+privileges for default privileges on new relations belonging to role test_admin in database root
4538+
+privileges for default privileges on new routines belonging to role test_admin in database root
45344539
+privileges for default privileges on new schemas belonging to role test_admin in database root
45354540
+privileges for default privileges on new sequences belonging to role test_admin in database root
45364541
+privileges for default privileges on new types belonging to role test_admin in database root
4537-
+HINT: USE root; ALTER DEFAULT PRIVILEGES FOR ROLE test_admin REVOKE ALL ON ROUTINES FROM regress_priv_user2;
4538-
+USE root; ALTER DEFAULT PRIVILEGES FOR ROLE test_admin REVOKE ALL ON TABLES FROM regress_priv_user2;
4542+
+HINT: USE root; ALTER DEFAULT PRIVILEGES FOR ROLE test_admin REVOKE ALL ON TABLES FROM regress_priv_user2;
4543+
+USE root; ALTER DEFAULT PRIVILEGES FOR ROLE test_admin REVOKE ALL ON ROUTINES FROM regress_priv_user2;
45394544
+USE root; ALTER DEFAULT PRIVILEGES FOR ROLE test_admin REVOKE ALL ON SCHEMAS FROM regress_priv_user2;
45404545
+USE root; ALTER DEFAULT PRIVILEGES FOR ROLE test_admin REVOKE ALL ON SEQUENCES FROM regress_priv_user2;
45414546
+USE root; ALTER DEFAULT PRIVILEGES FOR ROLE test_admin REVOKE ALL ON TYPES FROM regress_priv_user2;
@@ -4725,7 +4730,7 @@ diff -U3 --label=/mnt/data1/postgres/src/test/regress/expected/privileges.out --
47254730
-- clean up
47264731
DROP TABLE lock_table;
47274732
DROP USER regress_locktable_user;
4728-
@@ -2791,24 +4426,17 @@
4733+
@@ -2791,24 +4431,17 @@
47294734
\c -
47304735
CREATE ROLE regress_readallstats;
47314736
SELECT has_table_privilege('regress_readallstats','pg_backend_memory_contexts','SELECT'); -- no
@@ -4754,7 +4759,7 @@ diff -U3 --label=/mnt/data1/postgres/src/test/regress/expected/privileges.out --
47544759
SELECT has_table_privilege('regress_readallstats','pg_shmem_allocations','SELECT'); -- yes
47554760
has_table_privilege
47564761
---------------------
4757-
@@ -2818,11 +4446,7 @@
4762+
@@ -2818,11 +4451,7 @@
47584763
-- run query to ensure that functions within views can be executed
47594764
SET ROLE regress_readallstats;
47604765
SELECT COUNT(*) >= 0 AS ok FROM pg_backend_memory_contexts;
@@ -4767,7 +4772,7 @@ diff -U3 --label=/mnt/data1/postgres/src/test/regress/expected/privileges.out --
47674772
SELECT COUNT(*) >= 0 AS ok FROM pg_shmem_allocations;
47684773
ok
47694774
----
4770-
@@ -2838,28 +4462,48 @@
4775+
@@ -2838,28 +4467,48 @@
47714776
CREATE ROLE regress_group_indirect_manager;
47724777
CREATE ROLE regress_group_member;
47734778
GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE;
@@ -4826,7 +4831,7 @@ diff -U3 --label=/mnt/data1/postgres/src/test/regress/expected/privileges.out --
48264831
DROP ROLE regress_group;
48274832
DROP ROLE regress_group_direct_manager;
48284833
DROP ROLE regress_group_indirect_manager;
4829-
@@ -2871,22 +4515,59 @@
4834+
@@ -2871,22 +4520,59 @@
48304835
CREATE SCHEMA regress_roleoption;
48314836
GRANT CREATE, USAGE ON SCHEMA regress_roleoption TO PUBLIC;
48324837
GRANT regress_roleoption_donor TO regress_roleoption_protagonist WITH INHERIT TRUE, SET FALSE;

pkg/cmd/roachtest/tests/pg_regress.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,6 @@ func registerPGRegress(r registry.Registry) {
516516
Run: func(ctx context.Context, t test.Test, c cluster.Cluster) {
517517
runPGRegress(ctx, t, c)
518518
},
519-
// TODO(#150543): remove this.
520-
SkipPostValidations: registry.PostValidationInvalidDescriptors,
521519
})
522520
}
523521

pkg/sql/catalog/catpb/privilege.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ message DefaultPrivilegesForRole {
6262
// for the role causes it to "own" default privileges as it is no longer
6363
// the "default" case and the role cannot be dropped until the default case
6464
// is met.
65+
// Note: if adding any public_has_* fields in the future, be sure to update
66+
// checking of these default privileges in addDependentPrivileges.
6567
optional bool public_has_usage_on_types = 4 [(gogoproto.nullable) = false];
6668
optional bool role_has_all_privileges_on_tables = 5 [(gogoproto.nullable) = false];
6769
optional bool role_has_all_privileges_on_sequences = 6 [(gogoproto.nullable) = false];

pkg/sql/drop_role.go

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,16 +563,40 @@ func accumulateDependentDefaultPrivileges(
563563
role.ForAllRoles = true
564564
}
565565
for object, defaultPrivs := range defaultPrivilegesForRole.DefaultPrivilegesPerObject {
566-
addDependentPrivileges(object, defaultPrivs, role, userNames, dbName, schemaName)
566+
addDependentPrivileges(object, defaultPrivs, role, defaultPrivilegesForRole.GetExplicitRole(),
567+
userNames, dbName, schemaName)
567568
}
568569
return nil
569570
})
570571
}
571572

573+
// addDependentPrivileges checks if a specific default privilege creates dependencies
574+
// that would prevent dropping any of the target roles.
575+
//
576+
// This function examines one default privilege rule and determines if:
577+
// 1. The role that created the default privilege is being dropped (creator dependency).
578+
// 2. Any roles that were granted privileges by this default privilege are being
579+
// dropped (grantee dependency).
580+
//
581+
// Parameters:
582+
// - object: The type of database object this default privilege applies to
583+
// (e.g., privilege.Tables, privilege.Sequences, privilege.Routines, etc.).
584+
// - defaultPrivs: The privilege descriptor containing the actual privilege
585+
// grants/revokes for this specific default privilege rule.
586+
// - role: The role information for who created this default privilege rule.
587+
// Contains either a specific role (role.Role) or indicates it applies to all roles
588+
// (role.ForAllRoles)
589+
// - explicitRole: This is passed in if the default privilege is for a specific role.
590+
// - userNames: Map of roles being dropped -> their accumulated dependency objects.
591+
// Used both for lookup (is this role being dropped?) and for recording dependencies.
592+
// - dbName: Name of the database where this default privilege is defined.
593+
// - schemaName: Name of the schema where this default privilege is defined,
594+
// or empty string if this is a database-level default privilege.
572595
func addDependentPrivileges(
573596
object privilege.TargetObjectType,
574597
defaultPrivs catpb.PrivilegeDescriptor,
575598
role catpb.DefaultPrivilegesRole,
599+
explicitRole *catpb.DefaultPrivilegesForRole_ExplicitRole,
576600
userNames map[username.SQLUsername][]objectAndType,
577601
dbName string,
578602
schemaName string,
@@ -587,6 +611,10 @@ func addDependentPrivileges(
587611
objectType = "types"
588612
case privilege.Schemas:
589613
objectType = "schemas"
614+
case privilege.Routines:
615+
objectType = "routines"
616+
default:
617+
objectType = object.String()
590618
}
591619

592620
inSchemaMsg := ""
@@ -597,22 +625,34 @@ func addDependentPrivileges(
597625
createHint := func(
598626
role catpb.DefaultPrivilegesRole,
599627
grantee username.SQLUsername,
628+
isRevoke bool,
629+
privilegeKind privilege.Kind,
600630
) string {
601631

602632
roleString := "ALL ROLES"
603633
if !role.ForAllRoles {
604634
roleString = fmt.Sprintf("ROLE %s", role.Role.SQLIdentifier())
605635
}
606636

607-
return fmt.Sprintf("USE %s; ALTER DEFAULT PRIVILEGES FOR %s%s REVOKE ALL ON %s FROM %s;",
608-
dbName, roleString, strings.ToUpper(inSchemaMsg), strings.ToUpper(object.String()), grantee.SQLIdentifier())
637+
var action, targetKeyword string
638+
if isRevoke {
639+
action = "REVOKE"
640+
targetKeyword = "FROM"
641+
} else {
642+
action = "GRANT"
643+
targetKeyword = "TO"
644+
}
645+
646+
return fmt.Sprintf("USE %s; ALTER DEFAULT PRIVILEGES FOR %s%s %s %s ON %s %s %s;",
647+
dbName, roleString, strings.ToUpper(inSchemaMsg), action, string(privilegeKind.DisplayName()),
648+
strings.ToUpper(object.String()), targetKeyword, grantee.SQLIdentifier())
609649
}
610650

611651
for _, privs := range defaultPrivs.Users {
612652
grantee := privs.User()
613653
if !role.ForAllRoles {
614654
if _, ok := userNames[role.Role]; ok {
615-
hint := createHint(role, grantee)
655+
hint := createHint(role, grantee, true /* isRevoke */, privilege.ALL)
616656
userNames[role.Role] = append(userNames[role.Role],
617657
objectAndType{
618658
IsDefaultPrivilege: true,
@@ -625,7 +665,7 @@ func addDependentPrivileges(
625665
}
626666
}
627667
if _, ok := userNames[grantee]; ok {
628-
hint := createHint(role, grantee)
668+
hint := createHint(role, grantee, true /* isRevoke */, privilege.ALL)
629669
var err error
630670
if role.ForAllRoles {
631671
err = errors.Newf(
@@ -645,6 +685,32 @@ func addDependentPrivileges(
645685
})
646686
}
647687
}
688+
689+
// New roles grant EXECUTE on functions and USAGE on types by default. If any
690+
// of those two were revoked, the privilege needs to be granted again to
691+
// remove any dependencies.
692+
if explicitRole != nil {
693+
if _, ok := userNames[role.Role]; ok {
694+
var hint string
695+
if !explicitRole.PublicHasUsageOnTypes && object == privilege.Types {
696+
hint = createHint(role, username.PublicRoleName(), false /* isRevoke */, privilege.USAGE)
697+
}
698+
if !explicitRole.PublicHasExecuteOnFunctions && object == privilege.Routines {
699+
hint = createHint(role, username.PublicRoleName(), false /* isRevoke */, privilege.EXECUTE)
700+
}
701+
if hint != "" {
702+
userNames[role.Role] = append(userNames[role.Role],
703+
objectAndType{
704+
IsDefaultPrivilege: true,
705+
ErrorMessage: errors.WithHint(
706+
errors.Newf(
707+
"owner of default privileges that revokes privileges of %s from public in database %s%s",
708+
objectType, dbName, inSchemaMsg,
709+
), hint),
710+
})
711+
}
712+
}
713+
}
648714
}
649715

650716
func addDependentPrivilegesFromSystemPrivileges(

pkg/sql/logictest/testdata/logic_test/drop_user

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,78 @@ statement error role schema_owner cannot be dropped because some objects depend
215215
DROP ROLE schema_owner
216216

217217
subtest end
218+
219+
# Regression test for #150543. Ensure that DROP USER is blocked when role has
220+
# default privileges.
221+
subtest default_privileges_dependency
222+
223+
statement ok
224+
CREATE USER default_priv_user
225+
226+
# Create default privileges with REVOKE. Target routines and types initially. These undo
227+
# the implicitly created default privileges for new roles.
228+
statement ok
229+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user REVOKE EXECUTE ON ROUTINES FROM public
230+
231+
statement ok
232+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user REVOKE USAGE ON TYPES FROM public
233+
234+
# The next threes REVOKE's are essentially no-op since they don't change anything
235+
# as nothing was implicitly granted for them.
236+
statement ok
237+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user REVOKE USAGE ON SEQUENCES FROM public
238+
239+
statement ok
240+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user REVOKE ALL ON TABLES FROM public
241+
242+
statement ok
243+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user REVOKE ALL ON SCHEMAS FROM public
244+
245+
statement error role default_priv_user cannot be dropped because some objects depend on it\nowner of default privileges that revokes privileges of routines from public in database test\nowner of default privileges that revokes privileges of types from public in database test\nHINT: USE test; ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user GRANT EXECUTE ON ROUTINES TO public;\nUSE test; ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user GRANT USAGE ON TYPES TO public;
246+
DROP USER default_priv_user
247+
248+
# Test with GRANT case
249+
statement ok
250+
CREATE USER default_priv_user2
251+
252+
statement ok
253+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 GRANT ALL ON TABLES TO public
254+
255+
statement ok
256+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 GRANT ALL ON SCHEMAS TO public
257+
258+
statement ok
259+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 GRANT USAGE ON SEQUENCES TO public
260+
261+
statement error role default_priv_user2 cannot be dropped because some objects depend on it\nowner of default privileges on new relations belonging to role default_priv_user2 in database test\nowner of default privileges on new schemas belonging to role default_priv_user2 in database test\nowner of default privileges on new sequences belonging to role default_priv_user2 in database test\nHINT: USE test; ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 REVOKE ALL ON TABLES FROM public;\nUSE test; ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 REVOKE ALL ON SCHEMAS FROM public;\nUSE test; ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 REVOKE ALL ON SEQUENCES FROM public;
262+
DROP USER default_priv_user2
263+
264+
# Clean up for successful drop
265+
statement ok
266+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user GRANT EXECUTE ON FUNCTIONS TO public
267+
268+
statement ok
269+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user GRANT USAGE ON TYPES TO public
270+
271+
statement ok
272+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 REVOKE ALL ON TABLES FROM public
273+
274+
statement ok
275+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 REVOKE ALL ON SCHEMAS FROM public
276+
277+
statement ok
278+
ALTER DEFAULT PRIVILEGES FOR ROLE default_priv_user2 REVOKE USAGE ON SEQUENCES FROM public
279+
280+
statement ok
281+
DROP USER default_priv_user
282+
283+
statement ok
284+
DROP USER default_priv_user2
285+
286+
# Ensure there are no invalid objects left around. This was how the issue was
287+
# found prior to #150543.
288+
query TTTT
289+
SELECT database_name, schema_name, obj_name, error FROM crdb_internal.invalid_objects;
290+
----
291+
292+
subtest end

pkg/sql/schemachanger/scbuild/internal/scbuildstmt/drop_owned_by.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ func DropOwnedBy(b BuildCtx, n *tree.DropOwnedBy) {
101101
}
102102
})
103103

104+
// TODO(151425): we should cleanup default privileges for the user too like postgres
105+
104106
b.IncrementSubWorkID()
105107
b.IncrementDropOwnedByCounter()
106108

0 commit comments

Comments
 (0)