Skip to content

Commit c2d44ec

Browse files
committed
MDEV-29894: Calling a function from a different database in a slave side trigger crashes
When opening and locking tables, if triggers will be invoked in a separate database, thd->set_db() is invoked, thus freeeing the memory and headers which thd->db had previously pointed to. In row based replication, the event execution logic initializes thd->db to point to the database which the event targets, which is owned by the corresponding table share (introduced in d9898c9 for MDEV-7409). The problem then, is that during the table opening and locking process for a row event, memory which belongs to the table share would be freed, which is not valid. This patch replaces the thd->reset_db() calls to thd->set_db(), which copies-by-value, rather than by reference. Then when the memory is freed, our copy of memory is freed, rather than memory which belongs to a table share. Notes: 1. The call to change thd->db now happens on a higher-level, in Rows_log_event::do_apply_event() rather than ::do_exec_row(), in the call stack. This is because do_exec_row() is called within a loop, and each invocation would redundantly set and unset the db to the same value. 2. thd->set_db() is only used if triggers are to be invoked, as there is no vulnerability in the non-trigger case, and copying memory would be an unnecessary inefficiency. Reviewed By: ============ Andrei Elkin <andrei.elkin@mariadb.com>
1 parent 8171f9d commit c2d44ec

File tree

4 files changed

+204
-10
lines changed

4 files changed

+204
-10
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
include/master-slave.inc
2+
[connection master]
3+
connection slave;
4+
set global slave_run_triggers_for_rbr=1;
5+
connection master;
6+
CREATE TABLE t1 (a int);
7+
connection slave;
8+
connection slave;
9+
CREATE DATABASE db2;
10+
CREATE FUNCTION db2.get_value(a INT) RETURNS int(2) RETURN 0;
11+
#
12+
# Test Insert_rows_log_event
13+
connection slave;
14+
CREATE TRIGGER tr_ins BEFORE INSERT ON t1 FOR EACH ROW BEGIN
15+
DECLARE a INT;
16+
SET a = db2.get_value(1);
17+
END//
18+
connection master;
19+
INSERT INTO t1 VALUES (1);
20+
connection slave;
21+
connection slave;
22+
DROP TRIGGER tr_ins;
23+
#
24+
# Test Update_rows_log_event
25+
connection master;
26+
INSERT INTO t1 VALUES (5);
27+
connection slave;
28+
connection slave;
29+
CREATE TRIGGER tr_upd BEFORE UPDATE ON t1 FOR EACH ROW BEGIN
30+
DECLARE a INT;
31+
SET a = db2.get_value(1);
32+
END//
33+
connection master;
34+
UPDATE t1 SET a=a+1 WHERE a=5;
35+
connection slave;
36+
connection slave;
37+
DROP TRIGGER tr_upd;
38+
#
39+
# Test Delete_rows_log_event
40+
connection master;
41+
INSERT INTO t1 VALUES (7);
42+
connection slave;
43+
connection slave;
44+
CREATE TRIGGER tr_del BEFORE DELETE ON t1 FOR EACH ROW BEGIN
45+
DECLARE a INT;
46+
SET a = db2.get_value(1);
47+
END//
48+
connection master;
49+
DELETE FROM t1 WHERE a=7;
50+
connection slave;
51+
connection slave;
52+
DROP TRIGGER tr_del;
53+
#
54+
# Cleanup
55+
connection slave;
56+
SET GLOBAL slave_run_triggers_for_rbr=NO;
57+
DROP DATABASE db2;
58+
connection master;
59+
DROP TABLE t1;
60+
include/rpl_end.inc
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#
2+
# This test ensures that a table share's database name is not freed when
3+
# using row based replication with triggers that open different databases
4+
#
5+
#
6+
# References:
7+
# MDEV-29894: Calling a function from a different database in a slave side
8+
# trigger crashes
9+
#
10+
--source include/master-slave.inc
11+
--source include/have_binlog_format_row.inc
12+
13+
--connection slave
14+
--let $old_slave_run_triggers= `SELECT @@global.slave_run_triggers_for_rbr`
15+
set global slave_run_triggers_for_rbr=1;
16+
17+
--connection master
18+
CREATE TABLE t1 (a int);
19+
--sync_slave_with_master
20+
21+
--connection slave
22+
CREATE DATABASE db2;
23+
CREATE FUNCTION db2.get_value(a INT) RETURNS int(2) RETURN 0;
24+
25+
--echo #
26+
--echo # Test Insert_rows_log_event
27+
28+
--connection slave
29+
DELIMITER //;
30+
CREATE TRIGGER tr_ins BEFORE INSERT ON t1 FOR EACH ROW BEGIN
31+
DECLARE a INT;
32+
SET a = db2.get_value(1);
33+
END//
34+
DELIMITER ;//
35+
36+
--connection master
37+
INSERT INTO t1 VALUES (1);
38+
--sync_slave_with_master
39+
40+
--connection slave
41+
DROP TRIGGER tr_ins;
42+
43+
44+
--echo #
45+
--echo # Test Update_rows_log_event
46+
--connection master
47+
--let $row_val=5
48+
--eval INSERT INTO t1 VALUES ($row_val)
49+
--sync_slave_with_master
50+
51+
--connection slave
52+
DELIMITER //;
53+
CREATE TRIGGER tr_upd BEFORE UPDATE ON t1 FOR EACH ROW BEGIN
54+
DECLARE a INT;
55+
SET a = db2.get_value(1);
56+
END//
57+
DELIMITER ;//
58+
59+
--connection master
60+
--eval UPDATE t1 SET a=a+1 WHERE a=$row_val
61+
--sync_slave_with_master
62+
63+
--connection slave
64+
DROP TRIGGER tr_upd;
65+
66+
67+
--echo #
68+
--echo # Test Delete_rows_log_event
69+
--connection master
70+
--let $row_val=7
71+
--eval INSERT INTO t1 VALUES ($row_val)
72+
--sync_slave_with_master
73+
74+
--connection slave
75+
DELIMITER //;
76+
CREATE TRIGGER tr_del BEFORE DELETE ON t1 FOR EACH ROW BEGIN
77+
DECLARE a INT;
78+
SET a = db2.get_value(1);
79+
END//
80+
DELIMITER ;//
81+
82+
--connection master
83+
--eval DELETE FROM t1 WHERE a=$row_val
84+
--sync_slave_with_master
85+
86+
--connection slave
87+
DROP TRIGGER tr_del;
88+
89+
90+
--echo #
91+
--echo # Cleanup
92+
--connection slave
93+
--eval SET GLOBAL slave_run_triggers_for_rbr=$old_slave_run_triggers
94+
DROP DATABASE db2;
95+
--connection master
96+
DROP TABLE t1;
97+
98+
--source include/rpl_end.inc

