Index: lib/asan/asan_flags.h =================================================================== --- lib/asan/asan_flags.h +++ lib/asan/asan_flags.h @@ -66,6 +66,7 @@ bool detect_container_overflow; int detect_odr_violation; bool dump_instruction_bytes; + bool reexec_done; }; extern Flags asan_flags_dont_use_directly; Index: lib/asan/asan_internal.h =================================================================== --- lib/asan/asan_internal.h +++ lib/asan/asan_internal.h @@ -72,7 +72,6 @@ // asan_linux.cc / asan_mac.cc / asan_win.cc void *AsanDoesNotSupportStaticLinkage(); -void AsanCheckDynamicRTPrereqs(); void AsanCheckIncompatibleRT(); void GetPcSpBp(void *context, uptr *pc, uptr *sp, uptr *bp); Index: lib/asan/asan_linux.cc =================================================================== --- lib/asan/asan_linux.cc +++ lib/asan/asan_linux.cc @@ -41,6 +41,11 @@ #if SANITIZER_ANDROID || SANITIZER_FREEBSD #include +#include +#if defined(__ANDROID_API__) && (__ANDROID_API__ < 20) +extern "C" int dl_iterate_phdr(int (*cb)(struct dl_phdr_info*, + size_t, void*), void*); +#endif extern "C" void* _DYNAMIC; #else #include @@ -68,22 +73,117 @@ namespace __asan { -void MaybeReexec() { - // No need to re-exec on Linux. +extern "C" { +SANITIZER_WEAK_ATTRIBUTE extern void *__libc_stack_end; +SANITIZER_WEAK_ATTRIBUTE extern char **environ; } -void *AsanDoesNotSupportStaticLinkage() { - // This will fail to link with -static. - return &_DYNAMIC; // defined in link.h +static char **GetEnviron() { + if (environ) + // Use libc global variable "environ" if it is already initialized + return environ; + if (&__libc_stack_end) { + // Use "__libc_stack_end" variable if it is available and initialized + uptr stack_end = (uptr)__libc_stack_end; + int argc = *(int *)stack_end; + return (char **)(stack_end + argc + 2); + } + // Parse stack for environment variables + char *proc_env; + uptr proc_env_size; + uptr proc_env_len = ReadFileToBuffer("/proc/self/environ", &proc_env, + &proc_env_size, 1 << 26); + uptr stack_top, stack_bottom; + GetThreadStackTopAndBottom(true, &stack_top, &stack_bottom); + char *sp = (char *)__builtin_frame_address(0); + for (; sp <= (char *)stack_top - proc_env_len; ++sp) + if (!internal_memcmp(sp, proc_env, proc_env_len)) + break; + // Get number of environment variables on stack + uptr num_vars = 0; + for (uptr env_off = 0; env_off < proc_env_len; + env_off += internal_strlen(sp + env_off) + 1, ++num_vars) { + } + // Create new array of environment variables strings + char **stack_environ = + (char **)MmapOrDie(sizeof(char *) * (num_vars + 1), __func__); + for (uptr i = 0; i < num_vars; ++i) { + stack_environ[i] = sp; + sp += internal_strlen(stack_environ[i]) + 1; + } + return stack_environ; +} + +static char *SkipDelim(const char *str) { + char *s = internal_strchr(str, ':'); + return s ? s++ : NULL; +} + +static void FillWithDummyPattern(char *str, uptr len) { + char dummy_name[] = "ASAN_DUMMY"; + uptr dummy_len = sizeof(dummy_name) - 1; + uptr i = 0; + if (dummy_len <= len) { + internal_strncpy(str, dummy_name, len); + i += dummy_len; + } + internal_memset(str + i, 'X', len - i); + str[len - 1] = '\0'; +} + +static bool RemoveFromColonList(char *str, const char *del) { + uptr del_len = internal_strlen(del); + while (str) { + char *del_start = internal_strstr(str, del); + if (!del_start) + return false; + // Skip partly matched fragment + if (del_start != str && del_start[-1] != ':') { + str = SkipDelim(del_start); + continue; + } + if (del_start[del_len] != '\0') { + if (del_start[del_len] == ':') { + // Remove separator following the value + del_len++; + } else { + str = SkipDelim(del_start); + continue; + } + } + // Delete value + char *del_end = del_start + del_len; + internal_memmove(del_start, del_end, internal_strlen(del_end) + 1); + uptr str_len = internal_strlen(str); + // Fill interspaces after delete with 'ASAN_DUMMYXXX' patterns + FillWithDummyPattern(str + str_len + 1, del_len); + return true; + } + return false; +} + +UNUSED static void RemoveFromEnv(const char **del_vars) { + char **environ = GetEnviron(); + for (const char **del_varp = del_vars; *del_varp; ++del_varp) { + const char *del_var = *del_varp; + // Check if current variable is present in environment + uptr lhs_len, rhs_len; + ParseEnvVar(del_var, &lhs_len, &rhs_len); + int var_idx = + EnvVarIdx(const_cast(environ), del_var, lhs_len); + if (var_idx < 0) + continue; + if (RemoveFromColonList(environ[var_idx] + lhs_len + 1, + del_var + lhs_len + 1) && + (internal_strlen(environ[var_idx]) == lhs_len + 1)) { + // Replace env variable with dummy one if it is blank after deletion + FillWithDummyPattern(environ[var_idx], lhs_len); + } + } } -#if SANITIZER_ANDROID -// FIXME: should we do anything for Android? -void AsanCheckDynamicRTPrereqs() {} -void AsanCheckIncompatibleRT() {} -#else static int FindFirstDSOCallback(struct dl_phdr_info *info, size_t size, - void *data) { + void *data) { // Continue until the first dynamic library is found if (!info->dlpi_name || info->dlpi_name[0] == 0) return 0; @@ -96,26 +196,109 @@ return 1; } -static bool IsDynamicRTName(const char *libname) { +UNUSED static bool IsDynamicRTName(const char *libname) { return internal_strstr(libname, "libclang_rt.asan") || internal_strstr(libname, "libasan.so"); } -static void ReportIncompatibleRT() { - Report("Your application is linked against incompatible ASan runtimes.\n"); - Die(); +static int FindAsanDSOCallback(struct dl_phdr_info *info, size_t size, + void *data) { + if (!info->dlpi_name || info->dlpi_name[0] == 0) + return 0; + *(const char **)data = info->dlpi_name; + return IsDynamicRTName(info->dlpi_name) ? 1 : 0; +} + +static const char *RTDSOName() { + // Get Asan dynamic RT library name + static char *asan_dso_name = 0; + if (!asan_dso_name) { + if (!dl_iterate_phdr(FindAsanDSOCallback, &asan_dso_name)) + asan_dso_name = 0; + } + return asan_dso_name; +} + +UNUSED static char *GetLdPreloadDSO() { + static char *LdPreloadRTEnv = NULL; + if (LdPreloadRTEnv) + return LdPreloadRTEnv; + const char *rhs = RTDSOName(); + CHECK_NE(rhs, NULL); + char lhs[] = "LD_PRELOAD="; + uptr lhs_len = sizeof(lhs) - 1; + uptr rhs_len = internal_strlen(rhs); + LdPreloadRTEnv = (char *)MmapOrDie( + lhs_len + rhs_len + 1, "LD_PRELOAD enviroment variable assignment"); + internal_strncpy(LdPreloadRTEnv, lhs, lhs_len); + internal_strncpy(LdPreloadRTEnv + lhs_len, rhs, rhs_len); + return LdPreloadRTEnv; } -void AsanCheckDynamicRTPrereqs() { +UNUSED static bool IsRTFirstDSO() { // Ensure that dynamic RT is the first DSO in the list const char *first_dso_name = 0; dl_iterate_phdr(FindFirstDSOCallback, &first_dso_name); - if (first_dso_name && !IsDynamicRTName(first_dso_name)) { - Report("ASan runtime does not come first in initial library list; " - "you should either link runtime to your application or " - "manually preload it with LD_PRELOAD.\n"); - Die(); + return first_dso_name && IsDynamicRTName(first_dso_name); +} + +void MaybeReexec() { +#if ASAN_DYNAMIC + const char ReexecDoneEnv[] = "ASAN_OPTIONS=reexec_done=true"; + const char *RTPreload = GetLdPreloadDSO(); + // Make sure the dynamic ASan runtime library is preloaded so that the + // wrappers work. If it is not but reexec is allowed, set LD_PRELOAD + // and re-exec ourselves. + const char *reexec_vars[] = {RTPreload, ReexecDoneEnv, NULL}; + if (IsRTFirstDSO()) { + // In case of reexec-ed run - disable added environment + if (flags()->reexec_done) { + RemoveFromEnv(reexec_vars); + } + } else { + if (flags()->reexec_done) { + // Already in reexec-ed run but ASan RT is not preloaded + Report("ASan runtime does not come first in initial library list; " + "reexecuting with LD_PRELOAD doesn't take effect; " + "you should link runtime to your application\n"); + Die(); + } else { + // In case of initial run try to reexec + if (flags()->allow_reexec) { + // Set ASan option "reexec_done=true" to prevent infinite reexec + // in case library preload is unsuccessful e.g. when capabilities + // are set the LD_* variables are disabled for security reasons + VReport(1, "exec()-ing the program with\n%s\n" + "to enable ASan wrappers.\nSet ASAN_OPTIONS=allow_reexec=0 " + "to disable this.\n", + reexec_vars[0]); + ReExec(reexec_vars); + } else { + // Reeexec is disallowed in Asan options + Report("ASan runtime does not come first in initial library list; " + "you should either link runtime to your application or " + "let ASan to reexec by setting ASAN_OPTIONS=allow_reexec=true " + "or manually preload runtime with LD_PRELOAD.\n"); + Die(); + } + } } +#endif +} + +void *AsanDoesNotSupportStaticLinkage() { + // This will fail to link with -static. + return &_DYNAMIC; // defined in link.h +} + +#if SANITIZER_ANDROID +// FIXME: should we do anything for Android? +void AsanCheckIncompatibleRT() {} +#else + +static void ReportIncompatibleRT() { + Report("Your application is linked against incompatible ASan runtimes.\n"); + Die(); } void AsanCheckIncompatibleRT() { Index: lib/asan/asan_mac.cc =================================================================== --- lib/asan/asan_mac.cc +++ lib/asan/asan_mac.cc @@ -200,9 +200,6 @@ } // No-op. Mac does not support static linkage anyway. -void AsanCheckDynamicRTPrereqs() {} - -// No-op. Mac does not support static linkage anyway. void AsanCheckIncompatibleRT() {} bool AsanInterceptsSignal(int signum) { Index: lib/asan/asan_rtl.cc =================================================================== --- lib/asan/asan_rtl.cc +++ lib/asan/asan_rtl.cc @@ -233,6 +233,11 @@ ParseFlag(str, &f->dump_instruction_bytes, "dump_instruction_bytes", "If true, dump 16 bytes starting at the instruction that caused SEGV"); + + ParseFlag(str, &f->reexec_done, "reexec_done", + "The internal flag" + "to indicate the process has beed reexeced in asan init", + false); } void InitializeFlags(Flags *f, const char *env) { @@ -708,7 +713,6 @@ public: // NOLINT AsanInitializer() { AsanCheckIncompatibleRT(); - AsanCheckDynamicRTPrereqs(); if (UNLIKELY(!asan_inited)) __asan_init(); } Index: lib/asan/asan_win.cc =================================================================== --- lib/asan/asan_win.cc +++ lib/asan/asan_win.cc @@ -71,8 +71,6 @@ return 0; } -void AsanCheckDynamicRTPrereqs() {} - void AsanCheckIncompatibleRT() {} void AsanPlatformThreadInit() { Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -183,10 +183,14 @@ bool FileExists(const char *filename); const char *GetEnv(const char *name); bool SetEnv(const char *name, const char *value); +#if SANITIZER_LINUX +void ParseEnvVar(const char *env, uptr *lhs_len, uptr *rhs_len); +int EnvVarIdx(const char **environ, const char *var, const uptr var_len); +#endif const char *GetPwd(); char *FindPathToBinary(const char *name); u32 GetUid(); -void ReExec(); +void ReExec(const char **add_envp = 0); bool StackSizeIsUnlimited(); void SetStackSizeLimitInBytes(uptr limit); bool AddressSpaceIsUnlimited(); Index: lib/sanitizer_common/sanitizer_flags.h =================================================================== --- lib/sanitizer_common/sanitizer_flags.h +++ lib/sanitizer_common/sanitizer_flags.h @@ -18,14 +18,14 @@ namespace __sanitizer { -void ParseFlag(const char *env, bool *flag, - const char *name, const char *descr); -void ParseFlag(const char *env, int *flag, - const char *name, const char *descr); -void ParseFlag(const char *env, uptr *flag, - const char *name, const char *descr); -void ParseFlag(const char *env, const char **flag, - const char *name, const char *descr); +void ParseFlag(const char *env, bool *flag, const char *name, const char *descr, + const bool visible = true); +void ParseFlag(const char *env, int *flag, const char *name, const char *descr, + const bool visible = true); +void ParseFlag(const char *env, uptr *flag, const char *name, const char *descr, + const bool visible = true); +void ParseFlag(const char *env, const char **flag, const char *name, + const char *descr, const bool visible = true); struct CommonFlags { bool symbolize; Index: lib/sanitizer_common/sanitizer_flags.cc =================================================================== --- lib/sanitizer_common/sanitizer_flags.cc +++ lib/sanitizer_common/sanitizer_flags.cc @@ -240,11 +240,12 @@ } } -void ParseFlag(const char *env, bool *flag, - const char *name, const char *descr) { +void ParseFlag(const char *env, bool *flag, const char *name, const char *descr, + const bool visible) { const char *value; int value_length; - AddFlagDescription(name, descr); + if (visible) + AddFlagDescription(name, descr); if (!GetFlagValue(env, name, &value, &value_length)) return; if (StartsWith(value, value_length, "0") || @@ -257,31 +258,34 @@ *flag = true; } -void ParseFlag(const char *env, int *flag, - const char *name, const char *descr) { +void ParseFlag(const char *env, int *flag, const char *name, const char *descr, + const bool visible) { const char *value; int value_length; - AddFlagDescription(name, descr); + if (visible) + AddFlagDescription(name, descr); if (!GetFlagValue(env, name, &value, &value_length)) return; *flag = static_cast(internal_atoll(value)); } -void ParseFlag(const char *env, uptr *flag, - const char *name, const char *descr) { +void ParseFlag(const char *env, uptr *flag, const char *name, const char *descr, + const bool visible) { const char *value; int value_length; - AddFlagDescription(name, descr); + if (visible) + AddFlagDescription(name, descr); if (!GetFlagValue(env, name, &value, &value_length)) return; *flag = static_cast(internal_atoll(value)); } -void ParseFlag(const char *env, const char **flag, - const char *name, const char *descr) { +void ParseFlag(const char *env, const char **flag, const char *name, + const char *descr, const bool visible) { const char *value; int value_length; - AddFlagDescription(name, descr); + if (visible) + AddFlagDescription(name, descr); if (!GetFlagValue(env, name, &value, &value_length)) return; // Copy the flag value. Don't use locks here, as flags are parsed at Index: lib/sanitizer_common/sanitizer_linux.cc =================================================================== --- lib/sanitizer_common/sanitizer_linux.cc +++ lib/sanitizer_common/sanitizer_linux.cc @@ -399,9 +399,84 @@ #endif } -void ReExec() { +// Parse environment variable string and get name and value lengths +void ParseEnvVar(const char *env, uptr *lhs_len, uptr *rhs_len) { + uptr env_len = internal_strlen(env); + const char *val = internal_strchr(env, '='); + CHECK_NE(val, 0); + *lhs_len = val - env; + *rhs_len = env_len - *lhs_len - 1; +} + +// Return index of environment variable in environ array +// or -1 if the variable is not set +int EnvVarIdx(const char **environ, const char *var, const uptr var_len) { + for (int var_idx = 0; environ[var_idx]; ++var_idx) + if ((!internal_strncmp(environ[var_idx], var, var_len)) && + (environ[var_idx][var_len] == '=')) + return var_idx; + return -1; +} + +// Append new value to colon separated list 'str' started with "name=" +// where name is 'lhs' chars len +static char *AppendToColonList(const char *str, uptr lhs_len, const char *add, + uptr add_len) { + int new_str_len = internal_strlen(str) + add_len - lhs_len; + char *new_str = (char *)MmapOrDie((new_str_len + 1) * sizeof(char), __func__); + // Set firstly new value, so the new assignment begins with "name=val" + internal_strncpy(new_str, add, add_len); + // Then append old values separated with ":" + const char *str_rhs = str + lhs_len + 1; + if (str_rhs) { + new_str[add_len] = ':'; + internal_strncpy(new_str + add_len + 1, str_rhs, internal_strlen(str_rhs)); + } + new_str[new_str_len] = '\0'; + return new_str; +} + +static char **AppendToEnv(const char **environ, const char **add_vars) { + // Count number of original environment variables + int num_vars; + for (num_vars = 0; environ[num_vars]; ++num_vars) { + } + // Count number of environment variables to be added + int num_add_vars; + for (num_add_vars = 0; add_vars[num_add_vars]; ++num_add_vars) { + } + // Copy original environment to newly allocated array + char **new_environ = (char **)MmapOrDie( + sizeof(char *) * (num_vars + num_add_vars + 1), __func__); + internal_memcpy(new_environ, environ, num_vars * sizeof(char *)); + // Iterate over array of environment variables to be added + for (const char **add_varp = add_vars; *add_varp; ++add_varp) { + const char *add_var = *add_varp; + // Parse original environment to check if "name" already was set + uptr lhs_len, rhs_len; + ParseEnvVar(add_var, &lhs_len, &rhs_len); + int var_idx = + EnvVarIdx(environ, add_var, lhs_len); + if (var_idx < 0) { + // Add new enviroment variable to the new environ array + new_environ[num_vars++] = const_cast(add_var); + } else { + // Environment variable "name" was already set + // Append "val:" to the beginning of existing "name" assignemnt + new_environ[var_idx] = AppendToColonList(environ[var_idx], lhs_len, + add_var, lhs_len + rhs_len + 1); + } + } + // Set the end of new environment variables array + new_environ[num_vars + 1] = NULL; + return new_environ; +} + +void ReExec(const char **add_envp) { char **argv, **envp; GetArgsAndEnv(&argv, &envp); + if (add_envp) + envp = AppendToEnv(const_cast(envp), add_envp); uptr rv = internal_execve("/proc/self/exe", argv, envp); int rverrno; CHECK_EQ(internal_iserror(rv, &rverrno), true); Index: test/asan/TestCases/Linux/asan_dlopen_test.cc =================================================================== --- test/asan/TestCases/Linux/asan_dlopen_test.cc +++ test/asan/TestCases/Linux/asan_dlopen_test.cc @@ -1,7 +1,7 @@ // Test that dlopen of dynamic runtime is prohibited. // // RUN: %clangxx %s -DRT=\"%shared_libasan\" -o %t -ldl -// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: ASAN_OPTIONS=allow_reexec=false not %run %t 2>&1 | FileCheck %s // REQUIRES: asan-dynamic-runtime // XFAIL: android Index: test/asan/TestCases/Linux/asan_reexec_test-1.cc =================================================================== --- /dev/null +++ test/asan/TestCases/Linux/asan_reexec_test-1.cc @@ -0,0 +1,44 @@ +// Test that non-sanitized executables work with sanitized shared libs +// without preloaded runtime by reexecuting the process. +// +// RUN: %clangxx -DBUILD_SO=1 -fPIC -shared %s -o %t.so +// RUN: %clangxx %s %t.so -o %t +// RUN: %clangxx_asan -DBUILD_SO=1 -fPIC -shared %s -o %t.so +// +// Check that re-exec with libasan preloaded works +// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-reexec +// +// Check that error is correctly reported when reexec is disabled +// RUN: ASAN_OPTIONS=allow_reexec=false not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-noreexec +// +// Check that LD_PRELOAD for libasan works +// RUN: LD_PRELOAD=%shared_libasan not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-reexec +// +// Check that reexec works in presense on non-empty ASAN_OPTIONS +// RUN: ASAN_OPTIONS=verbosity=1 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-verbose --check-prefix=CHECK-reexec +// +// REQUIRES: asan-dynamic-runtime + +// CHECK-noreexec: ASan runtime does not come first in initial library list +// CHECK-noreexec: let ASan to reexec by setting ASAN_OPTIONS=allow_reexec=true + +// CHECK-verbose: Parsed ASAN_OPTIONS: verbosity=1 +// CHECK-verbose: exec()-ing the program with +// CHECK-verbose: Parsed ASAN_OPTIONS: reexec_done=true:verbosity=1 + +#if BUILD_SO +char dummy; +void do_access(const void *p) { + dummy = ((const char *)p)[1]; + // CHECK-reexec: AddressSanitizer: heap-buffer-overflow +} +#else +#include +extern void do_access(const void *p); +int main(int argc, char **argv) { + void *p = malloc(1); + do_access(p); + free(p); + return 0; +} +#endif Index: test/asan/TestCases/Linux/asan_reexec_test-2.cc =================================================================== --- /dev/null +++ test/asan/TestCases/Linux/asan_reexec_test-2.cc @@ -0,0 +1,53 @@ +// Test that that reexec trick is invisible to application +// +// RUN: %clangxx -DBUILD_SO1=1 -fPIC -shared %s -o %t.1.so +// RUN: %clangxx %s %t.1.so -o %t +// RUN: %clangxx_asan -DBUILD_SO1=1 -fPIC -shared %s -o %t.1.so +// RUN: %clangxx -DBUILD_SO2=1 -fPIC -shared %s -o %t.2.so +// +// Check that LD_PRELOAD and ASAN_OPTIONS are not changed after reexec +// and original environment "DUMMY_ENV=abcd" is preserved +// RUN: ASAN_OPTIONS=verbosity=1 DUMMY_ENV=abcd LD_PRELOAD=%t.2.so %run %t 2>&1 | FileCheck %s +// +// REQUIRES: asan-dynamic-runtime + +// CHECK: Parsed ASAN_OPTIONS: verbosity=1 +// CHECK: exec()-ing the program with +// CHECK: Parsed ASAN_OPTIONS: reexec_done=true:verbosity=1 + +#include +#include +#if BUILD_SO1 +void print_asan_options() { + char *asan_options = getenv("ASAN_OPTIONS"); + printf("Asan options: %s\n", asan_options); + // CHECK-NOT: Asan options: {{.*reexec_done}} + // CHECK: Asan options: {{verbosity=1$}} +} +void print_dummy_env() { + char *dummy_env = getenv("DUMMY_ENV"); + printf("Dummy env: %s\n", dummy_env); + // CHECK: Dummy env: {{abcd$}} +} +void print_ld_preload() { + printf("Function is not implemented in this library\n"); + // CHECK-NOT: Function is not implemented in this library +} +#elif BUILD_SO2 +void print_ld_preload() { + char *ld_preload = getenv("LD_PRELOAD"); + printf("Ld preload: %s\n", ld_preload); + // CHECK-NOT: Ld preload: {{.*libclang_rt.asan}} + // CHECK: Ld preload: {{.*.2.so$}} +} +#else +extern void print_asan_options(); +extern void print_dummy_env(); +extern void print_ld_preload(); +int main(int argc, char **argv) { + print_asan_options(); + print_dummy_env(); + print_ld_preload(); + return 0; +} +#endif