diff --git a/include/tool/coroutine.h b/include/tool/coroutine.h index af84670384..b1d0a45d3c 100644 --- a/include/tool/coroutine.h +++ b/include/tool/coroutine.h @@ -57,6 +57,36 @@ #include // mmap, mprotect, munmap #endif +#ifdef KICAD_SANITIZE_ADDRESS +#include +// ASan fiber switching APIs (available since Clang 3.9/GCC 7) +extern "C" { + void __sanitizer_start_switch_fiber(void** fake_stack_save, + const void* bottom, size_t size); + void __sanitizer_finish_switch_fiber(void* fake_stack_save, + const void** bottom_old, + size_t* size_old); +} + +// Thread-local tracking of ASAN fiber switch state +static bool& GetAsanFiberSwitchState() +{ + thread_local bool asan_fiber_switch_active = false; + return asan_fiber_switch_active; +} + +// Add runtime ASAN detection +static bool IsASanEnabled() +{ + static std::optional asan_enabled; + if (!asan_enabled.has_value()) + { + asan_enabled = (__has_feature(address_sanitizer) ); + } + return asan_enabled.value(); +} +#endif + /** * Implement a coroutine. * @@ -110,12 +140,24 @@ private: void* tsan_fiber; // The TSAN fiber for this context bool own_tsan_fiber; // Do we own this TSAN fiber? (we only delete fibers we own) #endif +#ifdef KICAD_SANITIZE_ADDRESS + void* asan_fake_stack; // ASan fake stack for this context + const void* asan_stack_bottom; // Stack bottom for ASan + size_t asan_stack_size; // Stack size for ASan + bool asan_stack_registered; // Track if we registered this stack +#endif CONTEXT_T() : ctx( nullptr ) #ifdef KICAD_SANITIZE_THREADS ,tsan_fiber( nullptr ) ,own_tsan_fiber( true ) +#endif +#ifdef KICAD_SANITIZE_ADDRESS + ,asan_fake_stack( nullptr ) + ,asan_stack_bottom( nullptr ) + ,asan_stack_size( 0 ) + ,asan_stack_registered( false ) #endif {} @@ -125,6 +167,15 @@ private: // Only destroy the fiber when we own it if( own_tsan_fiber ) __tsan_destroy_fiber( tsan_fiber ); +#endif +#ifdef KICAD_SANITIZE_ADDRESS + // Clean up ASAN registration if we registered it + if( asan_stack_registered && IsASanEnabled() ) + { + // Note: ASAN doesn't provide explicit stack deregistration, + // but we should reset our tracking + asan_stack_registered = false; + } #endif } }; @@ -206,9 +257,6 @@ public: m_retVal( 0 ) #ifdef KICAD_USE_VALGRIND ,m_valgrind_stack( 0 ) -#endif -#ifdef KICAD_SANITIZE_ADDRESS - ,asan_stack( nullptr ) #endif { m_stacksize = ADVANCED_CFG::GetCfg().m_CoroutineStackSize; @@ -276,12 +324,6 @@ public: CALL_CONTEXT ctx; INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx }; -#ifdef KICAD_SANITIZE_THREADS - // Get the TSAN fiber for the current stack here - m_caller.tsan_fiber = __tsan_get_current_fiber(); - m_caller.own_tsan_fiber = false; -#endif - wxLogTrace( kicadTraceCoroutineStack, "COROUTINE::Call (from root)" ); ctx.Continue( doCall( &args, aArg ) ); @@ -322,12 +364,6 @@ public: CALL_CONTEXT ctx; INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROOT, this, &ctx }; -#ifdef KICAD_SANITIZE_THREADS - // Get the TSAN fiber for the current stack here - m_caller.tsan_fiber = __tsan_get_current_fiber(); - m_caller.own_tsan_fiber = false; -#endif - wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::Resume (from root)" ) ); ctx.Continue( doResume( &args ) ); @@ -377,6 +413,22 @@ private: assert( m_func ); assert( !( m_callee.ctx ) ); +#ifdef KICAD_SANITIZE_THREADS + // Get the TSAN fiber for the current stack here + m_caller.tsan_fiber = __tsan_get_current_fiber(); + m_caller.own_tsan_fiber = false; +#endif +#ifdef KICAD_SANITIZE_ADDRESS + if( IsASanEnabled() ) + { + // Initialize caller's ASan context (main stack) + m_caller.asan_fake_stack = nullptr; + m_caller.asan_stack_bottom = nullptr; // Main stack, managed by ASan + m_caller.asan_stack_size = 0; + m_caller.asan_stack_registered = false; + } +#endif + m_args = &aArgs; std::size_t stackSize = m_stacksize; @@ -408,6 +460,22 @@ private: #ifdef KICAD_USE_VALGRIND m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() ); #endif +#ifdef KICAD_SANITIZE_ADDRESS + if( IsASanEnabled() ) + { + // Register the allocated stack with ASan + m_callee.asan_stack_bottom = m_stack.get(); + m_callee.asan_stack_size = stackSize; + m_callee.asan_fake_stack = nullptr; + m_callee.asan_stack_registered = true; + + // Poison the guard page for better debugging + if( m_stack.get() ) + { + __asan_poison_memory_region( m_stack.get(), SystemPageSize() ); + } + } +#endif #endif #ifdef KICAD_SANITIZE_THREADS @@ -494,6 +562,21 @@ private: INVOCATION_ARGS* doResume( INVOCATION_ARGS* args ) { + +#ifdef KICAD_SANITIZE_THREADS + // Get the TSAN fiber for the current stack here + m_caller.tsan_fiber = __tsan_get_current_fiber(); + m_caller.own_tsan_fiber = false; +#endif +#ifdef KICAD_SANITIZE_ADDRESS + if( IsASanEnabled() ) + { + // Initialize caller's ASan context for resume + m_caller.asan_fake_stack = nullptr; + m_caller.asan_stack_bottom = nullptr; + m_caller.asan_stack_size = 0; + } +#endif return jumpIn( args ); } @@ -519,17 +602,38 @@ private: INVOCATION_ARGS* jumpIn( INVOCATION_ARGS* args ) { + + wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) ); +#ifdef KICAD_SANITIZE_ADDRESS + // Only use ASAN fiber switching for root-level calls to avoid nesting + if( IsASanEnabled() && !GetAsanFiberSwitchState() + && ( m_callee.asan_stack_registered || m_callee.asan_stack_bottom ) ) + { + GetAsanFiberSwitchState() = true; + __sanitizer_start_switch_fiber( &m_caller.asan_fake_stack, + m_callee.asan_stack_bottom, + m_callee.asan_stack_size ); + } +#endif #ifdef KICAD_SANITIZE_THREADS // Tell TSAN we are changing fibers to the callee __tsan_switch_to_fiber( m_callee.tsan_fiber, 0 ); #endif - wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::jumpIn" ) ); - args = reinterpret_cast( libcontext::jump_fcontext( &( m_caller.ctx ), m_callee.ctx, reinterpret_cast( args ) ) ); +#ifdef KICAD_SANITIZE_ADDRESS + // Complete fiber switch only if we started it + if( IsASanEnabled() && GetAsanFiberSwitchState() ) + { + __sanitizer_finish_switch_fiber( m_caller.asan_fake_stack, + &m_caller.asan_stack_bottom, + &m_caller.asan_stack_size ); + GetAsanFiberSwitchState() = false; + } +#endif return args; } @@ -539,6 +643,16 @@ private: INVOCATION_ARGS args{ INVOCATION_ARGS::FROM_ROUTINE, nullptr, nullptr }; INVOCATION_ARGS* ret; +#ifdef KICAD_SANITIZE_ADDRESS + // Only use ASAN fiber switching for root-level calls to avoid nesting + if( IsASanEnabled() && !GetAsanFiberSwitchState() ) + { + GetAsanFiberSwitchState() = true; + __sanitizer_start_switch_fiber( &m_callee.asan_fake_stack, + m_caller.asan_stack_bottom, + m_caller.asan_stack_size ); + } +#endif #ifdef KICAD_SANITIZE_THREADS // Tell TSAN we are changing fibers back to the caller __tsan_switch_to_fiber( m_caller.tsan_fiber, 0 ); @@ -551,6 +665,16 @@ private: reinterpret_cast( &args ) ) ); +#ifdef KICAD_SANITIZE_ADDRESS + // Complete fiber switch only if we started it + if( IsASanEnabled() && GetAsanFiberSwitchState() ) + { + __sanitizer_finish_switch_fiber( m_callee.asan_fake_stack, + &m_callee.asan_stack_bottom, + &m_callee.asan_stack_size ); + GetAsanFiberSwitchState() = false; + } +#endif m_callContext = ret->context; if( ret->type == INVOCATION_ARGS::FROM_ROOT ) @@ -559,6 +683,24 @@ private: } } +#ifdef KICAD_SANITIZE_ADDRESS + // Additional helper for debugging ASAN integration + void LogASanStatus() const + { + if( !IsASanEnabled() ) + return; + + wxLogTrace( kicadTraceCoroutineStack, + wxT("ASAN Coroutine Status: fiber_switch_active=%s, " + "caller_bottom=%p, caller_size=%zu, " + "callee_bottom=%p, callee_size=%zu, callee_registered=%s"), + GetAsanFiberSwitchState() ? "yes" : "no", + m_caller.asan_stack_bottom, m_caller.asan_stack_size, + m_callee.asan_stack_bottom, m_callee.asan_stack_size, + m_callee.asan_stack_registered ? "yes" : "no" ); + } +#endif + #ifndef LIBCONTEXT_HAS_OWN_STACK /// Coroutine stack. std::unique_ptr m_stack; @@ -587,10 +729,6 @@ private: #ifdef KICAD_USE_VALGRIND uint32_t m_valgrind_stack; #endif - -#ifdef KICAD_SANITIZE_ADDRESS - void* asan_stack; -#endif }; -#endif +#endif \ No newline at end of file