Commit df07ea0b authored by sjaakola's avatar sjaakola Committed by Jan Lindström

MDEV-23557 Galera heap-buffer-overflow in wsrep_rec_get_foreign_key

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.
parent 5843dc48
#
# 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;
#
# 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;
......@@ -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;
......
--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;
......@@ -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);
......
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