From e120d8fd28dd66a47b15ea2b7e80fb63e448c49c Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Thu, 12 Feb 2026 13:10:41 +0530 Subject: [PATCH] MDEV-19194 ASAN use-after-poison in fk_prepare_copy_alter_table upon dropping FK Problem: ======== The issue occurs when ALTER TABLE contains duplicate DROP FOREIGN KEY operations (e.g., "DROP FOREIGN KEY f1, DROP FOREIGN KEY f1"). In fk_prepare_copy_alter_table(), Removes from the list all foreign keys which are to be dropped. But iterator continues to accessing the removed entry when it encounters duplicate foreign key. Solution: ========= mysql_prepare_alter_table(): Add duplicate detection logic to handle cases where the same foreign key is specified multiple times in a single ALTER TABLE statement's DROP clause fk_prepare_copy_alter_table(): Handles both parent foreign keys, child foreign keys. This prevents iterator invalidation while maintaining the same functional behavior for foreign key constraint removal during ALTER TABLE operations. This patch does the following: - Iterate through foreign key lists and collect FOREIGN_KEY_INFO pointers to be removed in a separate keys_to_remove list - For each collected key, rewind the original iterator, find the matching key by pointer comparison, and remove it safely. --- mysql-test/suite/innodb/r/foreign_key.result | 25 ++++++++++ mysql-test/suite/innodb/t/foreign_key.test | 19 +++++++ sql/sql_table.cc | 52 ++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/mysql-test/suite/innodb/r/foreign_key.result b/mysql-test/suite/innodb/r/foreign_key.result index 236953795e673..ab9e7ec924fab 100644 --- a/mysql-test/suite/innodb/r/foreign_key.result +++ b/mysql-test/suite/innodb/r/foreign_key.result @@ -258,6 +258,7 @@ CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; CREATE TABLE t2 (b INT PRIMARY KEY, FOREIGN KEY fk1 (b) REFERENCES t1 (a)) ENGINE=InnoDB; ALTER TABLE t2 DROP FOREIGN KEY fk1, DROP FOREIGN KEY fk1; +ERROR 42000: Can't DROP FOREIGN KEY `fk1`; check that it exists DROP TABLE t2, t1; CREATE TABLE t1 (f VARCHAR(256)) ENGINE=InnoDB; SET SESSION FOREIGN_KEY_CHECKS = OFF; @@ -1200,4 +1201,28 @@ set GLOBAL innodb_fast_shutdown=0; # restart ALTER TABLE t2 FORCE; DROP TABLE t2, t1, t3; +# +# MDEV-19194 ASAN use-after-poison in +# fk_prepare_copy_alter_table upon dropping FK +# +CREATE TABLE t1(f1 INT NOT NULL, KEY(f1))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, +FOREIGN KEY `f1`(f1) REFERENCES t1(f1))ENGINE=InnoDB; +ALTER TABLE t2 DROP FOREIGN KEY f1, DROP FOREIGN KEY f1, ALGORITHM=COPY; +ERROR 42000: Can't DROP FOREIGN KEY `f1`; check that it exists +ALTER TABLE t2 DROP FOREIGN KEY f1, DROP FOREIGN KEY IF EXISTS f1, ALGORITHM=COPY; +Warnings: +Note 1091 Can't DROP FOREIGN KEY `f1`; check that it exists +ALTER TABLE t2 DROP FOREIGN KEY f1, ALGORITHM=COPY; +ERROR 42000: Can't DROP FOREIGN KEY `f1`; check that it exists +ALTER TABLE t2 DROP FOREIGN KEY IF EXISTS f1, ALGORITHM=COPY; +Warnings: +Note 1091 Can't DROP FOREIGN KEY `f1`; check that it exists +ALTER TABLE t2 DROP FOREIGN KEY IF EXISTS f1, DROP FOREIGN KEY f1, ALGORITHM=COPY; +ERROR 42000: Can't DROP FOREIGN KEY `f1`; check that it exists +ALTER TABLE t2 DROP FOREIGN KEY IF EXISTS f1, DROP FOREIGN KEY IF EXISTS f1, ALGORITHM=COPY; +Warnings: +Note 1091 Can't DROP FOREIGN KEY `f1`; check that it exists +Note 1091 Can't DROP FOREIGN KEY `f1`; check that it exists +DROP TABLE t2, t1; # End of 10.6 tests diff --git a/mysql-test/suite/innodb/t/foreign_key.test b/mysql-test/suite/innodb/t/foreign_key.test index e7761f3cf4bde..f52dacbb949c0 100644 --- a/mysql-test/suite/innodb/t/foreign_key.test +++ b/mysql-test/suite/innodb/t/foreign_key.test @@ -256,6 +256,7 @@ DROP TABLE t1; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; CREATE TABLE t2 (b INT PRIMARY KEY, FOREIGN KEY fk1 (b) REFERENCES t1 (a)) ENGINE=InnoDB; +--error ER_CANT_DROP_FIELD_OR_KEY ALTER TABLE t2 DROP FOREIGN KEY fk1, DROP FOREIGN KEY fk1; DROP TABLE t2, t1; @@ -1271,4 +1272,22 @@ set GLOBAL innodb_fast_shutdown=0; ALTER TABLE t2 FORCE; DROP TABLE t2, t1, t3; +--echo # +--echo # MDEV-19194 ASAN use-after-poison in +--echo # fk_prepare_copy_alter_table upon dropping FK +--echo # +CREATE TABLE t1(f1 INT NOT NULL, KEY(f1))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, + FOREIGN KEY `f1`(f1) REFERENCES t1(f1))ENGINE=InnoDB; +--error ER_CANT_DROP_FIELD_OR_KEY +ALTER TABLE t2 DROP FOREIGN KEY f1, DROP FOREIGN KEY f1, ALGORITHM=COPY; +ALTER TABLE t2 DROP FOREIGN KEY f1, DROP FOREIGN KEY IF EXISTS f1, ALGORITHM=COPY; +--error ER_CANT_DROP_FIELD_OR_KEY +ALTER TABLE t2 DROP FOREIGN KEY f1, ALGORITHM=COPY; +ALTER TABLE t2 DROP FOREIGN KEY IF EXISTS f1, ALGORITHM=COPY; +--error ER_CANT_DROP_FIELD_OR_KEY +ALTER TABLE t2 DROP FOREIGN KEY IF EXISTS f1, DROP FOREIGN KEY f1, ALGORITHM=COPY; +ALTER TABLE t2 DROP FOREIGN KEY IF EXISTS f1, DROP FOREIGN KEY IF EXISTS f1, ALGORITHM=COPY; +DROP TABLE t2, t1; + --echo # End of 10.6 tests diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 549be4faf660b..26e13c9200778 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -9012,6 +9012,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, { Alter_drop *drop; drop_it.rewind(); + List dropped_fk; while ((drop=drop_it++)) { switch (drop->type) { case Alter_drop::KEY: @@ -9041,7 +9042,18 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, { if (my_strcasecmp(system_charset_info, f_key->foreign_id->str, drop->name) == 0) + { + List_iterator check_it(dropped_fk); + LEX_CSTRING *dropped_name; + while ((dropped_name = check_it++)) + { + if (my_strcasecmp(system_charset_info, dropped_name->str, + f_key->foreign_id->str) == 0) + goto fk_not_found; + } + dropped_fk.push_back(f_key->foreign_id); goto fk_found; + } } goto fk_not_found; fk_found: @@ -9336,6 +9348,7 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, when a foreign key has the same table as child and parent. */ List_iterator fk_parent_key_it(fk_parent_key_list); + List keys_to_remove; while ((f_key= fk_parent_key_it++)) { @@ -9358,7 +9371,26 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, &table->s->db) == 0) && (lex_string_cmp(table_alias_charset, f_key->foreign_table, &table->s->table_name) == 0)) + { + keys_to_remove.push_back(f_key); + break; + } + } + } + + /* Remove the identified keys */ + List_iterator remove_it(keys_to_remove); + while ((f_key = remove_it++)) + { + fk_parent_key_it.rewind(); + FOREIGN_KEY_INFO *fk; + while ((fk= fk_parent_key_it++)) + { + if (fk == f_key) + { fk_parent_key_it.remove(); + break; + } } } @@ -9432,6 +9464,7 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, by this ALTER TABLE. */ List_iterator fk_key_it(fk_child_key_list); + keys_to_remove.empty(); while ((f_key= fk_key_it++)) { @@ -9444,7 +9477,26 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, if ((drop->type == Alter_drop::FOREIGN_KEY) && (my_strcasecmp(system_charset_info, f_key->foreign_id->str, drop->name) == 0)) + { + keys_to_remove.push_back(f_key); + break; + } + } + } + + /* Remove the identified keys */ + List_iterator remove_it2(keys_to_remove); + while ((f_key = remove_it2++)) + { + FOREIGN_KEY_INFO *fk; + fk_key_it.rewind(); + while ((fk= fk_key_it++)) + { + if (fk == f_key) + { fk_key_it.remove(); + break; + } } }