Commit b9418ed3 authored by Vicențiu Ciorbaru's avatar Vicențiu Ciorbaru

MDEV-13676: Field "create Procedure" is NULL, even if the the user has role...

MDEV-13676: Field "create Procedure" is NULL, even if the the user has role which is the definer. (SHOW CREATE PROCEDURE)

During show create procedure we ommited to check the current role, if it
is the actual definer of the procedure. In addition, we should support
indirectly granted roles to the current role. Implemented a recursive
lookup to search the tree of grants if the rolename is present.

SQL Standard 2016, Part 5 Section 53 View I_S.ROUTINES selects
ROUTINE_BODY and its WHERE clause says that the GRANTEE must be
either PUBLIC, or CURRENT_USER or in the ENABLED_ROLES.
parent fc9ff695
......@@ -623,3 +623,117 @@ show grants for utest;
Grants for utest
GRANT SELECT ON *.* TO 'utest'
drop role utest;
#
# MDEV-13676: Field "create Procedure" is NULL, even if the the user
# has role which is the definer. (SHOW CREATE PROCEDURE)
#
create database rtest;
create role r1;
create role r2;
create role r3;
grant all privileges on rtest.* to r1;
create user user1;
grant r1 to user1;
grant r1 to r2;
grant r2 to user1;
grant r3 to user1;
set role r2;
use rtest;
CREATE DEFINER=current_role() PROCEDURE user1_proc() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
set role r2;
show create procedure user1_proc;
Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation
user1_proc CREATE DEFINER=`r2` PROCEDURE `user1_proc`()
SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END latin1 latin1_swedish_ci latin1_swedish_ci
#
# Currently one can not use as definer any role except CURRENT_ROLE
#
CREATE DEFINER='r1' PROCEDURE user1_proc2() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation
set role r1;
CREATE DEFINER='r1' PROCEDURE user1_proc2() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
show create procedure user1_proc2;
Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation
user1_proc2 CREATE DEFINER=`r1` PROCEDURE `user1_proc2`()
SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END latin1 latin1_swedish_ci latin1_swedish_ci
#
# Test to see if the user can still see the procedure code if the
# role that owns it is granted to him indirectly.
#
set role r2;
show create procedure user1_proc2;
Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation
user1_proc2 CREATE DEFINER=`r1` PROCEDURE `user1_proc2`()
SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END latin1 latin1_swedish_ci latin1_swedish_ci
#
# One should not be able to see the procedure code if the role that owns
# the procedure is not set by the user or is not in the subgraph of the
# currently active role.
#
set role r3;
show create procedure user1_proc2;
ERROR 42000: PROCEDURE user1_proc2 does not exist
use rtest;
#
# Try a few edge cases, with usernames identical to role name;
#
create user user_like_role;
create user foo;
create role user_like_role;
grant select on rtest.* to user_like_role;
grant select on rtest.* to foo;
grant select on rtest.* to user_like_role@'%';
grant user_like_role to foo;
#
# Here we have a procedure that is owned by user_like_role USER
# We don't want user_like_role ROLE to have access to its code.
#
CREATE DEFINER=`user_like_role`@`%` PROCEDURE sensitive_proc() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
use rtest;
show create procedure sensitive_proc;
Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation
sensitive_proc CREATE DEFINER=`user_like_role`@`%` PROCEDURE `sensitive_proc`()
SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END latin1 latin1_swedish_ci latin1_swedish_ci
set role user_like_role;
use rtest;
#
# Foo has the set rolename identical to the procedure's definer's username.
# Foo should not have access to this procedure.
#
show create procedure sensitive_proc;
ERROR 42000: PROCEDURE sensitive_proc does not exist
drop role r1;
drop role r2;
drop role r3;
drop role user_like_role;
drop user user1;
drop user foo;
drop user user_like_role;
drop procedure user1_proc;
drop procedure user1_proc2;
drop procedure sensitive_proc;
drop database rtest;
......@@ -329,3 +329,125 @@ execute stmt1;
show grants for utest;
drop role utest;
--echo #
--echo # MDEV-13676: Field "create Procedure" is NULL, even if the the user
--echo # has role which is the definer. (SHOW CREATE PROCEDURE)
--echo #
create database rtest;
create role r1;
create role r2;
create role r3;
grant all privileges on rtest.* to r1;
create user user1;
grant r1 to user1;
grant r1 to r2;
grant r2 to user1;
grant r3 to user1;
connect (user1, localhost,user1,,,,,);
set role r2;
use rtest;
DELIMITER //;
CREATE DEFINER=current_role() PROCEDURE user1_proc() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
DELIMITER ;//
set role r2;
show create procedure user1_proc;
--echo #
--echo # Currently one can not use as definer any role except CURRENT_ROLE
--echo #
DELIMITER //;
--error ER_SPECIFIC_ACCESS_DENIED_ERROR
CREATE DEFINER='r1' PROCEDURE user1_proc2() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
DELIMITER ;//
set role r1;
DELIMITER //;
CREATE DEFINER='r1' PROCEDURE user1_proc2() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
DELIMITER ;//
show create procedure user1_proc2;
--echo #
--echo # Test to see if the user can still see the procedure code if the
--echo # role that owns it is granted to him indirectly.
--echo #
set role r2;
show create procedure user1_proc2;
--echo #
--echo # One should not be able to see the procedure code if the role that owns
--echo # the procedure is not set by the user or is not in the subgraph of the
--echo # currently active role.
--echo #
set role r3;
--error ER_SP_DOES_NOT_EXIST
show create procedure user1_proc2;
connection default;
use rtest;
--echo #
--echo # Try a few edge cases, with usernames identical to role name;
--echo #
create user user_like_role;
create user foo;
create role user_like_role;
grant select on rtest.* to user_like_role;
grant select on rtest.* to foo;
grant select on rtest.* to user_like_role@'%';
grant user_like_role to foo;
--echo #
--echo # Here we have a procedure that is owned by user_like_role USER
--echo # We don't want user_like_role ROLE to have access to its code.
--echo #
DELIMITER //;
CREATE DEFINER=`user_like_role`@`%` PROCEDURE sensitive_proc() SQL SECURITY INVOKER
BEGIN
SELECT NOW(), VERSION();
END;//
DELIMITER ;//
connect (user_like_role, localhost, user_like_role,,,,,);
use rtest;
show create procedure sensitive_proc;
connect (foo, localhost, foo,,,,,);
set role user_like_role;
use rtest;
--echo #
--echo # Foo has the set rolename identical to the procedure's definer's username.
--echo # Foo should not have access to this procedure.
--echo #
--error ER_SP_DOES_NOT_EXIST
show create procedure sensitive_proc;
connection default;
drop role r1;
drop role r2;
drop role r3;
drop role user_like_role;
drop user user1;
drop user foo;
drop user user_like_role;
drop procedure user1_proc;
drop procedure user1_proc2;
drop procedure sensitive_proc;
drop database rtest;
......@@ -2588,10 +2588,18 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access)
*full_access= ((!check_table_access(thd, SELECT_ACL, &tables, FALSE,
1, TRUE) &&
(tables.grant.privilege & SELECT_ACL) != 0) ||
/* Check if user owns the routine. */
(!strcmp(sp->m_definer_user.str,
thd->security_ctx->priv_user) &&
!strcmp(sp->m_definer_host.str,
thd->security_ctx->priv_host)));
thd->security_ctx->priv_host)) ||
/* Check if current role or any of the sub-granted roles
own the routine. */
(sp->m_definer_host.length == 0 &&
(!strcmp(sp->m_definer_user.str,
thd->security_ctx->priv_role) ||
check_role_is_granted(thd->security_ctx->priv_role, NULL,
sp->m_definer_user.str))));
if (!*full_access)
return check_some_routine_access(thd, sp->m_db.str, sp->m_name.str,
sp->m_type == TYPE_ENUM_PROCEDURE);
......
......@@ -8253,6 +8253,52 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc)
mysql_mutex_unlock(&acl_cache->lock);
}
static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data)
{
LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data);
if (rolename->length == grantee->user.length &&
!strcmp(rolename->str, grantee->user.str))
return -1; // End search, we've found our role.
/* Keep looking, we haven't found our role yet. */
return 0;
}
/* Check if a role is granted to a user/role. We traverse the role graph
and return true if we find a match.
hostname == NULL means we are looking for a role as a starting point,
otherwise a user.
*/
bool check_role_is_granted(const char *username,
const char *hostname,
const char *rolename)
{
DBUG_ENTER("check_role_is_granted");
ACL_USER_BASE *root;
bool result= false;
mysql_mutex_lock(&acl_cache->lock);
if (hostname)
root= find_user_exact(username, hostname);
else
root= find_acl_role(username);
LEX_CSTRING role_lex;
role_lex.str= rolename;
role_lex.length= strlen(rolename);
if (root && /* No grantee, nothing to search. */
traverse_role_graph_down(root, &role_lex, check_role_is_granted_callback,
NULL) == -1)
{
/* We have found the role during our search. */
result= true;
}
/* We haven't found the role or we had no initial grantee to start from. */
mysql_mutex_unlock(&acl_cache->lock);
DBUG_RETURN(result);
}
/*
Open the grant tables.
......
......@@ -402,6 +402,14 @@ bool acl_check_proxy_grant_access (THD *thd, const char *host, const char *user,
int acl_setrole(THD *thd, char *rolename, ulonglong access);
int acl_check_setrole(THD *thd, char *rolename, ulonglong *access);
/* Check if a role is granted to a user/role.
If hostname == NULL, search for a role as the starting grantee.
*/
bool check_role_is_granted(const char *username,
const char *hostname,
const char *rolename);
#ifndef DBUG_OFF
extern ulong role_global_merges, role_db_merges, role_table_merges,
role_column_merges, role_routine_merges;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment