diff --git a/mysql-test/main/set_authorization.result b/mysql-test/main/set_authorization.result new file mode 100644 index 00000000000..22484a01c03 --- /dev/null +++ b/mysql-test/main/set_authorization.result @@ -0,0 +1,136 @@ +# +# MDEV-20299 SET SESSION AUTHORIZATION +# +create user foo@bar identified via mysql_native_password using password('foo'); +connect con1, localhost, root; +select user(), current_user(), database(); +user() current_user() database() +root@localhost root@localhost test +set session authorization bar@foo; +ERROR HY000: The user 'bar'@'foo' does not exist +select user(), current_user(), database(); +user() current_user() database() +root@localhost root@localhost test +set session authorization foo@bar; +select user(), current_user(), database(); +user() current_user() database() +foo@bar foo@bar NULL +set @a:='not changed'; +set session authorization bar@foo; +ERROR 28000: Access denied trying to change to user 'bar'@'foo' +select @a; +@a +not changed +set session authorization foo@bar; +select @a; +@a +NULL +disconnect con1; +connection default; +drop user foo@bar; +create user ''@'l%t' identified via mysql_native_password using password('foo'); +connect con1, localhost, root; +select user(), current_user(), database(); +user() current_user() database() +root@localhost root@localhost test +set session authorization fist@list; +select user(), current_user(), database(); +user() current_user() database() +fist@list @l%t NULL +set @a:='not changed'; +set session authorization first@last; +ERROR 28000: Access denied trying to change to user 'first'@'last' +select @a; +@a +not changed +set session authorization fist@list; +select @a; +@a +NULL +disconnect con1; +connection default; +drop user ''@'l%t'; +create user ''@'%' identified via mysql_native_password using password('foo'); +connect con1, localhost, root; +select user(), current_user(), database(); +user() current_user() database() +root@localhost root@localhost test +set session authorization ''@last; +ERROR HY000: The user ''@'last' does not exist +set session authorization foo@''; +ERROR HY000: The user 'foo'@'' does not exist +start transaction; +select user(), current_user(), database(), @@in_transaction; +user() current_user() database() @@in_transaction +root@localhost root@localhost test 1 +set session authorization foo@bar; +ERROR 25001: SESSION AUTHORIZATION can't be set while a transaction is in progress +select user(), current_user(), database(), @@in_transaction; +user() current_user() database() @@in_transaction +root@localhost root@localhost test 1 +disconnect con1; +connection default; +prepare s from 'set session authorization foo@bar'; +ERROR HY000: This command is not supported in the prepared statement protocol yet +create procedure sudo_foobar() set session authorization foo@bar; +ERROR 0A000: SET SESSION AUTHORIZATION is not allowed in stored procedures +create procedure sudo_foobar() +execute immediate 'set session authorization foo@bar'; +call sudo_foobar(); +ERROR HY000: This command is not supported in the prepared statement protocol yet +drop procedure sudo_foobar; +drop user ''@'%'; +# restart: --skip-grant-tables +set session authorization u@localhost; +ERROR HY000: The MariaDB server is running with the --skip-grant-tables option so it cannot execute this statement +flush privileges; +create user u1@localhost with max_statement_time 1; +connect u1,localhost,u1; +select @@max_statement_time; +@@max_statement_time +1.000000 +disconnect u1; +connect u1,localhost,root; +select @@max_statement_time; +@@max_statement_time +0.000000 +set session authorization u1@localhost; +select @@max_statement_time; +@@max_statement_time +1.000000 +disconnect u1; +connection default; +drop user u1@localhost; +# +# MDEV-36399 SET SESSION AUTHORIZATION allows an unrpivileged user to bypass resource limits +# +create user u1 with max_queries_per_hour 2; +connect u1,localhost,u1; +set session authorization u1@localhost; +select 1; +1 +1 +select 2; +ERROR 42000: User 'u1' has exceeded the 'max_queries_per_hour' resource (current value: 2) +disconnect u1; +connection default; +drop user u1; +# +# MDEV-36401 Access denied errors produced by SET SESSION AUTHORIZATION not reflected in status values +# +flush global status; +set session authorization u1@localhost; +ERROR HY000: The user 'u1'@'localhost' does not exist +create user u1; +connect u1,localhost,u1; +set session authorization root@localhost; +ERROR 28000: Access denied trying to change to user 'root'@'localhost' +set session authorization foo@bar; +ERROR 28000: Access denied trying to change to user 'foo'@'bar' +disconnect u1; +connection default; +show global status like 'access_denied_errors'; +Variable_name Value +Access_denied_errors 2 +drop user u1; +# End of 11.8 tests diff --git a/mysql-test/main/set_authorization.test b/mysql-test/main/set_authorization.test new file mode 100644 index 00000000000..9be6e6e2267 --- /dev/null +++ b/mysql-test/main/set_authorization.test @@ -0,0 +1,139 @@ +source include/not_embedded.inc; +source include/no_protocol.inc; + +--echo # +--echo # MDEV-20299 SET SESSION AUTHORIZATION +--echo # + +# simple tests +create user foo@bar identified via mysql_native_password using password('foo'); +connect con1, localhost, root; +select user(), current_user(), database(); +# sudo, with SET USER privilege, nonexistent user +--error ER_NO_SUCH_USER +set session authorization bar@foo; +select user(), current_user(), database(); +# sudo, with SET USER privilege +set session authorization foo@bar; +select user(), current_user(), database(); +set @a:='not changed'; +# sudo without SET USER privilege +--error ER_ACCESS_DENIED_CHANGE_USER_ERROR +set session authorization bar@foo; +select @a; +# to self, no privileges needed +set session authorization foo@bar; +select @a; +disconnect con1; +connection default; +drop user foo@bar; + +# user() != current_user() (w/ wildcards) +create user ''@'l%t' identified via mysql_native_password using password('foo'); +connect con1, localhost, root; +select user(), current_user(), database(); +# sudo, with SET USER privilege +set session authorization fist@list; +select user(), current_user(), database(); +set @a:='not changed'; +# sudo without SET USER privilege (note, same CURRENT_USER) +--error ER_ACCESS_DENIED_CHANGE_USER_ERROR +set session authorization first@last; +select @a; +# to self, no privileges needed +set session authorization fist@list; +select @a; +disconnect con1; +connection default; +drop user ''@'l%t'; + +create user ''@'%' identified via mysql_native_password using password('foo'); +connect con1, localhost, root; +select user(), current_user(), database(); +# empty username +--error ER_NO_SUCH_USER +set session authorization ''@last; +# empty hostname +--error ER_NO_SUCH_USER +set session authorization foo@''; +# in a transaction - an error +start transaction; +select user(), current_user(), database(), @@in_transaction; +--error ER_CANT_SET_IN_TRANSACTION +set session authorization foo@bar; +select user(), current_user(), database(), @@in_transaction; +disconnect con1; +connection default; + +# cannot be prepared +--error ER_UNSUPPORTED_PS +prepare s from 'set session authorization foo@bar'; + +# cannot be in a stored routine +--error ER_SP_BADSTATEMENT +create procedure sudo_foobar() set session authorization foo@bar; + +create procedure sudo_foobar() + execute immediate 'set session authorization foo@bar'; +--error ER_UNSUPPORTED_PS +call sudo_foobar(); +drop procedure sudo_foobar; + +drop user ''@'%'; + +# doesn't work if --skip-grant-tables +--let $restart_parameters= --skip-grant-tables +--source include/restart_mysqld.inc +--error ER_OPTION_PREVENTS_STATEMENT +set session authorization u@localhost; +flush privileges; + +# max_statement_time -> @@max_statement_time +create user u1@localhost with max_statement_time 1; +connect u1,localhost,u1; +select @@max_statement_time; +disconnect u1; +connect u1,localhost,root; +select @@max_statement_time; +set session authorization u1@localhost; +select @@max_statement_time; +disconnect u1; +connection default; +drop user u1@localhost; + +--echo # +--echo # MDEV-36399 SET SESSION AUTHORIZATION allows an unrpivileged user to bypass resource limits +--echo # + +create user u1 with max_queries_per_hour 2; +connect u1,localhost,u1; + +set session authorization u1@localhost; +select 1; +--error ER_USER_LIMIT_REACHED +select 2; +disconnect u1; +connection default; +drop user u1; + +--echo # +--echo # MDEV-36401 Access denied errors produced by SET SESSION AUTHORIZATION not reflected in status values +--echo # +flush global status; + +--error ER_NO_SUCH_USER +set session authorization u1@localhost; + +create user u1; + +connect u1,localhost,u1; +--error ER_ACCESS_DENIED_CHANGE_USER_ERROR +set session authorization root@localhost; +--error ER_ACCESS_DENIED_CHANGE_USER_ERROR +set session authorization foo@bar; +disconnect u1; +connection default; +show global status like 'access_denied_errors'; +drop user u1; + +--echo # End of 11.8 tests diff --git a/sql/lex.h b/sql/lex.h index 1549fb1a595..f58a8afd649 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -76,6 +76,7 @@ SYMBOL symbols[] = { { "AT", SYM(AT_SYM)}, { "ATOMIC", SYM(ATOMIC_SYM)}, { "AUTHORS", SYM(AUTHORS_SYM)}, + { "AUTHORIZATION", SYM(AUTHORIZATION_SYM)}, { "AUTO", SYM(AUTO_SYM)}, { "AUTO_INCREMENT", SYM(AUTO_INC)}, { "AVG", SYM(AVG_SYM)}, diff --git a/sql/privilege.h b/sql/privilege.h index 3e0a9df0d42..374e3a4ec85 100644 --- a/sql/privilege.h +++ b/sql/privilege.h @@ -332,6 +332,8 @@ constexpr privilege_t PRIV_DEFINER_CLAUSE= SET_USER_ACL; */ constexpr privilege_t PRIV_REVEAL_MISSING_DEFINER= SET_USER_ACL; +constexpr privilege_t PRIV_SUDO_CHANGE_USER= SET_USER_ACL; + /* Actions that require only the SUPER privilege */ constexpr privilege_t PRIV_DES_DECRYPT_ONE_ARG= SUPER_ACL; constexpr privilege_t PRIV_LOG_BIN_TRUSTED_SP_CREATOR= SUPER_ACL; diff --git a/sql/set_var.cc b/sql/set_var.cc index c1d826aeeb0..eecea0f203e 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -973,6 +973,39 @@ int set_var_password::update(THD *thd) #endif } +/***************************************************************************** + Functions to handle SET SESSION AUTHORIZATION +*****************************************************************************/ + +int set_var_authorization::check(THD *thd) +{ + /* + SET SESSION AUTHORIZATION cannot be combined with other variables, + so most of the checks are only done in update. + */ + if (!thd->stmt_arena->is_conventional()) + my_error(ER_UNSUPPORTED_PS, MYF(0)); + else + if (thd->in_active_multi_stmt_transaction()) + my_error(ER_CANT_SET_IN_TRANSACTION, MYF(0), "SESSION AUTHORIZATION"); + else + return 0; + + return 1; +} + +int set_var_authorization::update(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + int res= acl_setauthorization(thd, user); + if (!res) + thd->session_tracker.state_change.mark_as_changed(thd); + return res; +#else + return 0; +#endif +} + /***************************************************************************** Functions to handle SET ROLE *****************************************************************************/ diff --git a/sql/set_var.h b/sql/set_var.h index da74a135c37..50f36de3dbc 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -363,6 +363,17 @@ public: int update(THD *thd) override; }; +/* For SET SESSION AUTHORIZATION */ + +class set_var_authorization: public set_var_base +{ + LEX_USER *user; +public: + set_var_authorization(LEX_USER *user_arg) : user(user_arg) {} + int check(THD *thd) override; + int update(THD *thd) override; +}; + /* For SET ROLE */ class set_var_role: public set_var_base diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index a53788e2441..4d257985c0c 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -10102,11 +10102,11 @@ ER_SLAVE_RLI_INIT_REPOSITORY sw "Mtumwa alishindwa kuanzisha muundo wa habari ya relay wa logi kutoka kwa hazina" ER_ACCESS_DENIED_CHANGE_USER_ERROR 28000 - bgn "Отказан достъп при опит за смяна към потребител %-.48s'@'%-.64s' (използвана парола: %s). Затваряне на връзката" - chi "访问拒绝尝试更改为用户'%-.48s'@'%-.64s'(使用密码:%s)。断开连接" - eng "Access denied trying to change to user '%-.48s'@'%-.64s' (using password: %s). Disconnecting" - spa "Acceso denegado intentando cambiar a usuario '%-.48s'@'%-.64s' (usando contraseña: %s). Desconectando" - sw "Ufikiaji umekataliwa kujaribu kubadilisha hadi mtumiaji '%-.48s'@'%-.64s' (kwa kutumia nenosiri: %s). Inatenganisha" + bgn "Отказан достъп при опит за смяна към потребител %-.48s'@'%-.64s'" + chi "访问拒绝尝试更改为用户'%-.48s'@'%-.64s'" + eng "Access denied trying to change to user '%-.48s'@'%-.64s'" + spa "Acceso denegado intentando cambiar a usuario '%-.48s'@'%-.64s'" + sw "Ufikiaji umekataliwa kujaribu kubadilisha hadi mtumiaji '%-.48s'@'%-.64s'" ER_INNODB_READ_ONLY chi "innodb是只读模式" diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 3ec0fe5f8e7..7b8f50e1915 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -53,6 +53,7 @@ #include "sql_db.h" #include "sql_array.h" #include "sql_hset.h" +#include "sql_audit.h" #include "password.h" #include "scope.h" @@ -12608,6 +12609,87 @@ bool Sql_cmd_grant_table::execute(THD *thd) } +int acl_setauthorization(THD *thd, const LEX_USER *user) +{ + if (!initialized) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); + return 1; + } + + Security_context *sctx= thd->security_ctx, save_security_ctx= *sctx; + + DBUG_ASSERT(*user->host.str); // guaranteed by the parser + + if (user->host.str == host_not_specified.str) + { + my_error(ER_NO_SUCH_USER, MYF(0), user->user.str, ""); + return 1; + } + + if (!user->user.length) + { + my_error(ER_NO_SUCH_USER, MYF(0), "", user->host.str); + return 1; + } + + mysql_mutex_lock(&acl_cache->lock); + ACL_USER *acl_user= find_user_or_anon(user->host.str, user->user.str, user->host.str); + if (acl_user) + acl_user= acl_user->copy(thd->mem_root); + mysql_mutex_unlock(&acl_cache->lock); + + if (!acl_user) + { + if (!(sctx->master_access & PRIV_SUDO_CHANGE_USER)) + goto access_denied; + my_error(ER_NO_SUCH_USER, MYF(0), user->user.str, user->host.str); + return 1; + } + + if (!(sctx->master_access & PRIV_SUDO_CHANGE_USER) && + (strcmp(user->user.str, sctx->user) || + strcmp(user->host.str, sctx->host_or_ip) || + strcmp(acl_user->user.str, sctx->priv_user) || + strcmp(acl_user->host.hostname, sctx->priv_host))) + goto access_denied; + + thd->change_user(); + thd->clear_error(); // if errors from rollback + my_free(const_cast(thd->db.str)); + thd->reset_db(&null_clex_str); + + sctx->init(); + sctx->user= *user->user.str + ? my_strndup(key_memory_MPVIO_EXT_auth_info, user->user.str, + user->user.length, MYF(MY_WME)) + : NULL; + if (strcmp(user->host.str, my_localhost) == 0) + sctx->host= my_localhost; + else + sctx->host= my_strndup(PSI_INSTRUMENT_ME, user->host.str, + user->host.length, MYF(MY_WME)); + sctx->host_or_ip= sctx->host; + sctx->ip= 0; + + if (set_privs_on_login(thd, acl_user)) + { + sctx->destroy(); + *sctx= save_security_ctx; + return 1; + } + + mysql_audit_notify_connection_change_user(thd, &save_security_ctx); + save_security_ctx.destroy(); + return 0; + +access_denied: + my_error(ER_ACCESS_DENIED_CHANGE_USER_ERROR, MYF(0), + user->user.str, user->host.str); + status_var_increment(thd->status_var.access_denied_errors); + return 1; +} + #endif // NO_EMBEDDED_ACCESS_CHECKS diff --git a/sql/sql_acl.h b/sql/sql_acl.h index 5e3892ed3be..ce22a674002 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -284,6 +284,7 @@ bool acl_check_proxy_grant_access (THD *thd, const LEX_CSTRING &host, const LEX_CSTRING &user, bool with_grant); +int acl_setauthorization(THD *thd, const LEX_USER *user); int acl_setrole(THD *thd, const LEX_CSTRING &rolename, privilege_t access); int acl_check_setrole(THD *thd, const LEX_CSTRING &rolename, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 646da538d5c..115fd4f3b4a 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -693,7 +693,7 @@ THD::THD(my_thread_id id, bool is_wsrep_applier) /* statement id */ 0), rli_fake(0), rgi_fake(0), rgi_slave(NULL), protocol_text(this), protocol_binary(this), initial_status_var(0), - m_current_stage_key(0), m_psi(0), + m_current_stage_key(0), m_psi(0), start_time(0), start_time_sec_part(0), in_sub_stmt(0), log_all_errors(0), binlog_unsafe_warning_flags(0), current_stmt_binlog_format(BINLOG_FORMAT_MIXED), @@ -1357,7 +1357,7 @@ void THD::init() mysql_mutex_unlock(&LOCK_global_system_variables); - user_time.val= start_time= start_time_sec_part= 0; + user_time.val= 0; server_status= 0; if (variables.option_bits & OPTION_AUTOCOMMIT) @@ -1386,6 +1386,7 @@ void THD::init() status_var.max_local_memory_used= status_var.local_memory_used; bzero((char *) &org_status_var, sizeof(org_status_var)); status_in_global= 0; + bytes_sent_old= 0; start_bytes_received= 0; m_last_commit_gtid.seq_no= 0; last_stmt= NULL; @@ -1571,6 +1572,8 @@ void THD::change_user(void) get_stmt_da()->clear_warning_info(0); init(); + /* cannot clear map if it'll free the currently executing statement */ + DBUG_ASSERT(stmt_arena->is_conventional()); stmt_map.reset(); my_hash_init(key_memory_user_var_entry, &user_vars, Lex_ident_user_var::charset_info(), @@ -1580,6 +1583,8 @@ void THD::change_user(void) Lex_ident_fs::charset_info(), SEQUENCES_HASH_SIZE, 0, 0, get_sequence_last_key, free_sequence_last, HASH_THREAD_SPECIFIC); + /* cannot clear caches if it'll free the currently running routine */ + DBUG_ASSERT(!spcont); sp_caches_clear(); statement_rcontext_reinit(); opt_trace.delete_traces(); diff --git a/sql/sql_connect.h b/sql/sql_connect.h index 345d5d4fe92..b456fc68ce9 100644 --- a/sql/sql_connect.h +++ b/sql/sql_connect.h @@ -89,8 +89,11 @@ void reset_mqh(LEX_USER *lu, bool get_them); bool check_mqh(THD *thd, uint check_command); void time_out_user_resource_limits(THD *thd, USER_CONN *uc); #ifndef NO_EMBEDDED_ACCESS_CHECKS +int get_or_create_user_conn(THD *thd, const char *user, + const char *host, const USER_RESOURCES *mqh); void decrease_user_connections(USER_CONN *uc); #else +#define get_or_create_user_conn(A,B,C,D) 0 #define decrease_user_connections(X) do { } while(0) /* nothing */ #endif bool thd_init_client_charset(THD *thd, uint cs_number); @@ -105,8 +108,6 @@ int thd_set_peer_addr(THD *thd, sockaddr_storage *addr, void prepare_new_connection_state(THD* thd); void end_connection(THD *thd); void update_global_user_stats(THD* thd, bool create_user, time_t now); -int get_or_create_user_conn(THD *thd, const char *user, - const char *host, const USER_RESOURCES *mqh); int check_for_max_user_connections(THD *thd, USER_CONN *uc); extern HASH global_user_stats; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 20b3d59ed4a..8c77e8c330d 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -358,9 +358,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); */ %ifdef MARIADB -%expect 62 -%else %expect 63 +%else +%expect 64 %endif /* @@ -764,6 +764,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token AT_SYM /* SQL-2003-R */ %token ATOMIC_SYM /* SQL-2003-R */ %token AUTHORS_SYM +%token AUTHORIZATION_SYM /* SQL-2003-R */ %token AUTO_INC %token AUTO_SYM %token AVG_ROW_LENGTH @@ -16328,6 +16329,7 @@ keyword_verb_clause: keyword_set_special_case: NAMES_SYM + | AUTHORIZATION_SYM | ROLE_SYM | PASSWORD_SYM ; @@ -17000,6 +17002,20 @@ set_param: if (unlikely(sp_create_assignment_instr(thd, yychar == YYEMPTY))) MYSQL_YYABORT; } + | SESSION_SYM AUTHORIZATION_SYM user_name + { + if (Lex->sphead) + { + my_error(ER_SP_BADSTATEMENT, MYF(0), "SET SESSION AUTHORIZATION"); + MYSQL_YYABORT; + } + if (sp_create_assignment_lex(thd, $1.pos())) + MYSQL_YYABORT; + auto var= new (thd->mem_root) set_var_authorization($3); + if (var == NULL || Lex->var_list.push_back(var, thd->mem_root) || + sp_create_assignment_instr(thd, yychar == YYEMPTY)) + MYSQL_YYABORT; + } | option_type { Lex->option_type= $1;