Skip to content

Conversation

@happyCoder92
Copy link
Contributor

Intended use-case is for threads that use (or switch to) stack with special properties e.g. backed by MADV_DONTDUMP memory.

@llvmbot
Copy link
Member

llvmbot commented Sep 22, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: None (happyCoder92)

Changes

Intended use-case is for threads that use (or switch to) stack with special properties e.g. backed by MADV_DONTDUMP memory.


Full diff: https://github.com/llvm/llvm-project/pull/160135.diff

10 Files Affected:

  • (modified) compiler-rt/include/sanitizer/asan_interface.h (+7)
  • (modified) compiler-rt/lib/asan/asan_fake_stack.cpp (+30-7)
  • (modified) compiler-rt/lib/asan/asan_fake_stack.h (+3-1)
  • (modified) compiler-rt/lib/asan/asan_interface.inc (+2)
  • (modified) compiler-rt/lib/asan/asan_thread.cpp (+4-3)
  • (modified) compiler-rt/lib/asan/asan_thread.h (+7-1)
  • (modified) compiler-rt/lib/asan_abi/asan_abi.cpp (+2)
  • (modified) compiler-rt/lib/asan_abi/asan_abi.h (+3)
  • (modified) compiler-rt/lib/asan_abi/asan_abi_shim.cpp (+6)
  • (added) compiler-rt/test/asan/TestCases/disable_fake_stack.cpp (+27)
