From c19b367d37a7ab145ab8a0646cba876cbb8e8f97 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Mon, 29 Jun 2026 19:39:46 +0200 Subject: [PATCH] MDEV-35743 Role-granted routine privileges lost after FLUSH PRIVILEGES Roles are merged in dependency order: each role has a counter equal to the number of roles granted to it, and it is merged only once that counter is ticked down to zero (all roles it inherits from are done). The counter is ticked when the walk follows an edge into the role. When rebuilding everything from scratch (acl_load / FLUSH PRIVILEGES), merge_role_privileges() did not follow the edges out of a role whose own privileges did not change. For a role granted to several roles (more than one incoming edge), its counter never reached zero, it was never merged, and it lost the privileges it should have inherited indirectly. The "did not change, so stop" shortcut is only valid for incremental propagation, where the rest of the graph is already merged. During a full rebuild we must always keep walking so every counter reaches zero. --- mysql-test/suite/roles/MDEV-35743.result | 29 ++++++++++++++++++++ mysql-test/suite/roles/MDEV-35743.test | 34 ++++++++++++++++++++++++ sql/sql_acl.cc | 7 +++-- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 mysql-test/suite/roles/MDEV-35743.result create mode 100644 mysql-test/suite/roles/MDEV-35743.test diff --git a/mysql-test/suite/roles/MDEV-35743.result b/mysql-test/suite/roles/MDEV-35743.result new file mode 100644 index 0000000000000..9f97e71d1c6cb --- /dev/null +++ b/mysql-test/suite/roles/MDEV-35743.result @@ -0,0 +1,29 @@ +create user supervisor; +create role r_admin; +create role r_limit_mod; +create role r_limit_view; +grant r_limit_mod to r_admin with admin option; +grant r_limit_view to r_admin with admin option; +grant r_limit_view to r_limit_mod; +create or replace function g1(p int) returns int return p+1; +grant execute on function g1 to r_limit_mod; +grant r_admin to supervisor with admin option; +set default role r_admin for supervisor; +connect con, localhost, supervisor; +select g1(1); +g1(1) +2 +disconnect con; +connection default; +flush privileges; +connect con, localhost, supervisor; +select g1(2); +g1(2) +3 +disconnect con; +connection default; +drop function g1; +drop role r_limit_view; +drop role r_limit_mod; +drop role r_admin; +drop user supervisor; diff --git a/mysql-test/suite/roles/MDEV-35743.test b/mysql-test/suite/roles/MDEV-35743.test new file mode 100644 index 0000000000000..0416d0f11a919 --- /dev/null +++ b/mysql-test/suite/roles/MDEV-35743.test @@ -0,0 +1,34 @@ +# MDEV-35743: FLUSH PRIVILEGES breaks role-based function execution grants +create user supervisor; + +create role r_admin; +create role r_limit_mod; +create role r_limit_view; + +grant r_limit_mod to r_admin with admin option; +grant r_limit_view to r_admin with admin option; +grant r_limit_view to r_limit_mod; + +create or replace function g1(p int) returns int return p+1; + +grant execute on function g1 to r_limit_mod; +grant r_admin to supervisor with admin option; +set default role r_admin for supervisor; + +--connect con, localhost, supervisor +select g1(1); +--disconnect con +--connection default +flush privileges; + +--connect con, localhost, supervisor +select g1(2); +--disconnect con +--connection default + +# Cleanup +drop function g1; +drop role r_limit_view; +drop role r_limit_mod; +drop role r_admin; +drop user supervisor; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 788442523db29..8d055c50e422b 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -6359,6 +6359,7 @@ struct PRIVS_TO_MERGE ALL, GLOBAL, DB, TABLE_COLUMN, PROC, FUNC, PACKAGE_SPEC, PACKAGE_BODY } what; const char *db, *name; + bool rebuild_all; // full rebuild (acl_load/FLUSH): merge all roles, no shortcut }; @@ -6413,7 +6414,7 @@ static void propagate_role_grants(ACL_ROLE *role, return; mysql_mutex_assert_owner(&acl_cache->lock); - PRIVS_TO_MERGE data= { what, db, name }; + PRIVS_TO_MERGE data= { what, db, name, false }; /* Before updating grants to roles that inherit from this role, ensure that @@ -7222,6 +7223,8 @@ static int merge_role_privileges(ACL_USER_BASE *, changed|= merge_role_routine_grant_privileges(grantee, data->db, data->name, &role_hash, &package_body_priv_hash); + if (data->rebuild_all) + return 0; // full rebuild: always descend so every counter reaches zero return !changed; // don't recurse into the subgraph if privs didn't change } @@ -8295,7 +8298,7 @@ static my_bool propagate_role_grants_action(void *role_ptr, return 0; mysql_mutex_assert_owner(&acl_cache->lock); - PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 }; + PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0, true }; traverse_role_graph_up(role, &data, NULL, merge_role_privileges); return 0; }