Skip to content

Commit 0be4c6b

Browse files
authored
[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 <[email protected]>
1 parent b34d31d commit 0be4c6b

File tree

7 files changed

+152
-3
lines changed

7 files changed

+152
-3
lines changed

compiler-rt/lib/sanitizer_common/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ set(SANITIZER_NOLIBC_SOURCES
5959
set(SANITIZER_LIBCDEP_SOURCES
6060
sanitizer_common_libcdep.cpp
6161
sanitizer_allocator_checks.cpp
62+
sanitizer_dl.cpp
6263
sanitizer_linux_libcdep.cpp
6364
sanitizer_mac_libcdep.cpp
6465
sanitizer_posix_libcdep.cpp
@@ -139,6 +140,7 @@ set(SANITIZER_IMPL_HEADERS
139140
sanitizer_deadlock_detector_interface.h
140141
sanitizer_dense_map.h
141142
sanitizer_dense_map_info.h
143+
sanitizer_dl.h
142144
sanitizer_errno.h
143145
sanitizer_errno_codes.h
144146
sanitizer_file.h

compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,17 @@
3333
// COMMON_INTERCEPTOR_STRERROR
3434
//===----------------------------------------------------------------------===//
3535

36+
#include <stdarg.h>
37+
3638
#include "interception/interception.h"
3739
#include "sanitizer_addrhashmap.h"
40+
#include "sanitizer_dl.h"
3841
#include "sanitizer_errno.h"
3942
#include "sanitizer_placement_new.h"
4043
#include "sanitizer_platform_interceptors.h"
4144
#include "sanitizer_symbolizer.h"
4245
#include "sanitizer_tls_get_addr.h"
4346

44-
#include <stdarg.h>
45-
4647
#if SANITIZER_INTERCEPTOR_HOOKS
4748
#define CALL_WEAK_INTERCEPTOR_HOOK(f, ...) f(__VA_ARGS__);
4849
#define DECLARE_WEAK_INTERCEPTOR_HOOK(f, ...) \
@@ -6307,7 +6308,36 @@ INTERCEPTOR(int, fclose, __sanitizer_FILE *fp) {
63076308
INTERCEPTOR(void*, dlopen, const char *filename, int flag) {
63086309
void *ctx;
63096310
COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, dlopen, filename, flag);
6310-
if (filename) COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0);
6311+
6312+
if (filename) {
6313+
COMMON_INTERCEPTOR_READ_STRING(ctx, filename, 0);
6314+
6315+
# if !SANITIZER_DYNAMIC
6316+
// We care about a very specific use-case: dladdr on
6317+
// statically-linked ASan may return <main program>
6318+
// instead of the library.
6319+
// We therefore only take effect if the sanitizer is statically
6320+
// linked, and we don't bother canonicalizing paths because
6321+
// dladdr should return the same address both times (we assume
6322+
// the user did not canonicalize the result from dladdr).
6323+
if (common_flags()->test_only_replace_dlopen_main_program) {
6324+
VPrintf(1, "dlopen interceptor: filename: %s\n", filename);
6325+
6326+
const char *SelfFName = DladdrSelfFName();
6327+
VPrintf(1, "dlopen interceptor: DladdrSelfFName: %p %s\n",
6328+
(void *)SelfFName, SelfFName);
6329+
6330+
if (internal_strcmp(SelfFName, filename) == 0) {
6331+
// It's possible they copied the string from dladdr, so
6332+
// we do a string comparison rather than pointer comparison.
6333+
VPrintf(1, "dlopen interceptor: replacing %s because it matches %s\n",
6334+
filename, SelfFName);
6335+
filename = (char *)0; // RTLD_DEFAULT
6336+
}
6337+
}
6338+
# endif // !SANITIZER_DYNAMIC
6339+
}
6340+
63116341
void *res = COMMON_INTERCEPTOR_DLOPEN(filename, flag);
63126342
Symbolizer::GetOrInit()->InvalidateModuleList();
63136343
COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, res);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===-- sanitizer_dl.cpp --------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file has helper functions that depend on libc's dynamic loading
10+
// introspection.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "sanitizer_dl.h"
15+
16+
#include <dlfcn.h>
17+
18+
#include "sanitizer_common/sanitizer_platform.h"
19+
20+
namespace __sanitizer {
21+
extern const char *SanitizerToolName;
22+
23+
const char *DladdrSelfFName(void) {
24+
#if SANITIZER_GLIBC
25+
Dl_info info;
26+
int ret = dladdr((void *)&SanitizerToolName, &info);
27+
if (ret) {
28+
return info.dli_fname;
29+
}
30+
#endif
31+
32+
return nullptr;
33+
}
34+
35+
} // namespace __sanitizer
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//===-- sanitizer_dl.h ----------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file has helper functions that depend on libc's dynamic loading
10+
// introspection.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef SANITIZER_DL_H
15+
#define SANITIZER_DL_H
16+
17+
namespace __sanitizer {
18+
19+
// Returns the path to the shared object or - in the case of statically linked
20+
// sanitizers
21+
// - the main program itself, that contains the sanitizer.
22+
const char* DladdrSelfFName(void);
23+
24+
} // namespace __sanitizer
25+
26+
#endif // SANITIZER_DL_H

compiler-rt/lib/sanitizer_common/sanitizer_flags.inc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,9 @@ COMMON_FLAG(bool, detect_write_exec, false,
269269
COMMON_FLAG(bool, test_only_emulate_no_memorymap, false,
270270
"TEST ONLY fail to read memory mappings to emulate sanitized "
271271
"\"init\"")
272+
// With static linking, dladdr((void*)pthread_join) or similar will return the
273+
// path to the main program. This flag will replace dlopen(<main program,...>
274+
// with dlopen(NULL,...), which is the correct way to get a handle to the main
275+
// program.
276+
COMMON_FLAG(bool, test_only_replace_dlopen_main_program, false,
277+
"TEST ONLY replace dlopen(<main program>,...) with dlopen(NULL)")
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Test 'test_only_replace_dlopen_main_program' flag
2+
3+
// RUN: %clangxx %s -pie -fPIE -o %t
4+
// RUN: env %tool_options='test_only_replace_dlopen_main_program=true' %run %t
5+
// RUN: env %tool_options='test_only_replace_dlopen_main_program=false' not %run %t
6+
7+
// dladdr is 'nonstandard GNU extensions that are also present on Solaris'
8+
// REQUIRES: glibc
9+
10+
// Does not intercept dlopen
11+
// UNSUPPORTED: hwasan, lsan, ubsan
12+
13+
// Flag has no effect with dynamic runtime
14+
// UNSUPPORTED: asan-dynamic-runtime
15+
16+
#include <dlfcn.h>
17+
#include <stdio.h>
18+
#include <stdlib.h>
19+
20+
// We can't use the address of 'main' (error: ISO C++ does not allow 'main' to be used by a program [-Werror,-Wmain]')
21+
// so we add this function.
22+
__attribute__((noinline, no_sanitize("address"))) void foo() {
23+
printf("Hello World!\n");
24+
}
25+
26+
int main(int argc, char *argv[]) {
27+
foo();
28+
29+
// "If filename is NULL, then the returned handle is for the main program."
30+
void *correct_handle = dlopen(NULL, RTLD_LAZY);
31+
printf("dlopen(NULL,...): %p\n", correct_handle);
32+
33+
Dl_info info;
34+
if (dladdr((void *)&foo, &info) == 0) {
35+
printf("dladdr failed\n");
36+
return 1;
37+
}
38+
printf("dladdr(&foo): %s\n", info.dli_fname);
39+
void *test_handle = dlopen(info.dli_fname, RTLD_LAZY);
40+
printf("dlopen(%s,...): %p\n", info.dli_fname, test_handle);
41+
42+
if (test_handle != correct_handle) {
43+
printf("Error: handles do not match\n");
44+
return 1;
45+
}
46+
47+
return 0;
48+
}

llvm/utils/gn/secondary/compiler-rt/lib/sanitizer_common/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ source_set("sources") {
5454
"sanitizer_deadlock_detector_interface.h",
5555
"sanitizer_dense_map.h",
5656
"sanitizer_dense_map_info.h",
57+
"sanitizer_dl.cpp",
58+
"sanitizer_dl.h",
5759
"sanitizer_errno.cpp",
5860
"sanitizer_errno.h",
5961
"sanitizer_errno_codes.h",

0 commit comments

Comments
 (0)