diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.h b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.h @@ -35,6 +35,7 @@ bool SymbolizePC(uptr addr, SymbolizedStack *stack) override; bool SymbolizeData(uptr addr, DataInfo *info) override; + void LateInitialize() override; private: AtosSymbolizerProcess *process_; diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.cpp @@ -14,17 +14,18 @@ #include "sanitizer_platform.h" #if SANITIZER_MAC -#include "sanitizer_allocator_internal.h" -#include "sanitizer_mac.h" -#include "sanitizer_symbolizer_mac.h" - #include #include +#include #include #include #include #include +#include "sanitizer_allocator_internal.h" +#include "sanitizer_mac.h" +#include "sanitizer_symbolizer_mac.h" + namespace __sanitizer { bool DlAddrSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) { @@ -49,12 +50,28 @@ datainfo->start = (uptr)info.dli_saddr; return true; } +static const char kAtosEnvVar[] = "__check_mach_ports_lookup"; class AtosSymbolizerProcess : public SymbolizerProcess { public: explicit AtosSymbolizerProcess(const char *path) : SymbolizerProcess(path, /*use_posix_spawn*/ true) { pid_str_[0] = '\0'; + const char dummy_pid_str[] = "000000000000000"; + static_assert(sizeof(dummy_pid_str) == sizeof(pid_str_), + "sizes must match"); + updateAtosEnvVar(dummy_pid_str); + } + + void LateInitialize() { + if (SANITIZER_IOSSIM) { + // `putenv()` may call malloc/realloc so it is only safe to do this during + // LateInitialize() or later (i.e. we can't do this in the constructor). + // We use `putenv()` rather than `setenv()` so that we can later directly + // write into the storage without LibC getting involved to change what the + // variable is set to + CHECK_EQ(putenv(atos_mach_port_env_var_entry_), 0); + } } private: @@ -65,9 +82,54 @@ // the call to GetArgV. internal_snprintf(pid_str_, sizeof(pid_str_), "%d", internal_getpid()); + if (SANITIZER_IOSSIM) { + // `atos` in the simulator is restricted in its ability to retrieve the + // task port for the target process (us) so we need to do extra work + // to pass our task port to it. + mach_port_t ports[]{mach_task_self()}; + kern_return_t ret = + mach_ports_register(mach_task_self(), ports, /*count=*/1); + CHECK_EQ(ret, KERN_SUCCESS); + + // Set environment variable that signals to `atos` that it should look + // for our task port. We can't call `setenv()` here because it might call + // malloc/realloc. To avoid that we instead update the + // `atos_mach_port_env_var_entry_` variable with our current PID. + const char *current_atos_env_var = getenv(kAtosEnvVar); + if (current_atos_env_var != + getValueFromEnvEntry(atos_mach_port_env_var_entry_)) { + // The environment variable isn't set as expected. Something touched it + // so give up with symbolization. + Report( + "The %s environment variable was expected to be %p but was %p. " + "Symbolization will fail.\n", + kAtosEnvVar, atos_mach_port_env_var_entry_, current_atos_env_var); + return false; + } + updateAtosEnvVar(pid_str_); + } + return SymbolizerProcess::StartSymbolizerSubprocess(); } + void updateAtosEnvVar(const char *pid_str) { + uptr count = internal_snprintf(atos_mach_port_env_var_entry_, + sizeof(atos_mach_port_env_var_entry_), + "%s=%s", kAtosEnvVar, pid_str); + CHECK_GE(count, sizeof(kAtosEnvVar) + 1); + } + + const char *getValueFromEnvEntry(const char *entry) { + if (!entry) + return nullptr; + const char *c = entry; + while (*c && *c != '=') { + ++c; + } + CHECK_EQ(*c, '='); + return ++c; + } + bool ReachedEndOfOutput(const char *buffer, uptr length) const override { return (length >= 1 && buffer[length - 1] == '\n'); } @@ -88,6 +150,8 @@ } char pid_str_[16]; + // Space for `\0` in `kAtosEnvVar` is reused for `=`. + char atos_mach_port_env_var_entry_[sizeof(kAtosEnvVar) + sizeof(pid_str_)]; }; static bool ParseCommandOutput(const char *str, uptr addr, char **out_name, @@ -191,6 +255,8 @@ return true; } +void AtosSymbolizer::LateInitialize() { process_->LateInitialize(); } + } // namespace __sanitizer #endif // SANITIZER_MAC diff --git a/compiler-rt/test/tsan/Darwin/no_call_setenv_in_symbolize.cpp b/compiler-rt/test/tsan/Darwin/no_call_setenv_in_symbolize.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tsan/Darwin/no_call_setenv_in_symbolize.cpp @@ -0,0 +1,56 @@ +// RUN: %clangxx_tsan -O1 %s -o %t +// `handle_sigbus=0` is required because when the rdar://problem/58789439 bug was +// present TSan's runtime could derefence bad memory leading to SIGBUS being raised. +// If the signal was caught TSan would deadlock because it would try to run the +// symbolizer again. +// RUN: %env_tsan_opts=handle_sigbus=0,symbolize=1 %run %t 2>&1 | FileCheck %s +// RUN: %env_tsan_opts=handle_sigbus=0,symbolize=1 __check_mach_ports_lookup=some_value %run %t 2>&1 | FileCheck %s +// +// Check we can fail gracefully if a program messes with our environment variable. +// RUN: %clangxx_tsan -DMESS_WITH_ATOS -O1 %s -o %t_mess_with_atos +// RUN: %env_tsan_opts=handle_sigbus=0,symbolize=1 %run %t_mess_with_atos 2>&1 | FileCheck -check-prefix=CHECK-MESS-WITH-ATOS %s +#include +#include +#include + +const char *kEnvName = "__UNLIKELY_ENV_VAR_NAME__"; + +int main() { + if (getenv(kEnvName)) { + fprintf(stderr, "Env var %s should not be set\n", kEnvName); + abort(); + } + + // This will set an environment variable that isn't already in + // the environment array. This will cause Darwin's Libc to + // malloc() a new array. + if (setenv(kEnvName, "some_value", /*overwrite=*/1)) { + fprintf(stderr, "Failed to set %s \n", kEnvName); + abort(); + } + +#ifdef MESS_WITH_ATOS + // CHECK-MESS-WITH-ATOS: The __check_mach_ports_lookup environment variable was expected to be 0x{{[0-9a-f]+}} but was 0x{{[0-9a-f]+}}. Symbolization will fail. + // Deliberately mess with the an implementation detail. + if (unsetenv("__check_mach_ports_lookup")) { + fprintf(stderr, "Failed to set %s \n", kEnvName); + abort(); + } +#endif + + // rdar://problem/58789439 + // Now trigger symbolization. If symbolization tries to call + // to `setenv` that adds a new environment variable, then Darwin + // Libc will call `realloc()` and TSan's runtime will hit + // an assertion failure because TSan's runtime uses a different + // allocator during symbolization which leads to `realloc()` being + // called on a pointer that the allocator didn't allocate. + // + // CHECK: #{{[0-9]}} main {{.*}}no_call_setenv_in_symbolize.cpp:[[@LINE+1]] + __sanitizer_print_stack_trace(); + + // CHECK: DONE + fprintf(stderr, "DONE\n"); + + return 0; +}