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

Thurston Dang via llvm-commits llvm-commits at lists.llvm.org
Wed Nov 8 11:17:03 PST 2023


https://github.com/thurstond updated https://github.com/llvm/llvm-project/pull/71715

>From 2a73a5fba63e5dc769fb10166315f3c912bcf9fa Mon Sep 17 00:00:00 2001
From: Thurston Dang <thurston at google.com>
Date: Wed, 8 Nov 2023 17:50:29 +0000
Subject: [PATCH 1/3] [sanitizer_common] Add experimental flag to tweak
 dlopen(<main program>)

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.
---
 .../sanitizer_common_interceptors.inc         | 30 ++++++++++++++++++-
 .../lib/sanitizer_common/sanitizer_flags.inc  |  6 ++++
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
index 80efaf54a0607f6..0f7d9da9d148443 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
@@ -6304,10 +6304,38 @@ INTERCEPTOR(int, fclose, __sanitizer_FILE *fp) {
 #endif
 
 #if SANITIZER_INTERCEPT_DLOPEN_DLCLOSE
+// Returns 1 if key is a suffix of str, 0 otherwise
+static int internal_strcmp_suffix(const char *key, const char *str) {
+  if (!key || !str)
+    return 0;
+
+  if (internal_strlen(key) > internal_strlen(str))
+    return 0;
+
+  return !internal_strcmp(str + internal_strlen(str) - internal_strlen(key),
+                          key);
+}
+
+#  if SANITIZER_GLIBC
+extern char *__progname;
+#  endif
+
 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) {
+#  if SANITIZER_GLIBC
+    if (common_flags()->test_only_replace_dlopen_main_program &&
+        internal_strcmp_suffix(__progname, filename)) {
+      VPrintf(1, "dlopen interceptor: replacing %s because it matches %s\n",
+              filename, __progname);
+      filename = (char *)0;  // RTLD_DEFAULT
+    }
+#  endif
+    COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0);
+  }
+
   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_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)")

>From da65373205833bfd82cd88adf0ed8f796f037d70 Mon Sep 17 00:00:00 2001
From: Thurston Dang <thurston at google.com>
Date: Wed, 8 Nov 2023 18:29:32 +0000
Subject: [PATCH 2/3] Check validity of filename string before reading

---
 .../lib/sanitizer_common/sanitizer_common_interceptors.inc      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
index 0f7d9da9d148443..02167ad4c1c88e6 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
@@ -6325,6 +6325,7 @@ INTERCEPTOR(void*, dlopen, const char *filename, int flag) {
   COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, dlopen, filename, flag);
 
   if (filename) {
+    COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0);
 #  if SANITIZER_GLIBC
     if (common_flags()->test_only_replace_dlopen_main_program &&
         internal_strcmp_suffix(__progname, filename)) {
@@ -6333,7 +6334,6 @@ INTERCEPTOR(void*, dlopen, const char *filename, int flag) {
       filename = (char *)0;  // RTLD_DEFAULT
     }
 #  endif
-    COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0);
   }
 
   void *res = COMMON_INTERCEPTOR_DLOPEN(filename, flag);

>From c798bf13d30f1d6c03b367461eef97c3968b2d90 Mon Sep 17 00:00:00 2001
From: Thurston Dang <thurston at google.com>
Date: Wed, 8 Nov 2023 19:11:50 +0000
Subject: [PATCH 3/3] internal_is_suffix fixes per Kirill's suggestion Added
 test case per Vitaly's suggestion

---
 .../sanitizer_common_interceptors.inc         | 14 ++++-----
 .../replace_dlopen_main_program_test.cpp      | 29 +++++++++++++++++++
 2 files changed, 36 insertions(+), 7 deletions(-)
 create mode 100644 compiler-rt/test/sanitizer_common/TestCases/replace_dlopen_main_program_test.cpp

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
index 02167ad4c1c88e6..f9952a729541a3c 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
@@ -6304,16 +6304,16 @@ INTERCEPTOR(int, fclose, __sanitizer_FILE *fp) {
 #endif
 
 #if SANITIZER_INTERCEPT_DLOPEN_DLCLOSE
-// Returns 1 if key is a suffix of str, 0 otherwise
-static int internal_strcmp_suffix(const char *key, const char *str) {
-  if (!key || !str)
+// Returns 1 if 'suffix' matches the end of 'str', 0 otherwise
+static int internal_is_suffix(const char *suffix, const char *str) {
+  if (!suffix || !str)
     return 0;
 
-  if (internal_strlen(key) > internal_strlen(str))
+  if (internal_strlen(suffix) > internal_strlen(str))
     return 0;
 
-  return !internal_strcmp(str + internal_strlen(str) - internal_strlen(key),
-                          key);
+  return internal_strcmp(str + internal_strlen(str) - internal_strlen(suffix),
+                         suffix) == 0;
 }
 
 #  if SANITIZER_GLIBC
@@ -6328,7 +6328,7 @@ INTERCEPTOR(void*, dlopen, const char *filename, int flag) {
     COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0);
 #  if SANITIZER_GLIBC
     if (common_flags()->test_only_replace_dlopen_main_program &&
-        internal_strcmp_suffix(__progname, filename)) {
+        internal_is_suffix(__progname, filename)) {
       VPrintf(1, "dlopen interceptor: replacing %s because it matches %s\n",
               filename, __progname);
       filename = (char *)0;  // RTLD_DEFAULT
diff --git a/compiler-rt/test/sanitizer_common/TestCases/replace_dlopen_main_program_test.cpp b/compiler-rt/test/sanitizer_common/TestCases/replace_dlopen_main_program_test.cpp
new file mode 100644
index 000000000000000..7e9e45836efbe53
--- /dev/null
+++ b/compiler-rt/test/sanitizer_common/TestCases/replace_dlopen_main_program_test.cpp
@@ -0,0 +1,29 @@
+// Test 'test_only_replace_dlopen_main_program' flag
+
+// RUN: %clangxx %s -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
+// REQUIRES: glibc
+
+// Does not intercept dlopen
+// UNSUPPORTED: hwasan, lsan, ubsan
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[]) {
+  // "If filename is NULL, then the returned handle is for the main program."
+  void *correct_handle = dlopen(NULL, RTLD_LAZY);
+
+  // Check that this is equivalent to dlopen(NULL, ...)
+  void *handle = dlopen(argv[0], RTLD_LAZY);
+  printf("dlopen(NULL,...): %p\n", correct_handle);
+  printf("dlopen(<main program>,...): %p\n", handle);
+  fflush(stdout);
+
+  if (handle != correct_handle)
+    return 1;
+
+  return 0;
+}



More information about the llvm-commits mailing list