sql/log_event.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5188,6 +5188,51 @@ class Rows_log_event : public Log_event
51885188
uint m_key_nr; /* Key number */
51895189
bool master_had_triggers; /* set after tables opening */
51905190

5191+
/*
5192+
RAII helper class to automatically handle the override/restore of thd->db
5193+
when applying row events, so it will be visible in SHOW PROCESSLIST.
5194+
5195+
If triggers will be invoked, their logic frees the current thread's db,
5196+
so we use set_db() to use a copy of the table share's database.
5197+
5198+
If not using triggers, the db is never freed, and we can reference the
5199+
same memory owned by the table share.
5200+
*/
5201+
class Db_restore_ctx
5202+
{
5203+
private:
5204+
THD *thd;
5205+
LEX_CSTRING restore_db;
5206+
bool db_copied;
5207+
5208+
Db_restore_ctx(Rows_log_event *rev)
5209+
: thd(rev->thd), restore_db(rev->thd->db)
5210+
{
5211+
TABLE *table= rev->m_table;
5212+
5213+
if (table->triggers && rev->do_invoke_trigger())
5214+
{
5215+
thd->reset_db(&null_clex_str);
5216+
thd->set_db(&table->s->db);
5217+
db_copied= true;
5218+
}
5219+
else
5220+
{
5221+
thd->reset_db(&table->s->db);
5222+
db_copied= false;
5223+
}
5224+
}
5225+
5226+
~Db_restore_ctx()
5227+
{
5228+
if (db_copied)
5229+
thd->set_db(&null_clex_str);
5230+
thd->reset_db(&restore_db);
5231+
}
5232+
5233+
friend class Rows_log_event;
5234+
};
5235+
51915236
int find_key(); // Find a best key to use in find_row()
51925237
int find_row(rpl_group_info *);
51935238
int write_row(rpl_group_info *, const bool);

