summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsjaakola <seppo.jaakola@iki.fi>2020-08-27 10:52:39 +0300
committerJan Lindström <jan.lindstrom@mariadb.com>2020-08-28 13:13:35 +0300
commitdf07ea0b27f891c69e60b75869f474cd03232216 (patch)
tree898ea06f0667970a0561d09533150392fc717e94
parent5843dc485bb894e5d3fde2bb6bb7f473c8f78159 (diff)
downloadmariadb-git-df07ea0b27f891c69e60b75869f474cd03232216.tar.gz
MDEV-23557 Galera heap-buffer-overflow in wsrep_rec_get_foreign_keybb-10.2-MDEV-23557
This commit contains a fix and extended test case for a ASAN failure reported during galera.fk mtr testing. The reported heap buffer overflow happens in test case where a cascading foreign key constraint is defined for a column of varchar type, and galera.fk.test has such vulnerable test scenario. Troubleshoting revealed that erlier fix for MDEV-19660 has made a fix for cascading delete handling to append wsrep keys from pcur->old_rec, in row_ins_foreign_check_on_constraint(). And, the ASAN failuer comes from later scanning of this old_rec reference. The fix in this commit, moves the call for wsrep_append_foreign_key() to happen somewhat earlier, and inside ongoing mtr, and using clust_rec which is set earlier in the same mtr for both update and delete cascade operations. for wsrep key populating, it does not matter when the keys are populated, all keys just have to be appended before wsrep transaction replicates. Note that I also tried similar fix for earlier wsrep key append, but using the old implementation with pcur->old_rec (instead of clust_rec), and same ASAN failure was reported. So it appears that pcur->old_rec is not properly set, to be used for wsrep key appending. galera.galera_fk_cascade_delete test has been extended by two new test scenarios: * FK cascade on varchar column. This test case reproduces same scenario as galera.fk, and this test scenario will also trigger ASAN failure with non fixed MariaDB versions. * multi-master conflict with FK cascading. this scenario causes a conflict between a replicated FK cascading transaction and local transaction trying to modify the cascaded child table row. Local transaction should be aborted and get deadlock error. This test scenario is passing both with old MariaDB version and with this commit as well.
-rw-r--r--mysql-test/suite/galera/r/galera_fk_cascade_delete.result18
-rw-r--r--mysql-test/suite/galera/r/galera_fk_cascade_delete_debug.result67
-rw-r--r--mysql-test/suite/galera/t/galera_fk_cascade_delete.test18
-rw-r--r--mysql-test/suite/galera/t/galera_fk_cascade_delete_debug.test98
-rw-r--r--storage/innobase/row/row0ins.cc16
5 files changed, 200 insertions, 17 deletions
diff --git a/mysql-test/suite/galera/r/galera_fk_cascade_delete.result b/mysql-test/suite/galera/r/galera_fk_cascade_delete.result
index 73375ae55c5..a6c6504dc39 100644
--- a/mysql-test/suite/galera/r/galera_fk_cascade_delete.result
+++ b/mysql-test/suite/galera/r/galera_fk_cascade_delete.result
@@ -1,3 +1,8 @@
+#
+# test phase with cascading foreign key through 3 tables
+#
+connection node_1;
+set wsrep_sync_wait=0;
CREATE TABLE grandparent (
id INT NOT NULL PRIMARY KEY
) ENGINE=InnoDB;
@@ -19,14 +24,15 @@ INSERT INTO grandparent VALUES (1),(2);
INSERT INTO parent VALUES (1,1), (2,2);
INSERT INTO child VALUES (1,1), (2,2);
connection node_2;
+set wsrep_sync_wait=0;
DELETE FROM grandparent WHERE id = 1;
connection node_1;
-SELECT COUNT(*) = 0 FROM parent WHERE grandparent_id = 1;
-COUNT(*) = 0
-1
-SELECT COUNT(*) = 0 FROM child WHERE parent_id = 1;
-COUNT(*) = 0
-1
+SELECT COUNT(*), COUNT(*) = 0 FROM parent WHERE grandparent_id = 1;
+COUNT(*) COUNT(*) = 0
+0 1
+SELECT COUNT(*), COUNT(*) = 0 FROM child WHERE parent_id = 1;
+COUNT(*) COUNT(*) = 0
+0 1
DROP TABLE child;
DROP TABLE parent;
DROP TABLE grandparent;
diff --git a/mysql-test/suite/galera/r/galera_fk_cascade_delete_debug.result b/mysql-test/suite/galera/r/galera_fk_cascade_delete_debug.result
new file mode 100644
index 00000000000..89613b2856a
--- /dev/null
+++ b/mysql-test/suite/galera/r/galera_fk_cascade_delete_debug.result
@@ -0,0 +1,67 @@
+#
+# test phase with foreign key of varchar type
+#
+connection node_1;
+CREATE TABLE parent (
+`id` varchar(36) COLLATE utf8_unicode_ci NOT NULL,
+PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+CREATE TABLE child (
+`id` int NOT NULL,
+`parent_id` varchar(36) COLLATE utf8_unicode_ci DEFAULT NULL,
+PRIMARY KEY (`id`),
+KEY `parent_id` (`parent_id`),
+CONSTRAINT `ipallocations_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+INSERT INTO parent VALUES ('row one'), ('row two');
+INSERT INTO child VALUES (1,'row one'), (2,'row two');
+connection node_2;
+DELETE FROM parent;
+connection node_1;
+SELECT COUNT(*), COUNT(*) = 0 FROM parent;
+COUNT(*) COUNT(*) = 0
+0 1
+SELECT COUNT(*), COUNT(*) = 0 FROM child;
+COUNT(*) COUNT(*) = 0
+0 1
+DROP TABLE child;
+DROP TABLE parent;
+#
+# test phase with MM conflict in FK cascade
+#
+connection node_1;
+set wsrep_retry_autocommit=0;
+CREATE TABLE parent (
+id INT NOT NULL PRIMARY KEY
+) ENGINE=InnoDB;
+CREATE TABLE child (
+id INT NOT NULL PRIMARY KEY,
+j int default 0,
+parent_id INT,
+FOREIGN KEY (parent_id)
+REFERENCES parent(id)
+ON DELETE CASCADE
+) ENGINE=InnoDB;
+INSERT INTO parent VALUES (1);
+INSERT INTO child VALUES (1,0,1);
+connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1;
+SET GLOBAL debug_dbug = "d,sync.wsrep_apply_cb";
+connection node_2;
+DELETE FROM parent;
+connection node_1a;
+SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached";
+connection node_1;
+update child set j=2;;
+connection node_1a;
+SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb";
+SET GLOBAL debug_dbug = "";
+SET DEBUG_SYNC = "RESET";
+connection node_1;
+SELECT COUNT(*), COUNT(*) = 0 FROM parent;
+COUNT(*) COUNT(*) = 0
+0 1
+SELECT COUNT(*), COUNT(*) = 0 FROM child;
+COUNT(*) COUNT(*) = 0
+0 1
+DROP TABLE child;
+DROP TABLE parent;
diff --git a/mysql-test/suite/galera/t/galera_fk_cascade_delete.test b/mysql-test/suite/galera/t/galera_fk_cascade_delete.test
index 9b79b4c30b6..6f0de0a1f4a 100644
--- a/mysql-test/suite/galera/t/galera_fk_cascade_delete.test
+++ b/mysql-test/suite/galera/t/galera_fk_cascade_delete.test
@@ -3,7 +3,13 @@
#
--source include/galera_cluster.inc
---source include/have_innodb.inc
+
+--echo #
+--echo # test phase with cascading foreign key through 3 tables
+--echo #
+
+--connection node_1
+set wsrep_sync_wait=0;
CREATE TABLE grandparent (
id INT NOT NULL PRIMARY KEY
@@ -30,11 +36,17 @@ INSERT INTO parent VALUES (1,1), (2,2);
INSERT INTO child VALUES (1,1), (2,2);
--connection node_2
+set wsrep_sync_wait=0;
+
+--let $wait_condition = SELECT COUNT(*) = 2 FROM child;
+--source include/wait_condition.inc
DELETE FROM grandparent WHERE id = 1;
--connection node_1
-SELECT COUNT(*) = 0 FROM parent WHERE grandparent_id = 1;
-SELECT COUNT(*) = 0 FROM child WHERE parent_id = 1;
+--let $wait_condition = SELECT COUNT(*) = 1 FROM child;
+--source include/wait_condition.inc
+SELECT COUNT(*), COUNT(*) = 0 FROM parent WHERE grandparent_id = 1;
+SELECT COUNT(*), COUNT(*) = 0 FROM child WHERE parent_id = 1;
DROP TABLE child;
DROP TABLE parent;
diff --git a/mysql-test/suite/galera/t/galera_fk_cascade_delete_debug.test b/mysql-test/suite/galera/t/galera_fk_cascade_delete_debug.test
new file mode 100644
index 00000000000..f38c028b7d6
--- /dev/null
+++ b/mysql-test/suite/galera/t/galera_fk_cascade_delete_debug.test
@@ -0,0 +1,98 @@
+--source include/galera_cluster.inc
+--source include/have_debug.inc
+--source include/have_debug_sync.inc
+
+--echo #
+--echo # test phase with foreign key of varchar type
+--echo #
+--connection node_1
+ CREATE TABLE parent (
+ `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL,
+ PRIMARY KEY (`id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+
+ CREATE TABLE child (
+ `id` int NOT NULL,
+ `parent_id` varchar(36) COLLATE utf8_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `parent_id` (`parent_id`),
+ CONSTRAINT `ipallocations_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+
+INSERT INTO parent VALUES ('row one'), ('row two');
+INSERT INTO child VALUES (1,'row one'), (2,'row two');
+
+--connection node_2
+--let $wait_condition = SELECT COUNT(*) = 2 FROM child;
+--source include/wait_condition.inc
+DELETE FROM parent;
+
+--connection node_1
+--let $wait_condition = SELECT COUNT(*) = 0 FROM child;
+--source include/wait_condition.inc
+
+SELECT COUNT(*), COUNT(*) = 0 FROM parent;
+SELECT COUNT(*), COUNT(*) = 0 FROM child;
+
+DROP TABLE child;
+DROP TABLE parent;
+
+--echo #
+--echo # test phase with MM conflict in FK cascade
+--echo #
+
+--connection node_1
+set wsrep_retry_autocommit=0;
+CREATE TABLE parent (
+ id INT NOT NULL PRIMARY KEY
+) ENGINE=InnoDB;
+
+CREATE TABLE child (
+ id INT NOT NULL PRIMARY KEY,
+ j int default 0,
+ parent_id INT,
+ FOREIGN KEY (parent_id)
+ REFERENCES parent(id)
+ ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+INSERT INTO parent VALUES (1);
+INSERT INTO child VALUES (1,0,1);
+
+# block applier before applying
+--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1
+SET GLOBAL debug_dbug = "d,sync.wsrep_apply_cb";
+
+--connection node_2
+--let $wait_condition = SELECT COUNT(*) = 1 FROM child;
+--source include/wait_condition.inc
+DELETE FROM parent;
+
+--connection node_1a
+# wait until applier has reached the sync point
+SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached";
+
+
+--connection node_1
+# issue conflicting write to child table, it should fail in certification
+--error ER_LOCK_DEADLOCK
+--send update child set j=2;
+
+--connection node_1a
+# release the applier
+SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb";
+SET GLOBAL debug_dbug = "";
+SET DEBUG_SYNC = "RESET";
+
+--connection node_1
+--reap
+
+--let $wait_condition = SELECT COUNT(*) = 0 FROM child;
+--source include/wait_condition.inc
+SELECT COUNT(*), COUNT(*) = 0 FROM parent;
+SELECT COUNT(*), COUNT(*) = 0 FROM child;
+
+DROP TABLE child;
+DROP TABLE parent;
diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc
index 50394193a15..c0aa3655b7b 100644
--- a/storage/innobase/row/row0ins.cc
+++ b/storage/innobase/row/row0ins.cc
@@ -1402,20 +1402,20 @@ row_ins_foreign_check_on_constraint(
btr_pcur_store_position(cascade->pcur, mtr);
}
+#ifdef WITH_WSREP
+ err = wsrep_append_foreign_key(trx, foreign, clust_rec, clust_index,
+ FALSE, WSREP_KEY_EXCLUSIVE);
+ if (err != DB_SUCCESS) {
+ ib::info() << "WSREP: foreign key append failed: " << err;
+ goto nonstandard_exit_func;
+ }
+#endif /* WITH_WSREP */
mtr_commit(mtr);
ut_a(cascade->pcur->rel_pos == BTR_PCUR_ON);
cascade->state = UPD_NODE_UPDATE_CLUSTERED;
-#ifdef WITH_WSREP
- err = wsrep_append_foreign_key(trx, foreign, cascade->pcur->old_rec, clust_index,
- FALSE, WSREP_KEY_EXCLUSIVE);
- if (err != DB_SUCCESS) {
- fprintf(stderr,
- "WSREP: foreign key append failed: %d\n", err);
- } else
-#endif /* WITH_WSREP */
err = row_update_cascade_for_mysql(thr, cascade,
foreign->foreign_table);