From 0be4c6b9483594494051e8f1f67afc2b516270ca Mon Sep 17 00:00:00 2001 From: Thurston Dang Date: Thu, 9 Nov 2023 12:53:06 -0800 Subject: [PATCH] [sanitizer_common] Add experimental flag to tweak dlopen(
) (#71715) This introduces an experimental flag 'test_only_replace_dlopen_main_program'. When enabled, this will replace dlopen(main program,...) with dlopen(NULL,...), which is the correct way to get a handle to the main program. This can be useful when ASan is statically linked, since dladdr((void*)pthread_join) or similar will return the path to the main program. Note that dlopen(main program,...) never ends well: - PIE in recent glibc versions (glibc bugzilla 24323), or non-PIE: return an error - PIE in current GRTE and older glibc: attempt to load the main program again, leading to reinitializing ASan and failing to remap the shadow memory. --------- Co-authored-by: Thurston Dang --- .../lib/sanitizer_common/CMakeLists.txt | 2 + .../sanitizer_common_interceptors.inc | 36 ++++++++++++-- .../lib/sanitizer_common/sanitizer_dl.cpp | 35 ++++++++++++++ .../lib/sanitizer_common/sanitizer_dl.h | 26 ++++++++++ .../lib/sanitizer_common/sanitizer_flags.inc | 6 +++ .../replace_dlopen_main_program_test.cpp | 48 +++++++++++++++++++ .../compiler-rt/lib/sanitizer_common/BUILD.gn | 2 + 7 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 compiler-rt/lib/sanitizer_common/sanitizer_dl.cpp create mode 100644 compiler-rt/lib/sanitizer_common/sanitizer_dl.h create mode 100644 compiler-rt/test/sanitizer_common/TestCases/Linux/replace_dlopen_main_program_test.cpp diff --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt index 25304b71c0c7..ce6d4cf80919 100644 --- a/compiler-rt/lib/sanitizer_common/CMakeLists.txt +++ b/compiler-rt/lib/sanitizer_common/CMakeLists.txt @@ -59,6 +59,7 @@ set(SANITIZER_NOLIBC_SOURCES set(SANITIZER_LIBCDEP_SOURCES sanitizer_common_libcdep.cpp sanitizer_allocator_checks.cpp + sanitizer_dl.cpp sanitizer_linux_libcdep.cpp sanitizer_mac_libcdep.cpp sanitizer_posix_libcdep.cpp @@ -139,6 +140,7 @@ set(SANITIZER_IMPL_HEADERS sanitizer_deadlock_detector_interface.h sanitizer_dense_map.h sanitizer_dense_map_info.h + sanitizer_dl.h sanitizer_errno.h sanitizer_errno_codes.h sanitizer_file.h diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc index 80efaf54a060..607ecae6808b 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -33,16 +33,17 @@ // COMMON_INTERCEPTOR_STRERROR //===----------------------------------------------------------------------===// +#include + #include "interception/interception.h" #include "sanitizer_addrhashmap.h" +#include "sanitizer_dl.h" #include "sanitizer_errno.h" #include "sanitizer_placement_new.h" #include "sanitizer_platform_interceptors.h" #include "sanitizer_symbolizer.h" #include "sanitizer_tls_get_addr.h" -#include - #if SANITIZER_INTERCEPTOR_HOOKS #define CALL_WEAK_INTERCEPTOR_HOOK(f, ...) f(__VA_ARGS__); #define DECLARE_WEAK_INTERCEPTOR_HOOK(f, ...) \ @@ -6307,7 +6308,36 @@ INTERCEPTOR(int, fclose, __sanitizer_FILE *fp) { INTERCEPTOR(void*, dlopen, const char *filename, int flag) { void *ctx; COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, dlopen, filename, flag); - if (filename) COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0); + + if (filename) { + COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0); + +# if !SANITIZER_DYNAMIC + // We care about a very specific use-case: dladdr on + // statically-linked ASan may return
+ // instead of the library. + // We therefore only take effect if the sanitizer is statically + // linked, and we don't bother canonicalizing paths because + // dladdr should return the same address both times (we assume + // the user did not canonicalize the result from dladdr). + if (common_flags()->test_only_replace_dlopen_main_program) { + VPrintf(1, "dlopen interceptor: filename: %s\n", filename); + + const char *SelfFName = DladdrSelfFName(); + VPrintf(1, "dlopen interceptor: DladdrSelfFName: %p %s\n", + (void *)SelfFName, SelfFName); + + if (internal_strcmp(SelfFName, filename) == 0) { + // It's possible they copied the string from dladdr, so + // we do a string comparison rather than pointer comparison. + VPrintf(1, "dlopen interceptor: replacing %s because it matches %s\n", + filename, SelfFName); + filename = (char *)0; // RTLD_DEFAULT + } + } +# endif // !SANITIZER_DYNAMIC + } + void *res = COMMON_INTERCEPTOR_DLOPEN(filename, flag); Symbolizer::GetOrInit()->InvalidateModuleList(); COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, res); diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_dl.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_dl.cpp new file mode 100644 index 000000000000..65194c07b415 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_dl.cpp @@ -0,0 +1,35 @@ +//===-- sanitizer_dl.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file has helper functions that depend on libc's dynamic loading +// introspection. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_dl.h" + +#include + +#include "sanitizer_common/sanitizer_platform.h" + +namespace __sanitizer { +extern const char *SanitizerToolName; + +const char *DladdrSelfFName(void) { +#if SANITIZER_GLIBC + Dl_info info; + int ret = dladdr((void *)&SanitizerToolName, &info); + if (ret) { + return info.dli_fname; + } +#endif + + return nullptr; +} + +} // namespace __sanitizer diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_dl.h b/compiler-rt/lib/sanitizer_common/sanitizer_dl.h new file mode 100644 index 000000000000..ecde0664eb04 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_dl.h @@ -0,0 +1,26 @@ +//===-- sanitizer_dl.h ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file has helper functions that depend on libc's dynamic loading +// introspection. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_DL_H +#define SANITIZER_DL_H + +namespace __sanitizer { + +// Returns the path to the shared object or - in the case of statically linked +// sanitizers +// - the main program itself, that contains the sanitizer. +const char* DladdrSelfFName(void); + +} // namespace __sanitizer + +#endif // SANITIZER_DL_H diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc b/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc index 6148ae56067c..949bdbd148b6 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc @@ -269,3 +269,9 @@ COMMON_FLAG(bool, detect_write_exec, false, COMMON_FLAG(bool, test_only_emulate_no_memorymap, false, "TEST ONLY fail to read memory mappings to emulate sanitized " "\"init\"") +// With static linking, dladdr((void*)pthread_join) or similar will return the +// path to the main program. This flag will replace dlopen(
+// with dlopen(NULL,...), which is the correct way to get a handle to the main +// program. +COMMON_FLAG(bool, test_only_replace_dlopen_main_program, false, + "TEST ONLY replace dlopen(
,...) with dlopen(NULL)") diff --git a/compiler-rt/test/sanitizer_common/TestCases/Linux/replace_dlopen_main_program_test.cpp b/compiler-rt/test/sanitizer_common/TestCases/Linux/replace_dlopen_main_program_test.cpp new file mode 100644 index 000000000000..72fff2719def --- /dev/null +++ b/compiler-rt/test/sanitizer_common/TestCases/Linux/replace_dlopen_main_program_test.cpp @@ -0,0 +1,48 @@ +// Test 'test_only_replace_dlopen_main_program' flag + +// RUN: %clangxx %s -pie -fPIE -o %t +// RUN: env %tool_options='test_only_replace_dlopen_main_program=true' %run %t +// RUN: env %tool_options='test_only_replace_dlopen_main_program=false' not %run %t + +// dladdr is 'nonstandard GNU extensions that are also present on Solaris' +// REQUIRES: glibc + +// Does not intercept dlopen +// UNSUPPORTED: hwasan, lsan, ubsan + +// Flag has no effect with dynamic runtime +// UNSUPPORTED: asan-dynamic-runtime + +#include +#include +#include + +// We can't use the address of 'main' (error: ISO C++ does not allow 'main' to be used by a program [-Werror,-Wmain]') +// so we add this function. +__attribute__((noinline, no_sanitize("address"))) void foo() { + printf("Hello World!\n"); +} + +int main(int argc, char *argv[]) { + foo(); + + // "If filename is NULL, then the returned handle is for the main program." + void *correct_handle = dlopen(NULL, RTLD_LAZY); + printf("dlopen(NULL,...): %p\n", correct_handle); + + Dl_info info; + if (dladdr((void *)&foo, &info) == 0) { + printf("dladdr failed\n"); + return 1; + } + printf("dladdr(&foo): %s\n", info.dli_fname); + void *test_handle = dlopen(info.dli_fname, RTLD_LAZY); + printf("dlopen(%s,...): %p\n", info.dli_fname, test_handle); + + if (test_handle != correct_handle) { + printf("Error: handles do not match\n"); + return 1; + } + + return 0; +} diff --git a/llvm/utils/gn/secondary/compiler-rt/lib/sanitizer_common/BUILD.gn b/llvm/utils/gn/secondary/compiler-rt/lib/sanitizer_common/BUILD.gn index d6f18aec9f21..5a4a6f17cf31 100644 --- a/llvm/utils/gn/secondary/compiler-rt/lib/sanitizer_common/BUILD.gn +++ b/llvm/utils/gn/secondary/compiler-rt/lib/sanitizer_common/BUILD.gn @@ -54,6 +54,8 @@ source_set("sources") { "sanitizer_deadlock_detector_interface.h", "sanitizer_dense_map.h", "sanitizer_dense_map_info.h", + "sanitizer_dl.cpp", + "sanitizer_dl.h", "sanitizer_errno.cpp", "sanitizer_errno.h", "sanitizer_errno_codes.h",