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]