Skip to content

Commit

Permalink
[sanitizer_common] Add experimental flag to tweak dlopen(<main progra…
Browse files Browse the repository at this point in the history
…m>) (llvm#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@google.com>
  • Loading branch information
2 people authored and zahiraam committed Nov 20, 2023
1 parent 12c2d96 commit e05b1a7
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 3 deletions.
2 changes: 2 additions & 0 deletions compiler-rt/lib/sanitizer_common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
36 changes: 33 additions & 3 deletions compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...) \
Expand Down Expand Up @@ -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);
Expand Down
35 changes: 35 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_dl.cpp
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_dl.h
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit e05b1a7

Please sign in to comment.