randombit / botan

Cryptography Toolkit

Home Page:https://botan.randombit.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[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.