diff --git a/changelogs/unreleased/gh-8781-heap-use-after-free-in-memtx-tx-delete-gap.md b/changelogs/unreleased/gh-8781-heap-use-after-free-in-memtx-tx-delete-gap.md new file mode 100644 index 0000000000000000000000000000000000000000..f3f7a0a0c296583842566ac034e25497d49904dc --- /dev/null +++ b/changelogs/unreleased/gh-8781-heap-use-after-free-in-memtx-tx-delete-gap.md @@ -0,0 +1,5 @@ +## bugfix/memtx + +* Fixed a heap-use-after-free bug in the transaction manager, which could occur + when performing a DDL operation concurrently with a transaction on the same + space (gh-8781). diff --git a/src/box/memtx_tx.c b/src/box/memtx_tx.c index 852705eae5b1e3e65d9da013712c1c22e7f5f071..df34ef32d48842de42b2c1172a996d4791311d40 100644 --- a/src/box/memtx_tx.c +++ b/src/box/memtx_tx.c @@ -946,15 +946,18 @@ memtx_tx_story_new(struct space *space, struct tuple *tuple) return story; } +/** + * Deletes a story. Expects the story to be fully unlinked. + */ static void memtx_tx_story_delete(struct memtx_story *story) { - /* Expecting to delete fully unlinked story. */ assert(story->add_stmt == NULL); assert(story->del_stmt == NULL); for (uint32_t i = 0; i < story->index_count; i++) { assert(story->link[i].newer_story == NULL); assert(story->link[i].older_story == NULL); + assert(rlist_empty(&story->link[i].read_gaps)); } memtx_tx_stats_discard(&txm.story_stats[story->status], @@ -2943,6 +2946,16 @@ memtx_tx_on_space_delete(struct space *space) if (story->del_stmt != NULL) memtx_tx_history_remove_stmt(story->del_stmt); memtx_tx_story_full_unlink_on_space_delete(story); + for (uint32_t i = 0; i < story->index_count; i++) { + struct rlist *read_gaps = &story->link[i].read_gaps; + while (!rlist_empty(&story->link[i].read_gaps)) { + struct gap_item_base *item = + rlist_first_entry(read_gaps, + struct gap_item_base, + in_read_gaps); + memtx_tx_delete_gap(item); + } + } memtx_tx_story_delete(story); } }