| Description | ### Description
We discovered a Heap-use-after-free vulnerability in ChaiScript caused by a race condition between threads. The crash occurs when a detached or async thread attempts to access a shared std::map (likely the function dispatch table or variable scope) while the main thread is destroying it.
The ASAN report shows a READ violation in chaiscript::str_less::operator() on a string object that was part of a map node already freed by the main thread.
### Environment
- OS: Linux x86_64
- Complier: Clang
- Build Configuration: Release mode with ASan enabled.
### Vulnerability Details
- Target: ChaiScript
- Vulnerability Type: Heap-use-after-free (Race Condition)
- Function: chaiscript::str_less::operator()
- Location: include/chaiscript/chaiscript_defines.hpp:201
- Root Cause Analysis: The vulnerability is caused by a race condition between the engine's shutdown process and active asynchronous threads.
1. The provided PoC starts async threads (async(func)) that perform a loop.
2. Inside the loop, the code accesses an undefined variable re6. This forces the worker thread to repeatedly perform lookups in the shared symbol table (std::map).
3. The main script finishes execution and the ChaiScript engine begins to tear down, freeing the memory of the symbol table (Thread T0 calls operator delete).
4. The worker thread (Thread T1), still running the loop, attempts to access the symbol map to look up re6. It tries to read a std::string key from a map node that was just freed by T0, resulting in a UAF.
### Reproduce
1. Build [ChaiScript](https://github.com/ChaiScript/ChaiScript) with Release optimization and ASAN enabled.
2. Run with the crashing file:
<details>
<summary>poc</summary>
```
var func = fun(){
var ret = 0;
for (var i = 0; i < 50000; ++i) {
re6 += i;
}
return ret;
}
var fut1 = async(func);
var fut2 = async(func);
```
</details>
```
./chai crash.chai
```
<details>
<summary>ASAN report</summary>
```
==29807==ERROR: AddressSanitizer: heap-use-after-free on address 0x507000005280 at pc 0x56426d07d6db bp 0x7fec823fde90 sp 0x7fec823fde88
READ of size 8 at 0x507000005280 thread T1
#0 0x56426d07d6da in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_data() const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/basic_string.h:223:28
#1 0x56426d07d6da in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::begin() const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/basic_string.h:965:31
#2 0x56426d07d6da in bool chaiscript::str_less::operator()<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::basic_string_view<char, std::char_traits<char>>>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::basic_string_view<char, std::char_traits<char>> const&) const /src/ChaiScript/static_libs/../include/chaiscript/language/../dispatchkit/../chaiscript_defines.hpp:201:49
#3 0x56426d07d6da in std::_Rb_tree_const_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>> std::_Rb_tree<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>>, chaiscript::str_less, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>>>::_M_lower_bound_tr<std::basic_string_view<char, std::char_traits<char>>, void>(std::basic_string_view<char, std::char_traits<char>> const&) const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_tree.h:1338:11
#4 0x56426d07d6da in std::_Rb_tree_const_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>> std::_Rb_tree<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>>, chaiscript::str_less, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>>>::_M_find_tr<std::basic_string_view<char, std::char_traits<char>>, void>(std::basic_string_view<char, std::char_traits<char>> const&) const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_tree.h:1306:15
#5 0x56426d07d6da in decltype((*this)._M_t._M_find_tr(fp)) std::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, chaiscript::Boxed_Value, chaiscript::str_less, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, chaiscript::Boxed_Value>>>::find<std::basic_string_view<char, std::char_traits<char>>>(std::basic_string_view<char, std::char_traits<char>> const&) const /usr/lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/stl_map.h:1251:16
#6 0x56426d07d6da in chaiscript::detail::Dispatch_Engine::get_object(std::basic_string_view<char, std::char_traits<char>>, std::atomic<unsigned long>&, chaiscript::detail::Stack_Holder&) const /src/ChaiScript/static_libs/../include/chaiscript/language/../dispatchkit/dispatchkit.hpp:539:51
#7 0x56426d0766ff in chaiscript::detail::Dispatch_State::get_object(std::basic_string_view<char, std::char_traits<char>>, std::atomic<unsigned long>&) const /src/ChaiScript/static_libs/../include/chaiscript/language/../dispatchkit/dispatchkit.hpp:1193:31
#8 0x56426d0766ff in chaiscript::eval::Id_AST_Node<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>::eval_internal(chaiscript::detail::Dispatch_State const&) const /src/ChaiScript/static_libs/../include/chaiscript/language/chaiscript_eval.hpp:283:23
#9 0x56426d0761e8 in chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>::eval(chaiscript::detail::Dispatch_State const&) const /src/ChaiScript/static_libs/../include/chaiscript/language/chaiscript_eval.hpp:141:18
#10 0x56426d1c3646 in chaiscript::eval::Equation_AST_Node<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>::eval_internal(chaiscript::detail::Dispatch_State const&) const::'lambda'()::operator()() const /src/ChaiScript/static_libs/../include/chaiscript/language/chaiscript_eval.hpp:426:41
#11 0x56426d1c3646 in chaiscript::eval::Equation_AST_Node<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>::eval_internal(chaiscript::detail::Dispatch_State const&) const /src/ChaiScript/static_libs/../include/chaiscript/language/chaiscript_eval.hpp:420:23
#12 0x56426d0761e8 in chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>::eval(chaiscript::detail::Dispatch_State const&) const /src/ChaiScript/static_libs/../include/chaiscript/language/chaiscript_eval.hpp:141:18
#13 0x56426d0ce60c in auto chaiscript::optimizer::For_Loop::optimize<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>(std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>)::'lambda'(std::vector<std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>, std::allocator<std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>>> const&, chaiscript::detail::Dispatch_State const&)::operator()(std::vector<std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>, std::allocator<std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>>> const&, chaiscript::detail::Dispatch_State const&) const /src/ChaiScript/static_libs/../include/chaiscript/language/chaiscript_optimizer.hpp:398:60
#14 0x56426d0cd79d in chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail> std::__invoke_impl<chaiscript::Boxed_Value, auto chaiscript::optimizer::For_Loop::optimize<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>(std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>)::'lambda'(std::vector<std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>, std::allocator<std::unique_ptr<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>, std::default_delete<chaiscript::eval::AST_Node_Impl<chaiscript::eval::Tracer<chaiscript::eval::Noop_Tracer_Detail>>>>>> const&, chaiscript::detail::Dispatch_State const&) |
|---|