Skip to content

Commit 599ff0f

Browse files
committed
Fix GetMergeOperands() heap-use-after-free on flushed memtable
The unit test repro gives the following error prior to the fix: ``` ==2175705==ERROR: AddressSanitizer: heap-use-after-free on address 0x61f0000012a5 at pc 0x7f0fc36e76ce bp 0x7ffc103e9ca0 sp 0x7ffc103e9450 READ of size 5 at 0x61f0000012a5 thread T0 #0 0x7f0fc36e76cd in __interceptor_memcpy /home/engshare/third-party2/gcc/9.x/src/gcc-10.x/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 #1 0x7f0fc35a207e in std::char_traits<char>::copy(char*, char const*, unsigned long) /home/engshare/third-party2/libgcc/9.x/src/gcc-9.x/x86_64-facebook-linux/libstdc++-v3/include/bits/char_traits.h:365 #2 0x7f0fc35a207e in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy(char*, char const*, unsigned long) /home/engshare/third-party2/libgcc/9.x/src/gcc-9.x/x86_64-facebook-linux/libstdc++-v3/include/bits/basic_string.h:351 facebook#3 0x7f0fc35a207e in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long) /home/engshare/third-party2/libgcc/9.x/src/gcc-9.x/x86_64-facebook-linux/libstdc++-v3/include/bits/basic_string.tcc:440 facebook#4 0x8679ca in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::assign(char const*, unsigned long) /mnt/gvfs/third-party2/libgcc/4959b39cfbe5965a37c861c4c327fa7c5c759b87/9.x/platform009/9202ce7/include/c++/9.3.0/bits/basic_string.h:1422 facebook#5 0x8679ca in rocksdb::PinnableSlice::PinSelf(rocksdb::Slice const&) include/rocksdb/slice.h:171 facebook#6 0x8679ca in rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) db/db_impl/db_impl.cc:1930 facebook#7 0x547324 in rocksdb::DBImpl::GetMergeOperands(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*, rocksdb::GetMergeOperandsOptions*, int*) db/db_impl/db_impl.h:203 facebook#8 0x547324 in rocksdb::DBMergeOperandTest_FlushedMergeOperandReadAfterFreeBug_Test::TestBody() db/db_merge_operand_test.cc:117 facebook#9 0x7241da in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3899 facebook#10 0x7241da in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3935 facebook#11 0x701a47 in testing::Test::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3973 facebook#12 0x702040 in testing::Test::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3965 facebook#13 0x702040 in testing::TestInfo::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4149 facebook#14 0x7025f7 in testing::TestInfo::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4124 facebook#15 0x7025f7 in testing::TestCase::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4267 facebook#16 0x704217 in testing::TestCase::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4253 facebook#17 0x704217 in testing::internal::UnitTestImpl::RunAllTests() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:6633 facebook#18 0x72505a in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3899 facebook#19 0x72505a in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3935 facebook#20 0x704aa1 in testing::UnitTest::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:6242 facebook#21 0x4c4aff in RUN_ALL_TESTS() third-party/gtest-1.8.1/fused-src/gtest/gtest.h:22110 facebook#22 0x4c4aff in main db/db_merge_operand_test.cc:404 facebook#23 0x7f0fc3108dc4 in __libc_start_main ../csu/libc-start.c:308 facebook#24 0x5445fd in _start (/data/users/andrewkr/rocksdb/db_merge_operand_test+0x5445fd) 0x61f0000012a5 is located 1061 bytes inside of 3264-byte region [0x61f000000e80,0x61f000001b40) freed by thread T0 here: #0 0x7f0fc375b6af in operator delete(void*, unsigned long) /home/engshare/third-party2/gcc/9.x/src/gcc-10.x/libsanitizer/asan/asan_new_delete.cc:177 #1 0x743be8 in rocksdb::SuperVersion::~SuperVersion() db/column_family.cc:432 #2 0x8052aa in rocksdb::DBImpl::CleanupSuperVersion(rocksdb::SuperVersion*) db/db_impl/db_impl.cc:3534 facebook#3 0x8676c2 in rocksdb::DBImpl::ReturnAndCleanupSuperVersion(rocksdb::ColumnFamilyData*, rocksdb::SuperVersion*) db/db_impl/db_impl.cc:3544 facebook#4 0x8676c2 in rocksdb::DBImpl::GetImpl(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::DBImpl::GetImplOptions&) db/db_impl/db_impl.cc:1911 facebook#5 0x547324 in rocksdb::DBImpl::GetMergeOperands(rocksdb::ReadOptions const&, rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, rocksdb::PinnableSlice*, rocksdb::GetMergeOperandsOptions*, int*) db/db_impl/db_impl.h:203 facebook#6 0x547324 in rocksdb::DBMergeOperandTest_FlushedMergeOperandReadAfterFreeBug_Test::TestBody() db/db_merge_operand_test.cc:117 facebook#7 0x7241da in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3899 facebook#8 0x7241da in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3935 facebook#9 0x701a47 in testing::Test::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3973 facebook#10 0x702040 in testing::Test::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3965 facebook#11 0x702040 in testing::TestInfo::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4149 facebook#12 0x7025f7 in testing::TestInfo::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4124 facebook#13 0x7025f7 in testing::TestCase::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4267 facebook#14 0x704217 in testing::TestCase::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4253 facebook#15 0x704217 in testing::internal::UnitTestImpl::RunAllTests() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:6633 facebook#16 0x72505a in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3899 facebook#17 0x72505a in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3935 facebook#18 0x704aa1 in testing::UnitTest::Run() third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:6242 facebook#19 0x4c4aff in RUN_ALL_TESTS() third-party/gtest-1.8.1/fused-src/gtest/gtest.h:22110 facebook#20 0x4c4aff in main db/db_merge_operand_test.cc:404 facebook#21 0x7f0fc3108dc4 in __libc_start_main ../csu/libc-start.c:308 facebook#22 0x5445fd in _start (/data/users/andrewkr/rocksdb/db_merge_operand_test+0x5445fd) ... ``` Test Plan: following the fix in this PR, the unit test passes
1 parent 36bc3da commit 599ff0f

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

