[compiler-rt] 0be4c6b - [sanitizer_common] Add experimental flag to tweak dlopen(<main program>) (#71715)

via llvm-commits llvm-commits at lists.llvm.org
Thu Nov 9 12:53:09 PST 2023


Author: Thurston Dang
Date: 2023-11-09T12:53:06-08:00
New Revision: 0be4c6b9483594494051e8f1f67afc2b516270ca

URL: https://github.com/llvm/llvm-project/commit/0be4c6b9483594494051e8f1f67afc2b516270ca
DIFF: https://github.com/llvm/llvm-project/commit/0be4c6b9483594494051e8f1f67afc2b516270ca.diff

LOG: [sanitizer_common] Add experimental flag to tweak dlopen(<main program>) (#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 <thurston at google.com>

Added: 
    compiler-rt/lib/sanitizer_common/sanitizer_dl.cpp
    compiler-rt/lib/sanitizer_common/sanitizer_dl.h
    compiler-rt/test/sanitizer_common/TestCases/Linux/replace_dlopen_main_program_test.cpp

Modified: 
    compiler-rt/lib/sanitizer_common/CMakeLists.txt
    compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
    compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
    llvm/utils/gn/secondary/compiler-rt/lib/sanitizer_common/BUILD.gn

Removed: 
    


################################################################################
diff  --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt
index 25304b71c0c7061..ce6d4cf80919b25 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 80efaf54a0607f6..607ecae6808b72a 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 <stdarg.h>
+
 #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 <stdarg.h>
-
 #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 <main program>
+    // 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 000000000000000..65194c07b41530a
--- /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 <dlfcn.h>
+
+#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 000000000000000..ecde0664eb04950
--- /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 6148ae56067cae0..949bdbd148b6b89 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(<main program,...>
+// 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(<main program>,...) 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 000000000000000..72fff2719def2c4
--- /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 <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// 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 d6f18aec9f2113f..5a4a6f17cf311a6 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",


        


More information about the llvm-commits mailing list