diff --git a/src/box/memtx_tx.c b/src/box/memtx_tx.c index 52963c49efc6592c9a228846d25ac9a2b54df73f..f9e32cbc113527c70320ef858269f1e907930802 100644 --- a/src/box/memtx_tx.c +++ b/src/box/memtx_tx.c @@ -956,6 +956,7 @@ memtx_tx_story_delete(struct memtx_story *story) { assert(story->add_stmt == NULL); assert(story->del_stmt == NULL); + assert(rlist_empty(&story->reader_list)); for (uint32_t i = 0; i < story->index_count; i++) { assert(story->link[i].newer_story == NULL); assert(story->link[i].older_story == NULL); @@ -2991,6 +2992,18 @@ memtx_tx_on_space_delete(struct space *space) memtx_tx_delete_gap(item); } } + /* + * Remove all read trackers since they point to the story that + * is going to be deleted. + */ + while (!rlist_empty(&story->reader_list)) { + struct tx_read_tracker *tracker = + rlist_first_entry(&story->reader_list, + struct tx_read_tracker, + in_reader_list); + rlist_del(&tracker->in_reader_list); + rlist_del(&tracker->in_read_set); + } memtx_tx_story_delete(story); } } diff --git a/test/box-luatest/mvcc_ddl_test.lua b/test/box-luatest/mvcc_ddl_test.lua index 24fd45c7fc41078e6c822ff77e89f2a57151dbfa..0b6b1080fc8e517ae84191c30e923812f3480566 100644 --- a/test/box-luatest/mvcc_ddl_test.lua +++ b/test/box-luatest/mvcc_ddl_test.lua @@ -86,3 +86,54 @@ g.test_background_build = function(cg) box.internal.memtx_tx_gc(1000) end) end + +-- The test covers a crash when transaction that is being deleted removes +-- itself from reader list of a deleted story that leads to use-after-free +g.test_reader_list_use_after_free = function(cg) + cg.server:exec(function() + local txn_proxy = require("test.box.lua.txn_proxy") + + -- Create space with tuples + local s = box.schema.space.create('test') + s:create_index('pk') + + box.begin() + for i = 1, 10000 do + s:replace{i} + end + box.commit() + + -- Create a transaction that reads every tuple so it's + -- inserted to reader list of every story + local tx = txn_proxy.new() + tx:begin() + for i = 1, 10000 do + tx('box.space.test:get{' .. i .. '}') + end + + -- Create a new index + -- Firstly, we need it so that all the stories will be deleted + -- due to DDL + -- Secondly, we need to create a new index so that layout of stories + -- will be changed and use-after-free on rlist link will trash another + -- field (for example, pointer to tuple) and that's will definitely lead + -- to crash + box.space.test:create_index('sk') + + -- Open a read-view so that stories for all tuples from the space + -- are created + local rv = txn_proxy.new() + rv:begin() + rv('box.space.test:select{}') + + -- Rollback the first reader so that it will delete itself from reader + -- lists of all stories and that will lead to use-after-free + tx:rollback() + + -- Read all the tuples, Tarantool is most likely to crash here if + -- use-after-free broke something + for i = 1, 10000 do + s:get{i} + end + end) +end