This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git

commit d2ffdeb73b0345f4ec59f849e8e5c7bd8f2420ac
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Sep 20 16:34:32 2026 +0800

    branch-4.1: [fix](be) Avoid ASAN double-free during PHDR unwinding #68217 
(#68229)
    
    Cherry-picked from #68217
    
    Co-authored-by: HappenLee <[email protected]>
---
 be/src/common/phdr_cache.cpp       |  5 +++++
 be/src/common/phdr_cache.h         |  2 ++
 be/test/common/phdr_cache_test.cpp | 31 +++++++++++++++++++++++++++++++
 3 files changed, 38 insertions(+)

diff --git a/be/src/common/phdr_cache.cpp b/be/src/common/phdr_cache.cpp
index afc2cfb5ab7..ddaad7c06cc 100644
--- a/be/src/common/phdr_cache.cpp
+++ b/be/src/common/phdr_cache.cpp
@@ -228,6 +228,10 @@ int iteratePHDRCache(int (*callback)(dl_phdr_info* info, 
size_t size, void* data
 
 } // namespace
 
+// ASAN's slow unwinder can reach this interposer while dlsym is freeing its 
previous error
+// string. Resolving the original function with dlsym here would reenter that 
cleanup and
+// free the same string twice. Keep ASAN on the system implementation instead.
+#if !defined(ADDRESS_SANITIZER)
 extern "C"
 #ifndef __clang__
         [[gnu::visibility("default")]] [[gnu::externally_visible]]
@@ -240,6 +244,7 @@ extern "C"
 
     return iteratePHDRCache(callback, data, 0);
 }
+#endif
 
 extern "C"
 #ifndef __clang__
diff --git a/be/src/common/phdr_cache.h b/be/src/common/phdr_cache.h
index abf08a0500f..a2a47d56124 100644
--- a/be/src/common/phdr_cache.h
+++ b/be/src/common/phdr_cache.h
@@ -39,6 +39,8 @@
   * code, and C++ exception handling. Use ScopedPHDRCacheRead only around the 
minimal
   * signal-handler unwind section; GNU libunwind reaches this cache through
   * doris_unwind_iterate_phdr without changing ordinary dl_iterate_phdr 
callers.
+  * ASAN builds do not interpose dl_iterate_phdr, even inside 
ScopedPHDRCacheRead, to avoid
+  * dlsym reentrancy during ASAN's slow unwinding. The dedicated GNU libunwind 
hook still caches.
   *
   * Old cache snapshots are intentionally leaked and remain readable by 
concurrent signal-handler
   * unwinders.
diff --git a/be/test/common/phdr_cache_test.cpp 
b/be/test/common/phdr_cache_test.cpp
index c965d075044..5d7d2cb4ded 100644
--- a/be/test/common/phdr_cache_test.cpp
+++ b/be/test/common/phdr_cache_test.cpp
@@ -20,6 +20,7 @@
 #include "common/phdr_cache.h"
 
 #include <dlfcn.h>
+#include <gtest/gtest.h>
 #include <link.h>
 
 #include <cstdlib>
@@ -62,6 +63,31 @@ std::string test_dso_path() {
 
 } // namespace
 
+#if defined(ADDRESS_SANITIZER)
+TEST(PhdrCacheDeathTest, FailedSymbolLookupsWithSlowUnwinding) {
+    const char* asan_options = std::getenv("ASAN_OPTIONS");
+    const bool had_options = asan_options != nullptr;
+    const std::string saved_options = had_options ? asan_options : "";
+    const std::string child_options = saved_options + 
":fast_unwind_on_malloc=0:disable_coredump=1";
+    ASSERT_EQ(0, setenv("ASAN_OPTIONS", child_options.c_str(), 1));
+
+    // Re-exec so ASAN reads the slow-unwind option before initializing the 
child process.
+    ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+    EXPECT_EXIT(
+            {
+                // Do not consume dlerror() between lookups: the second dlsym 
must free the
+                // first lookup's error string while ASAN collects the free 
stack trace.
+                void* first = dlsym(RTLD_DEFAULT, 
"doris_phdr_cache_missing_symbol_one");
+                void* second = dlsym(RTLD_DEFAULT, 
"doris_phdr_cache_missing_symbol_two");
+                std::_Exit(static_cast<int>(first != nullptr || second != 
nullptr));
+            },
+            ::testing::ExitedWithCode(0), "");
+
+    EXPECT_EQ(0, had_options ? setenv("ASAN_OPTIONS", saved_options.c_str(), 1)
+                             : unsetenv("ASAN_OPTIONS"));
+}
+#endif
+
 // Covers the exact late-dlopen risk of PHDR caching. Normal callers of 
dl_iterate_phdr must keep
 // seeing the live loader list, while the stack-trace signal handler can 
explicitly opt in to the
 // cached snapshot to avoid re-entering glibc's loader lock from an 
interrupted thread.
@@ -84,8 +110,13 @@ TEST(PhdrCacheTest, 
DefaultLoaderViewIsLiveWhileScopedViewUsesSnapshot) {
 
     {
         ScopedPHDRCacheRead cache_scope;
+#if defined(ADDRESS_SANITIZER)
+        EXPECT_TRUE(phdr_contains_test_dso())
+                << "ASAN must use the live loader list even inside a cache 
scope";
+#else
         EXPECT_FALSE(phdr_contains_test_dso())
                 << "scoped PHDR cache should read the pre-dlopen snapshot";
+#endif
     }
     
EXPECT_FALSE(unwind_phdr_cache_contains_test_dso(reinterpret_cast<uintptr_t>(marker)))
             << "libunwind PHDR hook should also read the pre-dlopen snapshot";


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to