Index: lib/sanitizer_common/CMakeLists.txt =================================================================== --- lib/sanitizer_common/CMakeLists.txt +++ lib/sanitizer_common/CMakeLists.txt @@ -27,6 +27,7 @@ sanitizer_suppressions.cc sanitizer_symbolizer.cc sanitizer_symbolizer_libbacktrace.cc + sanitizer_symbolizer_mac.cc sanitizer_symbolizer_win.cc sanitizer_tls_get_addr.cc sanitizer_thread_registry.cc Index: lib/sanitizer_common/sanitizer_symbolizer.h =================================================================== --- lib/sanitizer_common/sanitizer_symbolizer.h +++ lib/sanitizer_common/sanitizer_symbolizer.h @@ -184,7 +184,7 @@ char *SendCommandImpl(const char *command); bool ReadFromSymbolizer(char *buffer, uptr max_length); bool WriteToSymbolizer(const char *buffer, uptr length); - bool StartSymbolizerSubprocess(); + virtual bool StartSymbolizerSubprocess(); virtual bool ReachedEndOfOutput(const char *buffer, uptr length) const { UNIMPLEMENTED(); Index: lib/sanitizer_common/sanitizer_symbolizer_mac.h =================================================================== --- lib/sanitizer_common/sanitizer_symbolizer_mac.h +++ lib/sanitizer_common/sanitizer_symbolizer_mac.h @@ -0,0 +1,46 @@ +//===-- sanitizer_symbolizer_mac.h ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is shared between various sanitizers' runtime libraries. +// +// Header for Mac-specific "atos" symbolizer. +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_SYMBOLIZER_MAC_H +#define SANITIZER_SYMBOLIZER_MAC_H + +#include "sanitizer_symbolizer.h" + +namespace __sanitizer { + +class DlAddrSymbolizer : public SymbolizerInterface { + public: + bool SymbolizePC(uptr addr, SymbolizedStack *stack) override; + bool SymbolizeData(uptr addr, DataInfo *info) override; +}; + +class AtosSymbolizerProcess; + +class AtosSymbolizer : public SymbolizerInterface { + public: + explicit AtosSymbolizer(const char *path); + + bool SymbolizePC(uptr addr, SymbolizedStack *stack) override; + bool SymbolizeData(uptr addr, DataInfo *info) override; + + private: + AtosSymbolizerProcess *process_; + + static const uptr kBufferSize = 16 * 1024; + char buffer_[kBufferSize]; +}; + +} // namespace __sanitizer + +#endif // SANITIZER_SYMBOLIZER_MAC_H Index: lib/sanitizer_common/sanitizer_symbolizer_mac.cc =================================================================== --- lib/sanitizer_common/sanitizer_symbolizer_mac.cc +++ lib/sanitizer_common/sanitizer_symbolizer_mac.cc @@ -0,0 +1,207 @@ +//===-- sanitizer_symbolizer_mac.cc ---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is shared between various sanitizers' runtime libraries. +// +// Implementation of Mac-specific "atos" symbolizer. +//===----------------------------------------------------------------------===// + +#include "sanitizer_allocator_internal.h" +#include "sanitizer_symbolizer.h" +#include "sanitizer_symbolizer_mac.h" + +namespace __sanitizer { + +#if SANITIZER_MAC + +#include +#include +#include +#include +#include +#include + +bool DlAddrSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) { + Dl_info info; + int result = dladdr((const void *)addr, &info); + if (!result) return false; + stack->info.function = internal_strdup(info.dli_sname); + return true; +} + +bool DlAddrSymbolizer::SymbolizeData(uptr addr, DataInfo *info) { + return false; +} + +class AtosSymbolizerProcess : public SymbolizerProcess { + public: + explicit AtosSymbolizerProcess(const char *path, pid_t parent_pid) + : SymbolizerProcess(path), + parent_pid_(parent_pid), + fd_to_child_(kInvalidFd), + fork_failed_(false) {} + + void Shutdown() { + fd_to_child_ = input_fd_ = output_fd_ = kInvalidFd; + fork_failed_ = true; + } + + private: + bool StartSymbolizerSubprocess() override { + if (!FileExists(path_)) { + if (!reported_invalid_path_) { + Report("WARNING: invalid path to external symbolizer!\n"); + reported_invalid_path_ = true; + } + return false; + } + + if (fd_to_child_ != kInvalidFd) return true; + if (fork_failed_) return false; + + fd_t fd = kInvalidFd; + // Use forkpty to disable buffering in the new terminal. + int pid = forkpty(&fd, 0, 0, 0); + + if (pid == -1) { + // forkpty() failed. + Report("WARNING: failed to fork external symbolizer (errno: %d)\n", + errno); + fork_failed_ = true; + return false; + } else if (pid == 0) { + // Child subprocess. + ExecuteWithDefaultArgs(path_); + internal__exit(1); + } + + // Continue execution in parent process. + fd_to_child_ = input_fd_ = output_fd_ = fd; + + // Disable echo in the new terminal, disable CR. + struct termios termflags; + tcgetattr(fd_to_child_, &termflags); + termflags.c_oflag &= ~ONLCR; + termflags.c_lflag &= ~ECHO; + tcsetattr(fd_to_child_, TCSANOW, &termflags); + + // Check that symbolizer subprocess started successfully. + int pid_status; + SleepForMillis(kSymbolizerStartupTimeMillis); + int exited_pid = waitpid(pid, &pid_status, WNOHANG); + if (exited_pid != 0) { + // Either waitpid failed, or child has already exited. + Report("WARNING: external symbolizer didn't start up correctly!\n"); + return false; + } + + return true; + } + + bool ReachedEndOfOutput(const char *buffer, uptr length) const override { + return (length >= 1 && buffer_[length - 1] == '\n'); + } + + void ExecuteWithDefaultArgs(const char *path_to_binary) const override { + // The `atos` binary has some issues with DYLD_ROOT_PATH on i386. + unsetenv("DYLD_ROOT_PATH"); + + char pid_str[16] = {0}; + internal_snprintf(pid_str, sizeof(pid_str), "%d", parent_pid_); + execl(path_, path_, "-p", pid_str, (char *)0); + } + + pid_t parent_pid_; + fd_t fd_to_child_; + bool fork_failed_; +}; + +bool ParseCommandOutput(const char *str, SymbolizedStack *res) { + // Trim ending newlines. + char *trim; + ExtractTokenUpToDelimiter(str, "\n", &trim); + + // The line from `atos` is in one of these formats: + // myfunction (in library.dylib) (sourcefile.c:17) + // myfunction (in library.dylib) + 0x1fe + // 0xdeadbeef (in library.dylib) + 0x1fe + // 0xdeadbeef (in library.dylib) + // 0xdeadbeef + + if (internal_strstr(trim, "atos cannot examine process") || + internal_strstr(trim, "unable to get permission to examine process") || + internal_strstr(trim, "An admin user name and password is required") || + internal_strstr(trim, "could not load inserted library") || + internal_strstr(trim, "architecture mismatch between analysis process")) { + Report("atos returned an error: %s\n", trim); + return false; + } + + const char *rest = trim; + char *function_name; + rest = ExtractTokenUpToDelimiter(rest, " (in ", &function_name); + if (internal_strncmp(function_name, "0x", 2) != 0) + res->info.function = function_name; + else + InternalFree(function_name); + rest = ExtractTokenUpToDelimiter(rest, ") ", &res->info.module); + + if (rest[0] == '(') { + rest++; + rest = ExtractTokenUpToDelimiter(rest, ":", &res->info.file); + char *extracted_line_number; + rest = ExtractTokenUpToDelimiter(rest, ")", &extracted_line_number); + res->info.line = internal_atoll(extracted_line_number); + InternalFree(extracted_line_number); + } + + InternalFree(trim); + return true; +} + +AtosSymbolizer::AtosSymbolizer(const char *path) + : process_(new AtosSymbolizerProcess(path, getpid())) {} + +bool AtosSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) { + internal_snprintf(buffer_, kBufferSize, "0x%zx\n", addr); + char *buf = process_->SendCommand(buffer_); + if (!buf) return false; + bool result = ParseCommandOutput(buf, stack); + if (!result) { + process_->Shutdown(); + return false; + } + return true; +} + +bool AtosSymbolizer::SymbolizeData(uptr addr, DataInfo *info) { return false; } + +#else // SANITIZER_MAC + +bool DlAddrSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) { + return false; +} + +bool DlAddrSymbolizer::SymbolizeData(uptr addr, DataInfo *info) { + return false; +} + +AtosSymbolizer::AtosSymbolizer(const char *path) : process_(nullptr) { + UNIMPLEMENTED(); +} + +bool AtosSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) { + return false; +} + +bool AtosSymbolizer::SymbolizeData(uptr addr, DataInfo *info) { return false; } + +#endif // SANITIZER_MAC + +} // namespace __sanitizer Index: lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc +++ lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc @@ -23,6 +23,7 @@ #include "sanitizer_procmaps.h" #include "sanitizer_symbolizer.h" #include "sanitizer_symbolizer_libbacktrace.h" +#include "sanitizer_symbolizer_mac.h" #include #include @@ -781,6 +782,16 @@ AddSymbolizerIntoChain(&sym_first, &sym_last, libbacktrace_symbolizer); } + if (SANITIZER_MAC) { + const char *path = FindPathToBinary("atos"); + if (path) { + VReport(2, "Using atos symbolizer.\n"); + AtosSymbolizer *atos_symbolizer = + new(symbolizer_allocator_) AtosSymbolizer(path); + AddSymbolizerIntoChain(&sym_first, &sym_last, atos_symbolizer); + } + } + SymbolizerInterface *external_symbolizer = nullptr; const char *path_to_external = common_flags()->external_symbolizer_path; if (!(path_to_external && path_to_external[0] == '\0')) { @@ -805,6 +816,12 @@ AddSymbolizerIntoChain(&sym_first, &sym_last, external_symbolizer); } + if (SANITIZER_MAC) { + DlAddrSymbolizer *dladdr_symbolizer = + new(symbolizer_allocator_) DlAddrSymbolizer(); + AddSymbolizerIntoChain(&sym_first, &sym_last, dladdr_symbolizer); + } + return new(symbolizer_allocator_) POSIXSymbolizer(sym_first); } Index: test/asan/TestCases/Darwin/dyld-root-path.cc =================================================================== --- test/asan/TestCases/Darwin/dyld-root-path.cc +++ test/asan/TestCases/Darwin/dyld-root-path.cc @@ -0,0 +1,18 @@ +// Check that when having a DYLD_ROOT_PATH set, the symbolizer still works. +// RUN: %clangxx_asan -O0 %s -o %t -framework Foundation +// RUN: DYLD_ROOT_PATH="/" ASAN_OPTIONS=verbosity=2 ASAN_SYMBOLIZER_PATH=/dev/null/NONEXISTENT \ +// RUN: not %run %t 2>&1 | FileCheck %s + +#include + +// CHECK: Using atos symbolizer. + +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: {{AddressSanitizer: heap-use-after-free on address}} + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK-NOT: atos returned an error + // CHECK: {{#0 0x.* in main .*dyld-root-path.cc:}}[[@LINE-4]] +} Index: test/asan/TestCases/Darwin/sandbox-symbolizer.cc =================================================================== --- test/asan/TestCases/Darwin/sandbox-symbolizer.cc +++ test/asan/TestCases/Darwin/sandbox-symbolizer.cc @@ -0,0 +1,31 @@ +// In a non-forking sandbox, we can't spawn an external symbolizer, but dladdr() +// should still work and provide function names. No line numbers though. + +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny process-fork)' %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -O3 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny process-fork)' %t 2>&1 | FileCheck %s + +// `atos` symbolizer can't inspect a process that has an inaccessible task port, +// in which case we should again fallback to dladdr gracefully. + +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny mach-priv-task-port)' %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -O3 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny mach-priv-task-port)' %t 2>&1 | FileCheck %s + +#include +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: {{.*ERROR: AddressSanitizer: heap-use-after-free on address}} + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK: {{ #0 0x.* in main}} + // CHECK: {{freed by thread T0 here:}} + // CHECK: {{ #0 0x.* in wrap_free}} + // CHECK: {{ #1 0x.* in main}} + // CHECK: {{previously allocated by thread T0 here:}} + // CHECK: {{ #0 0x.* in wrap_malloc}} + // CHECK: {{ #1 0x.* in main}} +} Index: test/asan/TestCases/Darwin/suppressions-sandbox.cc =================================================================== --- test/asan/TestCases/Darwin/suppressions-sandbox.cc +++ test/asan/TestCases/Darwin/suppressions-sandbox.cc @@ -0,0 +1,26 @@ +// Check that without suppressions, we catch the issue. +// RUN: %clangxx_asan -O0 %s -o %t -framework Foundation +// RUN: not %run %t 2>&1 | FileCheck --check-prefix=CHECK-CRASH %s + +// Check that suppressing a function name works within a no-fork sandbox +// RUN: echo "interceptor_via_fun:CFStringCreateWithBytes" > %t.supp +// RUN: ASAN_OPTIONS=suppressions=%t.supp \ +// RUN: sandbox-exec -p '(version 1)(allow default)(deny process-fork)' \ +// RUN: %run %t 2>&1 | FileCheck --check-prefix=CHECK-IGNORE %s + +#include + +int main() { + char *a = (char *)malloc(6); + strcpy(a, "hello"); + CFStringRef str = + CFStringCreateWithBytes(kCFAllocatorDefault, (unsigned char *)a, 10, + kCFStringEncodingUTF8, FALSE); // BOOM + fprintf(stderr, "Ignored.\n"); + free(a); +} + +// CHECK-CRASH: AddressSanitizer: heap-buffer-overflow +// CHECK-CRASH-NOT: Ignored. +// CHECK-IGNORE-NOT: AddressSanitizer: heap-buffer-overflow +// CHECK-IGNORE: Ignored. Index: test/asan/TestCases/closed-fds.cc =================================================================== --- test/asan/TestCases/closed-fds.cc +++ test/asan/TestCases/closed-fds.cc @@ -0,0 +1,31 @@ +// Check that when the program closed its std(in|out|err), running the external +// symbolizer still works. + +// RUN: rm -f %t.log.* +// RUN: %clangxx_asan -O0 %s -o %t 2>&1 && ASAN_OPTIONS=log_path=%t.log:verbosity=2 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OUTPUT +// RUN: FileCheck %s --check-prefix=CHECK-FILE < %t.log.* + +#include +#include +#include +#include + +int main(int argc, char **argv) { + fprintf(stderr, "Closing streams.\n"); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + fprintf(stderr, "Can you hear me now?\n"); + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + x[argc] = 'X'; // BOOM + return 0; +} + +// CHECK-OUTPUT: Closing streams. +// CHECK-OUTPUT-NOT: Can you hear me now? +// CHECK-FILE: {{.*ERROR: AddressSanitizer: heap-use-after-free on address}} +// CHECK-FILE: {{0x.* at pc 0x.* bp 0x.* sp 0x.*}} +// CHECK-FILE: {{WRITE of size 1 at 0x.* thread T0}} +// CHECK-FILE: Using llvm-symbolizer at path: {{.*}} +// CHECK-FILE: {{ #0 0x.* in main .*closed-fds.cc:}}[[@LINE-10]] Index: test/asan/TestCases/suppressions-function.cc =================================================================== --- test/asan/TestCases/suppressions-function.cc +++ test/asan/TestCases/suppressions-function.cc @@ -12,6 +12,7 @@ #include #include +__attribute__((noinline)) // some symbolizers don't work with inlined functions void crash_function() { char *a = (char *)malloc(6); free(a);