diff --git a/compiler-rt/include/sanitizer/asan_interface.h b/compiler-rt/include/sanitizer/asan_interface.h index 37b6d08f4db19..06deb9116709f 100644 --- a/compiler-rt/include/sanitizer/asan_interface.h +++ b/compiler-rt/include/sanitizer/asan_interface.h @@ -333,6 +333,13 @@ void SANITIZER_CDECL __asan_handle_no_return(void); /// trace. Returns 1 if successful, 0 if not. int SANITIZER_CDECL __asan_update_allocation_context(void *addr); +/// Disables fake stack usage for the current thread. +/// Temporarily disables use-after-return detection for current thread. +void SANITIZER_CDECL __asan_disable_fake_stack_usage(void); + +/// (Re)enables fake stack usage for the current thread. +void SANITIZER_CDECL __asan_enable_fake_stack_usage(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/compiler-rt/lib/asan/asan_fake_stack.cpp b/compiler-rt/lib/asan/asan_fake_stack.cpp index c3ed2526f0ed4..e1772997d681f 100644 --- a/compiler-rt/lib/asan/asan_fake_stack.cpp +++ b/compiler-rt/lib/asan/asan_fake_stack.cpp @@ -217,26 +217,44 @@ static THREADLOCAL FakeStack *fake_stack_tls; FakeStack *GetTLSFakeStack() { return fake_stack_tls; } -void SetTLSFakeStack(FakeStack *fs) { +void SetTLSFakeStack(AsanThread* t, FakeStack *fs) { + if (fs && !t->fake_stack_usage_enabled()) { + return; + } fake_stack_tls = fs; } #else FakeStack *GetTLSFakeStack() { return 0; } -void SetTLSFakeStack(FakeStack *fs) { } +void SetTLSFakeStack(AsanThread* t, FakeStack *fs) { } #endif // (SANITIZER_LINUX && !SANITIZER_ANDROID) || SANITIZER_FUCHSIA -static FakeStack *GetFakeStack() { +static void DisableFakeStackUsage() { AsanThread *t = GetCurrentThread(); - if (!t) return nullptr; + if (t) { + t->set_fake_stack_usage_enabled(false); + } + SetTLSFakeStack(t, nullptr); +} + +static void EnableFakeStackUsage() { + AsanThread *t = GetCurrentThread(); + if (t) { + t->set_fake_stack_usage_enabled(true); + } +} + +static FakeStack *GetFakeStack(bool for_allocation = true) { + AsanThread *t = GetCurrentThread(); + if (!t || (for_allocation && !t->fake_stack_usage_enabled())) return nullptr; return t->get_or_create_fake_stack(); } -static FakeStack *GetFakeStackFast() { +static FakeStack *GetFakeStackFast(bool for_allocation = true) { if (FakeStack *fs = GetTLSFakeStack()) return fs; if (!__asan_option_detect_stack_use_after_return) return nullptr; - return GetFakeStack(); + return GetFakeStack(for_allocation); } static FakeStack *GetFakeStackFastAlways() { @@ -311,7 +329,7 @@ extern "C" { // -asan-use-after-return=never, after modal UAR flag lands // (https://github.com/google/sanitizers/issues/1394) SANITIZER_INTERFACE_ATTRIBUTE -void *__asan_get_current_fake_stack() { return GetFakeStackFast(); } +void *__asan_get_current_fake_stack() { return GetFakeStackFast(/*for_allocation=*/false); } SANITIZER_INTERFACE_ATTRIBUTE void *__asan_addr_is_in_fake_stack(void *fake_stack, void *addr, void **beg, @@ -349,4 +367,9 @@ void __asan_allocas_unpoison(uptr top, uptr bottom) { (reinterpret_cast<void *>(MemToShadow(top)), 0, (bottom - top) / ASAN_SHADOW_GRANULARITY); } + +SANITIZER_INTERFACE_ATTRIBUTE +void __asan_disable_fake_stack_usage() { return DisableFakeStackUsage(); } +SANITIZER_INTERFACE_ATTRIBUTE +void __asan_enable_fake_stack_usage() { return EnableFakeStackUsage(); } } // extern "C" diff --git a/compiler-rt/lib/asan/asan_fake_stack.h b/compiler-rt/lib/asan/asan_fake_stack.h index 50706e6e5876c..9f1f452834b97 100644 --- a/compiler-rt/lib/asan/asan_fake_stack.h +++ b/compiler-rt/lib/asan/asan_fake_stack.h @@ -18,6 +18,8 @@ namespace __asan { +class AsanThread; + // Fake stack frame contains local variables of one function. struct FakeFrame { uptr magic; // Modified by the instrumented code. @@ -196,7 +198,7 @@ class FakeStack { }; FakeStack *GetTLSFakeStack(); -void SetTLSFakeStack(FakeStack *fs); +void SetTLSFakeStack(AsanThread* t, FakeStack *fs); } // namespace __asan diff --git a/compiler-rt/lib/asan/asan_interface.inc b/compiler-rt/lib/asan/asan_interface.inc index bfc44b4619623..a279ae834549a 100644 --- a/compiler-rt/lib/asan/asan_interface.inc +++ b/compiler-rt/lib/asan/asan_interface.inc @@ -15,6 +15,8 @@ INTERFACE_FUNCTION(__asan_alloca_poison) INTERFACE_FUNCTION(__asan_allocas_unpoison) INTERFACE_FUNCTION(__asan_before_dynamic_init) INTERFACE_FUNCTION(__asan_describe_address) +INTERFACE_FUNCTION(__asan_disable_fake_stack_usage) +INTERFACE_FUNCTION(__asan_enable_fake_stack_usage) INTERFACE_FUNCTION(__asan_exp_load1) INTERFACE_FUNCTION(__asan_exp_load2) INTERFACE_FUNCTION(__asan_exp_load4) diff --git a/compiler-rt/lib/asan/asan_thread.cpp b/compiler-rt/lib/asan/asan_thread.cpp index 2627ae1289012..f6f54022bf377 100644 --- a/compiler-rt/lib/asan/asan_thread.cpp +++ b/compiler-rt/lib/asan/asan_thread.cpp @@ -163,7 +163,7 @@ void AsanThread::StartSwitchFiber(FakeStack **fake_stack_save, uptr bottom, if (fake_stack_save) *fake_stack_save = fake_stack_; fake_stack_ = nullptr; - SetTLSFakeStack(nullptr); + SetTLSFakeStack(this, nullptr); // if fake_stack_save is null, the fiber will die, delete the fakestack if (!fake_stack_save && current_fake_stack) current_fake_stack->Destroy(this->tid()); @@ -177,7 +177,7 @@ void AsanThread::FinishSwitchFiber(FakeStack *fake_stack_save, uptr *bottom_old, } if (fake_stack_save) { - SetTLSFakeStack(fake_stack_save); + SetTLSFakeStack(this, fake_stack_save); fake_stack_ = fake_stack_save; } @@ -242,7 +242,7 @@ FakeStack *AsanThread::AsyncSignalSafeLazyInitFakeStack() { Max(stack_size_log, static_cast<uptr>(flags()->min_uar_stack_size_log)); fake_stack_ = FakeStack::Create(stack_size_log); DCHECK_EQ(GetCurrentThread(), this); - SetTLSFakeStack(fake_stack_); + SetTLSFakeStack(this, fake_stack_); return fake_stack_; } return nullptr; @@ -251,6 +251,7 @@ FakeStack *AsanThread::AsyncSignalSafeLazyInitFakeStack() { void AsanThread::Init(const InitOptions *options) { DCHECK_NE(tid(), kInvalidTid); next_stack_top_ = next_stack_bottom_ = 0; + fake_stack_usage_enabled_ = true; atomic_store(&stack_switching_, false, memory_order_release); CHECK_EQ(this->stack_size(), 0U); SetThreadStackAndTls(options); diff --git a/compiler-rt/lib/asan/asan_thread.h b/compiler-rt/lib/asan/asan_thread.h index 12f0cc7a62dae..c54bbb1c64604 100644 --- a/compiler-rt/lib/asan/asan_thread.h +++ b/compiler-rt/lib/asan/asan_thread.h @@ -104,7 +104,7 @@ class AsanThread { if (!fake_stack_) return; FakeStack *t = fake_stack_; fake_stack_ = nullptr; - SetTLSFakeStack(nullptr); + SetTLSFakeStack(this, nullptr); t->Destroy(tid); } @@ -144,6 +144,11 @@ class AsanThread { GetStartData(&data, sizeof(data)); } + bool fake_stack_usage_enabled() const { return fake_stack_usage_enabled_; } + void set_fake_stack_usage_enabled(bool enabled) { + fake_stack_usage_enabled_ = enabled; + } + private: // NOTE: There is no AsanThread constructor. It is allocated // via mmap() and *must* be valid in zero-initialized state. @@ -179,6 +184,7 @@ class AsanThread { DTLS *dtls_; FakeStack *fake_stack_; + bool fake_stack_usage_enabled_; AsanThreadLocalMallocStorage malloc_storage_; AsanStats stats_; bool unwinding_; diff --git a/compiler-rt/lib/asan_abi/asan_abi.cpp b/compiler-rt/lib/asan_abi/asan_abi.cpp index cf8663024eb73..c2bf2f52dd4e5 100644 --- a/compiler-rt/lib/asan_abi/asan_abi.cpp +++ b/compiler-rt/lib/asan_abi/asan_abi.cpp @@ -73,6 +73,8 @@ void *__asan_abi_addr_is_in_fake_stack(void *fake_stack, void *addr, void **beg, void **end) { return NULL; } +void __asan_abi_disable_fake_stack_usage(void) {} +void __asan_abi_enable_fake_stack_usage(void) {} // Functions concerning poisoning and unpoisoning fake stack alloca void __asan_abi_alloca_poison(void *addr, size_t size) {} diff --git a/compiler-rt/lib/asan_abi/asan_abi.h b/compiler-rt/lib/asan_abi/asan_abi.h index 8702bcd133919..372e44e755cc5 100644 --- a/compiler-rt/lib/asan_abi/asan_abi.h +++ b/compiler-rt/lib/asan_abi/asan_abi.h @@ -76,6 +76,9 @@ void *__asan_abi_load_cxx_array_cookie(void **p); void *__asan_abi_get_current_fake_stack(); void *__asan_abi_addr_is_in_fake_stack(void *fake_stack, void *addr, void **beg, void **end); +void *__asan_abi_disable_fake_stack_usage(); +void *__asan_abi_enable_fake_stack_usage(); + // Functions concerning poisoning and unpoisoning fake stack alloca void __asan_abi_alloca_poison(void *addr, size_t size); void __asan_abi_allocas_unpoison(void *top, void *bottom); diff --git a/compiler-rt/lib/asan_abi/asan_abi_shim.cpp b/compiler-rt/lib/asan_abi/asan_abi_shim.cpp index 2512abc641250..0b4a170015b61 100644 --- a/compiler-rt/lib/asan_abi/asan_abi_shim.cpp +++ b/compiler-rt/lib/asan_abi/asan_abi_shim.cpp @@ -365,6 +365,12 @@ void *__asan_addr_is_in_fake_stack(void *fake_stack, void *addr, void **beg, void **end) { return __asan_abi_addr_is_in_fake_stack(fake_stack, addr, beg, end); } +void __asan_disable_fake_stack_usage(void) { + return __asan_abi_disable_fake_stack_usage(); +} +void __asan_enable_fake_stack_usage(void) { + return __asan_abi_enable_fake_stack_usage(); +} // Functions concerning poisoning and unpoisoning fake stack alloca void __asan_alloca_poison(uptr addr, uptr size) { diff --git a/compiler-rt/test/asan/TestCases/disable_fake_stack.cpp b/compiler-rt/test/asan/TestCases/disable_fake_stack.cpp new file mode 100644 index 0000000000000..b1ee8d72e2b58 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/disable_fake_stack.cpp @@ -0,0 +1,27 @@ +// RUN: %clangxx_asan %s -o %t && %run %t + +#include "defines.h" + +#include <sanitizer/asan_interface.h> + +volatile char *saved; + +ATTRIBUTE_NOINLINE bool IsOnStack() { + volatile char temp = ' '; + void* fake_stack = __asan_get_current_fake_stack(); + void* real = __asan_addr_is_in_fake_stack(fake_stack, const_cast<char*>(&temp), nullptr, nullptr); + saved = &temp; + return real == nullptr; +} + +int main(int argc, char *argv[]) { + __asan_disable_fake_stack_usage(); + if (!IsOnStack()) { + return 1; + } + __asan_enable_fake_stack_usage(); + if (IsOnStack()) { + return 2; + } + return 0; +} 
@github-actions
Copy link

