[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@google.com>
This commit is contained in:
Thurston Dang 2023-11-09 12:53:06 -08:00 committed by GitHub
parent b34d31d2e1
commit 0be4c6b948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 152 additions and 3 deletions

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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)")

View File

@ -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;
}

View File

@ -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",