db/db_impl/db_impl.cc

+4-2
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,8 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
18891889
return s;
18901890
}
18911891
}
1892+
TEST_SYNC_POINT("DBImpl::GetImpl:PostMemTableGet:0");
1893+
TEST_SYNC_POINT("DBImpl::GetImpl:PostMemTableGet:1");
18921894
PinnedIteratorsManager pinned_iters_mgr;
18931895
if (!done) {
18941896
PERF_TIMER_GUARD(get_from_output_files_time);
@@ -1906,8 +1908,6 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
19061908
{
19071909
PERF_TIMER_GUARD(get_post_process_time);
19081910

1909-
ReturnAndCleanupSuperVersion(cfd, sv);
1910-
19111911
RecordTick(stats_, NUMBER_KEYS_READ);
19121912
size_t size = 0;
19131913
if (s.ok()) {
@@ -1934,6 +1934,8 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
19341934
PERF_COUNTER_ADD(get_read_bytes, size);
19351935
}
19361936
RecordInHistogram(stats_, BYTES_PER_READ, size);
1937+
1938+
ReturnAndCleanupSuperVersion(cfd, sv);
19371939
}
19381940
return s;
19391941
}

db/db_merge_operand_test.cc

+36-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class DBMergeOperandTest : public DBTestBase {
4747
: DBTestBase("db_merge_operand_test", /*env_do_fsync=*/true) {}
4848
};
4949

50-
TEST_F(DBMergeOperandTest, MergeOperandReadAfterFreeBug) {
50+
TEST_F(DBMergeOperandTest, CacheEvictedMergeOperandReadAfterFreeBug) {
5151
// There was a bug of reading merge operands after they are mistakely freed
5252
// in DB::GetMergeOperands, which is surfaced by cache full.
5353
// See PR#9507 for more.
@@ -86,6 +86,41 @@ TEST_F(DBMergeOperandTest, MergeOperandReadAfterFreeBug) {
8686
ASSERT_EQ(values[3].ToString(), "v4");
8787
}
8888

89+
TEST_F(DBMergeOperandTest, FlushedMergeOperandReadAfterFreeBug) {
90+
// Repro for a bug where a memtable containing a merge operand could be
91+
// deleted before the merge operand was saved to the result.
92+
auto options = CurrentOptions();
93+
options.merge_operator = MergeOperators::CreateStringAppendOperator();
94+
Reopen(options);
95+
96+
ASSERT_OK(Merge("key", "value"));
97+
98+
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
99+
{{"DBImpl::GetImpl:PostMemTableGet:0",
100+
"DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PreFlush"},
101+
{"DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PostFlush",
102+
"DBImpl::GetImpl:PostMemTableGet:1"}});
103+
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
104+
105+
auto flush_thread = port::Thread([&]() {
106+
TEST_SYNC_POINT(
107+
"DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PreFlush");
108+
ASSERT_OK(Flush());
109+
TEST_SYNC_POINT(
110+
"DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PostFlush");
111+
});
112+
113+
PinnableSlice value;
114+
GetMergeOperandsOptions merge_operands_info;
115+
merge_operands_info.expected_max_number_of_operands = 1;
116+
int number_of_operands;
117+
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
118+
"key", &value, &merge_operands_info,
119+
&number_of_operands));
120+
121+
flush_thread.join();
122+
}
123+
89124
TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) {
90125
Options options;
91126
options.create_if_missing = true;

0 commit comments

Comments
 (0)