[3.3.0] heap-use-after-free for ffi.cpp:g_last_exception_what
ni4 opened this issue · comments
After checking Botan 3.3.0 with enabled sanitizers together with RNP library on macOS 13.6.4, got the crash report attached below. It seems that thread_local std::string g_last_exception_what
is accessed from botan_rng_destroy()
while it was previously destroyed in dyld4::RuntimeState::_finalizeListTLV()
?
Do you have an idea what may cause this (Botan/dyld/RNP?)/or should I dig more on RNP side?
Thanks!
245: ==41582==ERROR: AddressSanitizer: heap-use-after-free on address 0x60400000d350 at pc 0x000108f5a947 bp 0x7ff7bc69dde0 sp 0x7ff7bc69ddd8
245: WRITE of size 1 at 0x60400000d350 thread T0
245: #0 0x108f5a946 in std::__1::char_traits<char>::assign(char&, char const&) char_traits.h:207
245: #1 0x108f5a52b in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::clear[abi:v160006]() string:3277
245: #2 0x1091bf18a in Botan_FFI::ffi_guard_thunk(char const*, std::__1::function<int ()> const&) ffi.cpp:117
245: #3 0x109826296 in int Botan_FFI::ffi_delete_object<Botan::RandomNumberGenerator, 1224866241u>(Botan_FFI::botan_struct<Botan::RandomNumberGenerator, 1224866241u>*, char const*) ffi_util.h:128
245: #4 0x1098260fc in botan_rng_destroy ffi_rng.cpp:144
245: #5 0x1048dfe94 in rnp::RNG::~RNG() rng.cpp:49
245: #6 0x1048dff08 in rnp::RNG::~RNG() rng.cpp:48
245: #7 0x104980a4e in rnp::SecurityContext::~SecurityContext() sec_profile.cpp:204
245: #8 0x104980ac8 in rnp::SecurityContext::~SecurityContext() sec_profile.cpp:202
245: #9 0x7ff81b3c5ba0 in __cxa_finalize_ranges+0x198 (libsystem_c.dylib:x86_64+0x2aba0)
245: #10 0x7ff81b3c59ba in exit+0x22 (libsystem_c.dylib:x86_64+0x2a9ba)
245: #11 0x7ff81b50b8d2 in dyld4::LibSystemHelpers::exit(int) const+0xa (libdyld.dylib:x86_64+0x118d2)
245: #12 0x7ff81b19a457 in start+0x7a7 (dyld:x86_64+0xfffffffffff6e457)
245:
245: 0x60400000d350 is located 0 bytes inside of 48-byte region [0x60400000d350,0x60400000d380)
245: freed by thread T0 here:
245: #0 0x1065d02cd in wrap__ZdlPv+0x7d (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0xec2cd)
245: #1 0x7ff81b1a6fd9 in invocation function for block in dyld4::RuntimeState::_finalizeListTLV(void*)+0x34 (dyld:x86_64+0xfffffffffff7afd9)
245: #2 0x7ff81b1a6f6a in dyld4::RuntimeState::_finalizeListTLV(void*)+0x4e (dyld:x86_64+0xfffffffffff7af6a)
245: #3 0x7ff81b3c59b3 in exit+0x1b (libsystem_c.dylib:x86_64+0x2a9b3)
245: #4 0x7ff81b50b8d2 in dyld4::LibSystemHelpers::exit(int) const+0xa (libdyld.dylib:x86_64+0x118d2)
245: #5 0x7ff81b19a457 in start+0x7a7 (dyld:x86_64+0xfffffffffff6e457)
245:
245: previously allocated by thread T0 here:
245: #0 0x1065cfead in wrap__Znwm+0x7d (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0xebead)
245: #1 0x7ff81b436fd9 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::__grow_by_and_replace(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, char const*)+0x8f (libc++.1.dylib:x86_64+0x13fd9)
245: #2 0x7ff81b46c864 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::__assign_external(char const*, unsigned long)+0x54 (libc++.1.dylib:x86_64+0x49864)
245: #3 0x1091be931 in Botan_FFI::ffi_error_exception_thrown(char const*, char const*, int) ffi.cpp:25
245: #4 0x1091bf7b9 in Botan_FFI::ffi_guard_thunk(char const*, std::__1::function<int ()> const&) ffi.cpp:126
245: #5 0x1091c1626 in botan_hex_decode ffi.cpp:317
245: #6 0x1048f72b0 in rnp::hex_decode(char const*, unsigned char*, unsigned long) mem.cpp:62
245: #7 0x1045eadad in rnp_tests_test_utils_hex2bin_Test::TestBody() utils-hex2bin.cpp:71
245: #8 0x1062066af in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) gtest.cc:2635
245: #9 0x1060baf67 in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) gtest.cc:2671
245: #10 0x1060ba78e in testing::Test::Run() gtest.cc:2710
245: #11 0x1060bf932 in testing::TestInfo::Run() gtest.cc:2858
245: #12 0x1060c5efd in testing::TestSuite::Run() gtest.cc:3036
245: #13 0x106107221 in testing::internal::UnitTestImpl::RunAllTests() gtest.cc:5955
245: #14 0x106250d4f in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) gtest.cc:2635
245: #15 0x106104a1e in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) gtest.cc:2671
245: #16 0x106104289 in testing::UnitTest::Run() gtest.cc:5519
245: #17 0x1044e90b0 in RUN_ALL_TESTS() gtest.h:2334
245: #18 0x1044e8d31 in main rnp_tests.cpp:92
245: #19 0x7ff81b19a41e in start+0x76e (dyld:x86_64+0xfffffffffff6e41e)
245:
245: SUMMARY: AddressSanitizer: heap-use-after-free char_traits.h:207 in std::__1::char_traits<char>::assign(char&, char const&)
245: Shadow bytes around the buggy address:
245: 0x60400000d080: fa fa fd fd fd fd fd fd fa fa 00 00 00 00 03 fa
245: 0x60400000d100: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
245: 0x60400000d180: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
245: 0x60400000d200: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
245: 0x60400000d280: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
245: =>0x60400000d300: fa fa fd fd fd fd fd fd fa fa[fd]fd fd fd fd fd
245: 0x60400000d380: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fd
245: 0x60400000d400: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd
245: 0x60400000d480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
245: 0x60400000d500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
245: 0x60400000d580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
245: Shadow byte legend (one shadow byte represents 8 application bytes):
245: Addressable: 00
245: Partially addressable: 01 02 03 04 05 06 07
245: Heap left redzone: fa
245: Freed heap region: fd
245: Stack left redzone: f1
245: Stack mid redzone: f2
245: Stack right redzone: f3
245: Stack after return: f5
245: Stack use after scope: f8
245: Global redzone: f9
245: Global init order: f6
245: Poisoned by user: f7
245: Container overflow: fc
245: Array cookie: ac
245: Intra object redzone: bb
245: ASan internal: fe
245: Left alloca redzone: ca
245: Right alloca redzone: cb
245: ==41582==ABORTING
Mhh, so what I read from this: your thread is ending and the runtime is cleaning up thread-local globals. As it turns out, the thread_local g_last_exception_what
is cleaned up (in _finalizeListTLV
) before some other global object is destroyed by the runtime (in __cxa_finalize_ranges
). Unfortunately, the destructor of the latter still touches g_last_exception_what
and AddressSanitizer is not happy (for good reason).
As it turns out, that's what the standard mandates: https://eel.is/c++draft/basic.start.term#2.sentence-2
The destruction of all constructed objects with thread storage duration within that thread strongly happens before destroying any object with static storage duration
@reneme thanks for the detailed reply! Definitely it's our issue - I forgot/mislooked that in tests we use global static variable, was thinking about the dynamic one. Thanks again, closing this now.