github-actions bot commented Sep 22, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@vitalybuka
Copy link
Collaborator

I thought StartSwitchFiber was added for that

I am not sure I understand what is special with MADV_DONTDUMP
Can you add a test which essentially reproduces the issue?
You don't need to remove the existing test.

Copy link
Collaborator

@vitalybuka vitalybuka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments above

@happyCoder92
Copy link
Contributor Author

Addressed the comments.
We switch to a MADV_DONTDUMP stack and don't want any of the local variable contents to end up in a core dump.
As fake stack is not allocated with the same flags, its usage will result in those variables ending up in the core dump.

@@ -311,7 +329,9 @@ extern "C" {
// -asan-use-after-return=never, after modal UAR flag lands
// (https://github.com/google/sanitizers/issues/1394)
SANITIZER_INTERFACE_ATTRIBUTE
void *__asan_get_current_fake_stack() { return GetFakeStackFast(); }
void* __asan_get_current_fake_stack() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is TODO for to remove this one
if this is just for test, can we instead rely on __asan_get_current_fake_stack
Use __builtin_frame_address

__builtin_frame_address always return real stack address

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the argument and adjusted the test.

Copy link
Collaborator

@vitalybuka vitalybuka Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you forget to upload the patch?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have rebased and updated it

@@ -217,26 +217,44 @@ static THREADLOCAL FakeStack *fake_stack_tls;
FakeStack *GetTLSFakeStack() {
return fake_stack_tls;
}
void SetTLSFakeStack(FakeStack *fs) {
void SetTLSFakeStack(AsanThread* t, FakeStack* fs) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will #163481 help to simplify this patch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly. By only calling SetTLSFakeStack from GetFakeStack #163481 also indirectly ensures the tls var won't be set if fake stack is disabled. I added the thread arg and put the logic into SetTLSFakeStack to make it more explicit.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL

Intended use-case is for threads that use (or switch to) stack with special properties e.g. backed by MADV_DONTDUMP memory.
vitalybuka added a commit that referenced this pull request Oct 16, 2025
vitalybuka added a commit that referenced this pull request Oct 16, 2025
…163674) To simplify implementation of #160135 To keep the logic of figuring out what should be in TLS to one place. The rest of the code should just reset it and rely on GetFakeStackFast()/GetFakeStackFastAlways().
happyCoder92 and others added 6 commits October 15, 2025 18:58
@vitalybuka
Copy link
Collaborator

vitalybuka commented Oct 16, 2025

@happyCoder92 can you please elaborate description message?

llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Oct 16, 2025
…SFakeStack (#163674) To simplify implementation of llvm/llvm-project#160135 To keep the logic of figuring out what should be in TLS to one place. The rest of the code should just reset it and rely on GetFakeStackFast()/GetFakeStackFastAlways().
Copy link
Contributor

@thurstond thurstond left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intended interaction of this API with -mllvm -asan-use-after-return={never,runtime,always} and ASAN_OPTIONS=detect_stack_use_after_return={0,1}? Please update the description and TestCases/disable_fake_stack.cpp to clarify.

Also, if the behavior of __asan_disable_fake_stack()/__asan_enable_fake_stack() depends on those flags, the naming could be confusing.

@happyCoder92
Copy link
Contributor Author

The behavior does depend on the flags. Would __asan_suppress_fake_stack and __asan_unsuppress_fake_stack be less confusing?

@vitalybuka
Copy link
Collaborator

The behavior does depend on the flags. Would __asan_suppress_fake_stack and __asan_unsuppress_fake_stack be less confusing?

I don't have a preference for naming.

I assume @thurstond is asking not to disable the fake stack in always mode.

But in this case __asan_disable_fake_stack caller needs to know that the call failed?

I the current __asan_get_current_fake_stack can be used for that.

Also we need actually not a bool but counters, see __lsan_disable().

This will fix the issue when code on multiple layers will try to suppress the fake stack.

@thurstond
Copy link
Contributor

The behavior does depend on the flags. Would __asan_suppress_fake_stack and __asan_unsuppress_fake_stack be less confusing?

I don't have a preference for naming.

I assume @thurstond is asking not to disable the fake stack in always mode.

But in this case __asan_disable_fake_stack caller needs to know that the call failed?

I the current __asan_get_current_fake_stack can be used for that.

Also we need actually not a bool but counters, see __lsan_disable().

This will fix the issue when code on multiple layers will try to suppress the fake stack.

Based on testing, I believe the behavior is:

  1. compiled with "never", any ASAN_OPTIONS: fake stack is always off
  2. compiled with "runtime", ASAN_OPTIONS=detect_stack_use_after_return=0: fake stack is always off
  3. compiled with "runtime", ASAN_OPTIONS=detect_stack_use_after_return=1: fake stack on initially, __asan_disable_fake_stack()/__asan_enable_fake_stack() work as expected
  4. compiled with "always", any ASAN_OPTIONS: fake stack is always on

The behavior of 1, 3 and 4 are as good as can be expected (but needs to be documented/tested).

For 2., since __asan_disable_fake_stack()/__asan_enable_fake_stack() is a runtime API, it is debatable why it can't override ASAN_OPTIONS=detect_stack_use_after_return (i.e., should detect_stack_use_after_return= set the initial state of whether the fake stack is on/off, rather than permanently turning it off in the case of =0?).

In terms of naming, variables such as fake_stack_enabled_ and related functions such as IsFakeStackEnabled() aren't quite accurate descriptions, because e.g., in "never" mode, the fake stack is always off but after calling __asan_enable_fake_stack(), fake_stack_enabled_ == true, IsFakeStackEnabled() == true.

@vitalybuka
Copy link
Collaborator

For 2., since __asan_disable_fake_stack()/__asan_enable_fake_stack() is a runtime API, it is debatable why it can't

The point of '=2' is to change Asan pass to generate code which does not check for nullptr.

Regarding documentation, In this header yes, in Clang docs, better not yet.

@thurstond
Copy link
Contributor

For 2., since __asan_disable_fake_stack()/__asan_enable_fake_stack() is a runtime API, it is debatable why it can't

The point of '=2' is to change Asan pass to generate code which does not check for nullptr.

Regarding documentation, In this header yes, in Clang docs, better not yet.

By "For 2.", I was referring to bullet point 2 (sorry for the ambiguity):

  1. compiled with "runtime", ASAN_OPTIONS=detect_stack_use_after_return=0: fake stack is always off

Why can't this be overridden by __asan_disable_fake_stack()/__asan_enable_fake_stack()?

@happyCoder92
Copy link
Contributor Author

The intention was to be always able to disable fake stack with this API (regardless of compile & runtime options), but not to enable it if it was disabled via other means. I think it should work like that as currently implemented, but more thorough testing is needed.

@happyCoder92
Copy link
Contributor Author

I added tests to clarify the behavior.
Changed the naming and also used a counter.

As stated before the intention is to always suppress/disable fake stack __asan_suppress_fake_stack(), but not reenable it with __asan_unsuppress_fake_stack() if disabled via some other option.

@happyCoder92 happyCoder92 changed the title Add API to temporalily disable usage of ASAN's fake stack Add API to temporalily suppress usage of ASAN's fake stack Nov 25, 2025
@thurstond thurstond requested a review from vitalybuka November 25, 2025 18:37
@thurstond
Copy link
Contributor

I added tests to clarify the behavior. Changed the naming and also used a counter.

As stated before the intention is to always suppress/disable fake stack __asan_suppress_fake_stack(), but not reenable it with __asan_unsuppress_fake_stack() if disabled via some other option.

Thanks, LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment