facebook / rocksdb

A library that provides an embeddable, persistent key-value store for fast storage.
http://rocksdb.org
GNU General Public License v2.0
28.55k stars 6.31k forks source link

AddressSanitizer: heap-use-after-free when using TransactionDB #12976

Closed ljcui closed 2 months ago

ljcui commented 2 months ago

Note: Please use Issues only for bug reports. For questions, discussions, feature requests, etc. post to dev group: https://groups.google.com/forum/#!forum/rocksdb or https://www.facebook.com/groups/rocksdb.dev

Expected behavior

AddressSanitizer do not report error

Actual behavior

AddressSanitizer report heap-use-after-free,

=================================================================
==117924==ERROR: AddressSanitizer: heap-use-after-free on address 0x61d00000d71a at pc 0x7fb398151397 bp 0x7ffdbf72fab0 sp 0x7ffdbf72f258
READ of size 5 at 0x61d00000d71a thread T0
    #0 0x7fb398151396 in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827
    #1 0x7fb39462bb22 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_append(char const*, unsigned long) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14db22)
    #2 0x7fb395d50091 in rocksdb::PutLengthPrefixedSlice(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, rocksdb::Slice const&) /root/rocksdb/util/coding.h:222
    #3 0x7fb396732fa6 in rocksdb::WriteBatchInternal::Delete(rocksdb::WriteBatch*, unsigned int, rocksdb::Slice const&) /root/rocksdb/db/write_batch.cc:1220
    #4 0x7fb39673378c in rocksdb::WriteBatch::Delete(rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&) /root/rocksdb/db/write_batch.cc:1249
    #5 0x7fb3973a95f8 in rocksdb::WriteBatchWithIndex::Delete(rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&) /root/rocksdb/utilities/write_batch_with_index/write_batch_with_index.cc:391
    #6 0x7fb3972dbc3c in operator() /root/rocksdb/utilities/transactions/pessimistic_transaction.cc:351
    #7 0x7fb3972eaab9 in Operate<rocksdb::Slice, rocksdb::WriteCommittedTxn::Delete(rocksdb::ColumnFamilyHandle*, const rocksdb::Slice&, bool)::<lambda()> > /root/rocksdb/utilities/transactions/pessimistic_transaction.cc:486
    #8 0x7fb3972dbe92 in rocksdb::WriteCommittedTxn::Delete(rocksdb::ColumnFamilyHandle*, rocksdb::Slice const&, bool) /root/rocksdb/utilities/transactions/pessimistic_transaction.cc:356
    #9 0x7fb3972d476b in rocksdb::TransactionBaseImpl::Delete(rocksdb::Slice const&) /root/rocksdb/utilities/transactions/transaction_base.h:179
    #10 0x7fb398e62c27 in rocksdb::My_test_Test::TestBody() /root/rocksdb/utilities/transactions/transaction_test.cc:7584
    #11 0x7fb3990be3b9 in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3899
    #12 0x7fb3990b05fa in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3935
    #13 0x7fb39905ea99 in testing::Test::Run() /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3973
    #14 0x7fb39905ff30 in testing::TestInfo::Run() /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4149
    #15 0x7fb399060b51 in testing::TestCase::Run() /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:4267
    #16 0x7fb39907bdec in testing::internal::UnitTestImpl::RunAllTests() /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:6633
    #17 0x7fb3990c0ed2 in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3899
    #18 0x7fb3990b2bad in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:3935
    #19 0x7fb399078b3a in testing::UnitTest::Run() /root/rocksdb/third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc:6242
    #20 0x7fb398e8d32b in RUN_ALL_TESTS() (/root/rocksdb/build/transaction_test+0x34632b)
    #21 0x7fb398e6341f in main /root/rocksdb/utilities/transactions/transaction_test.cc:7599
    #22 0x7fb3941d6d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #23 0x7fb3941d6e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #24 0x7fb398c3d884 in _start (/root/rocksdb/build/transaction_test+0xf6884)

0x61d00000d71a is located 1178 bytes inside of 1921-byte region [0x61d00000d280,0x61d00000da01)
freed by thread T0 here:
    #0 0x7fb3981cdd07 in operator delete(void*) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:160
    #1 0x7fb39462a13c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14c13c)

previously allocated by thread T0 here:
    #0 0x7fb3981cd1e7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
    #1 0x7fb39462a0bd in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14c0bd)

SUMMARY: AddressSanitizer: heap-use-after-free ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 in __interceptor_memcpy
Shadow bytes around the buggy address:
  0x0c3a7fff9a90: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9aa0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9ab0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9ac0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9ad0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x0c3a7fff9ae0: fd fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9af0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9b00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9b10: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9b20: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c3a7fff9b30: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==117924==ABORTING

Steps to reproduce the behavior

checkout to v9.5.2, compile rocksdb with -DWITH_ASAN=ON and run the following test code :

  std::string testkv = "testkv";
  std::filesystem::remove_all(testkv);
  rocksdb::Options options;
  options.create_if_missing = true;
  options.create_missing_column_families = true;
  rocksdb::TransactionDBOptions txn_db_options;
  rocksdb::TransactionDB* db = nullptr;
  auto s = rocksdb::TransactionDB::Open(options, txn_db_options, testkv,&db);
  assert(s.ok());
  rocksdb::WriteOptions wo;
  rocksdb::TransactionOptions to;
  rocksdb::Transaction* txn = db->BeginTransaction(wo, to);
  for (auto i = 0; i < 100; i++) {
    std::string key = "key" + std::to_string(i);
    s = txn->Put(key, key);
    assert(s.ok());
  }
  rocksdb::ReadOptions ro;
  auto iter = txn->GetIterator(ro);
  for (iter->Seek("key"); iter->Valid(); iter->Next()) {
    auto k = iter->key();
    txn->Delete(k);
  }

  delete iter;
  txn->Commit();
  delete txn;
  db->Close();
  delete db;
adamretter commented 2 months ago

@ljcui Is the code you describe above, the same as from the frame shown in your stack?, i.e.: rocksdb::My_test_Test::TestBody() /root/rocksdb/utilities/transactions/transaction_test.cc:7584

ljcui commented 2 months ago

@ljcui Is the code you describe above, the same as from the frame shown in your stack?, i.e.: rocksdb::My_test_Test::TestBody() /root/rocksdb/utilities/transactions/transaction_test.cc:7584

yes, I add a temporary test case in transaction_test.cc,and the test case content is the code above. Maybe i found the reason, i change the code

from

  for (iter->Seek("key"); iter->Valid(); iter->Next()) {
    auto k = iter->key();
    txn->Delete(k);
  }

to

  for (iter->Seek("key"); iter->Valid(); iter->Next()) {
    auto k = iter->key().ToString();
    txn->Delete(k);
  }

then AddressSanitizer do not report error.