sql/log_event_server.cc

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5676,6 +5676,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
56765676
" (master had triggers)" : ""));
56775677
if (table)
56785678
{
5679+
Rows_log_event::Db_restore_ctx restore_ctx(this);
56795680
master_had_triggers= table->master_had_triggers;
56805681
bool transactional_table= table->file->has_transactions_and_rollback();
56815682
table->file->prepare_for_insert(get_general_type_code() != WRITE_ROWS_EVENT);
@@ -7601,15 +7602,13 @@ Write_rows_log_event::do_exec_row(rpl_group_info *rgi)
76017602
{
76027603
DBUG_ASSERT(m_table != NULL);
76037604
const char *tmp= thd->get_proc_info();
7604-
LEX_CSTRING tmp_db= thd->db;
76057605
char *message, msg[128];
76067606
const LEX_CSTRING &table_name= m_table->s->table_name;
76077607
const char quote_char=
76087608
get_quote_char_for_identifier(thd, table_name.str, table_name.length);
76097609
my_snprintf(msg, sizeof msg,
76107610
"Write_rows_log_event::write_row() on table %c%.*s%c",
76117611
quote_char, int(table_name.length), table_name.str, quote_char);
7612-
thd->reset_db(&m_table->s->db);
76137612
message= msg;
76147613
int error;
76157614

@@ -7631,7 +7630,6 @@ Write_rows_log_event::do_exec_row(rpl_group_info *rgi)
76317630
my_error(ER_UNKNOWN_ERROR, MYF(0));
76327631
}
76337632

7634-
thd->reset_db(&tmp_db);
76357633
return error;
76367634
}
76377635

@@ -8237,15 +8235,13 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
82378235
{
82388236
int error;
82398237
const char *tmp= thd->get_proc_info();
8240-
LEX_CSTRING tmp_db= thd->db;
82418238
char *message, msg[128];
82428239
const LEX_CSTRING &table_name= m_table->s->table_name;
82438240
const char quote_char=
82448241
get_quote_char_for_identifier(thd, table_name.str, table_name.length);
82458242
my_snprintf(msg, sizeof msg,
82468243
"Delete_rows_log_event::find_row() on table %c%.*s%c",
82478244
quote_char, int(table_name.length), table_name.str, quote_char);
8248-
thd->reset_db(&m_table->s->db);
82498245
message= msg;
82508246
const bool invoke_triggers= (m_table->triggers && do_invoke_trigger());
82518247
DBUG_ASSERT(m_table != NULL);
@@ -8305,7 +8301,6 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
83058301
error= HA_ERR_GENERIC; // in case if error is not set yet
83068302
m_table->file->ha_index_or_rnd_end();
83078303
}
8308-
thd->reset_db(&tmp_db);
83098304
thd_proc_info(thd, tmp);
83108305
return error;
83118306
}
@@ -8406,15 +8401,13 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
84068401
const bool invoke_triggers= (m_table->triggers && do_invoke_trigger());
84078402
const char *tmp= thd->get_proc_info();
84088403
DBUG_ASSERT(m_table != NULL);
8409-
LEX_CSTRING tmp_db= thd->db;
84108404
char *message, msg[128];
84118405
const LEX_CSTRING &table_name= m_table->s->table_name;
84128406
const char quote_char=
84138407
get_quote_char_for_identifier(thd, table_name.str, table_name.length);
84148408
my_snprintf(msg, sizeof msg,
84158409
"Update_rows_log_event::find_row() on table %c%.*s%c",
84168410
quote_char, int(table_name.length), table_name.str, quote_char);
8417-
thd->reset_db(&m_table->s->db);
84188411
message= msg;
84198412

84208413
#ifdef WSREP_PROC_INFO
@@ -8443,7 +8436,6 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
84438436
if ((m_curr_row= m_curr_row_end))
84448437
unpack_current_row(rgi, &m_cols_ai);
84458438
thd_proc_info(thd, tmp);
8446-
thd->reset_db(&tmp_db);
84478439
return error;
84488440
}
84498441

@@ -8532,7 +8524,6 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
85328524

85338525
err:
85348526
thd_proc_info(thd, tmp);
8535-
thd->reset_db(&tmp_db);
85368527
m_table->file->ha_index_or_rnd_end();
85378528
return error;
85388529
}

0 commit comments

Comments
 (0)