diff options
Diffstat (limited to 'lib/asan')
77 files changed, 12051 insertions, 0 deletions
diff --git a/lib/asan/Makefile.mk b/lib/asan/Makefile.mk new file mode 100644 index 000000000000..4d9e58d6746f --- /dev/null +++ b/lib/asan/Makefile.mk @@ -0,0 +1,22 @@ +#===- lib/asan/Makefile.mk ---------------------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +ModuleName := asan +SubDirs := mach_override sysinfo + +Sources := $(foreach file,$(wildcard $(Dir)/*.cc),$(notdir $(file))) +ObjNames := $(Sources:%.cc=%.o) + +Implementation := Generic + +# FIXME: use automatic dependencies? +Dependencies := $(wildcard $(Dir)/*.h) + +# Define a convenience variable for all the asan functions. +AsanFunctions := $(Sources:%.cc=%) diff --git a/lib/asan/Makefile.old b/lib/asan/Makefile.old new file mode 100644 index 000000000000..a96ff424fa0d --- /dev/null +++ b/lib/asan/Makefile.old @@ -0,0 +1,352 @@ +#===- lib/asan/Makefile.old --------------------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +OS=$(shell uname | tr '[A-Z]' '[a-z]') +ROOT=$(shell pwd) +MAKEFILE=Makefile.old # this file. + +ifeq ($(ARCH), android) + ANDROID_CFLAGS= \ + -DANDROID \ + -D__WORDSIZE=32 \ + -I$(ANDROID_BUILD_TOP)/external/stlport/stlport \ + -I$(ANDROID_BUILD_TOP)/bionic \ + -I$(ANDROID_BUILD_TOP)/bionic/libstdc++/include \ + -I$(ANDROID_BUILD_TOP)/bionic/libc/arch-arm/include \ + -I$(ANDROID_BUILD_TOP)/bionic/libc/include \ + -I$(ANDROID_BUILD_TOP)/bionic/libc/kernel/common \ + -I$(ANDROID_BUILD_TOP)/bionic/libc/kernel/arch-arm \ + -I$(ANDROID_BUILD_TOP)/bionic/libm/include \ + -I$(ANDROID_BUILD_TOP)/bionic/libm/include/arm \ + -I$(ANDROID_BUILD_TOP)/bionic/libthread_db/include \ + -L$(ANDROID_PRODUCT_OUT)/obj/lib + CLANG_FLAGS= \ + -ccc-host-triple arm-linux-androideabi \ + -D__compiler_offsetof=__builtin_offsetof \ + -D__ELF__=1 \ + -ccc-gcc-name arm-linux-androideabi-g++ \ + $(ANDROID_CFLAGS) + CC=$(ANDROID_EABI_TOOLCHAIN)/arm-linux-androideabi-gcc $(ANDROID_CFLAGS) + CXX=$(ANDROID_EABI_TOOLCHAIN)/arm-linux-androideabi-g++ $(ANDROID_CFLAGS) +endif + +ifeq ($(ARCH), arm) + # Example make command line: + # CROSSTOOL=$HOME/x-tools/arm-unknown-linux-gnueabi/ PATH=$CROSSTOOL/bin:$PATH make ARCH=arm asan_test + CLANG_FLAGS= \ + -ccc-host-triple arm-unknown-linux-gnueabi \ + -march=armv7-a -mfloat-abi=softfp -mfp=neon \ + -ccc-gcc-name arm-unknown-linux-gnueabi-g++ \ + -B$(CROSSTOOL)/lib/gcc/arm-unknown-linux-gnueabi/4.4.4 \ + -B$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/usr/lib \ + -I$(CROSSTOOL)/lib/gcc/arm-unknown-linux-gnueabi/4.4.4/include \ + -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/include/c++/4.4.4 \ + -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/include/c++/4.4.4/arm-unknown-linux-gnueabi \ + -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/include \ + -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/usr/include \ + -L$(CROSSTOOL)/lib/gcc/arm-unknown-linux-gnueabi/4.4.4 \ + -L$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/lib \ + -L$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/usr/lib + CC=$(CROSSTOOL)/bin/arm-unknown-linux-gnueabi-gcc + CXX=$(CROSSTOOL)/bin/arm-unknown-linux-gnueabi-g++ +endif + +CLANG_FLAGS= +CLANG_BUILD=$(ROOT)/../../../../build/Release+Asserts +CLANG_CC=$(CLANG_BUILD)/bin/clang $(CLANG_FLAGS) +CLANG_CXX=$(CLANG_BUILD)/bin/clang++ $(CLANG_FLAGS) + +CC=$(CLANG_CC) +CXX=$(CLANG_CXX) + +CFLAGS:=-Wall -fvisibility=hidden + +CLEANROOM_CXX=$(CXX) -Wall + +INSTALL_DIR=../asan_clang_$(OS) +BIN=bin_$(OS) + +LIBS=#-lpthread -ldl +ARCH=x86_64 + +ASAN_STACK=1 +ASAN_GLOBALS=1 +ASAN_USE_CALL=1 +ASAN_SCALE=0 # default will be used +ASAN_OFFSET=-1 #default will be used +ASAN_UAR=0 +ASAN_HAS_EXCEPTIONS=1 +ASAN_FLEXIBLE_MAPPING_AND_OFFSET=0 +ASAN_HAS_BLACKLIST=1 +ASAN_NEEDS_SEGV=1 +ASAN_PIE=0 + +ifeq ($(ARCH), i386) +BITS=32 +SUFF=$(BITS) +CFLAGS:=$(CFLAGS) -m$(BITS) +endif + +ifeq ($(ARCH), x86_64) +BITS=64 +SUFF=$(BITS) +CFLAGS:=$(CFLAGS) -m$(BITS) +endif + +ifeq ($(ARCH), arm) +BITS=32 +SUFF=_arm +CFLAGS:=$(CFLAGS) -march=armv7-a +ASAN_HAS_EXCEPTIONS=0 +endif + +ifeq ($(ARCH), android) +BITS=32 +SUFF=_android +CFLAGS:=$(CFLAGS) +ASAN_HAS_EXCEPTIONS=0 +endif + +PIE= +ifeq ($(ASAN_PIE), 1) + PIE=-fPIE -pie +endif + +# This will build libasan on linux for both x86_64 and i386 in the +# desired location. The Mac library is already build by the clang's make. +# $(CLANG_BUILD)/lib/clang/3.1/lib/$(OS)/libclang_rt.asan-$(ARCH).a +LIBASAN_INST_DIR=$(CLANG_BUILD)/lib/clang/3.1/lib/$(OS) +LIBASAN_A=$(LIBASAN_INST_DIR)/libclang_rt.asan-$(ARCH).a + +BLACKLIST= +ifeq ($(ASAN_HAS_BLACKLIST), 1) + BLACKLIST=-mllvm -asan-blacklist=$(ROOT)/tests/asan_test.ignore +endif + +COMMON_ASAN_DEFINES=\ + -DASAN_UAR=$(ASAN_UAR) \ + -DASAN_HAS_EXCEPTIONS=$(ASAN_HAS_EXCEPTIONS) \ + -DASAN_NEEDS_SEGV=$(ASAN_NEEDS_SEGV) \ + -DASAN_HAS_BLACKLIST=$(ASAN_HAS_BLACKLIST) + +CLANG_ASAN_CXX=$(CLANG_CXX) \ + -faddress-sanitizer \ + $(BLACKLIST) \ + -mllvm -asan-stack=$(ASAN_STACK) \ + -mllvm -asan-globals=$(ASAN_GLOBALS) \ + -mllvm -asan-use-call=$(ASAN_USE_CALL) \ + -mllvm -asan-mapping-scale=$(ASAN_SCALE) \ + -mllvm -asan-mapping-offset-log=$(ASAN_OFFSET) \ + -mllvm -asan-use-after-return=$(ASAN_UAR) \ + $(COMMON_ASAN_DEFINES) + +CLANG_ASAN_LD=$(CLANG_CXX) -faddress-sanitizer + +GCC_ASAN_PATH=SET_FROM_COMMAND_LINE +GCC_ASAN_CXX=$(GCC_ASAN_PATH)/g++ \ + -faddress-sanitizer \ + $(COMMON_ASAN_DEFINES) + +GCC_ASAN_LD=$(GCC_ASAN_PATH)/g++ -ldl -lpthread + +ASAN_COMPILER=clang + +ifeq ($(ASAN_COMPILER), clang) + ASAN_CXX=$(CLANG_ASAN_CXX) + ASAN_LD=$(CLANG_ASAN_LD) + ASAN_LD_TAIL= +endif + +ifeq ($(ASAN_COMPILER), gcc) + ASAN_CXX=$(GCC_ASAN_CXX) + ASAN_LD=$(GCC_ASAN_LD) + ASAN_LD_TAIL=$(LIBASAN_A) +endif + +RTL_HDR=asan_allocator.h \ + asan_internal.h \ + asan_interceptors.h \ + asan_interface.h \ + asan_lock.h \ + asan_mac.h \ + asan_mapping.h \ + asan_stack.h \ + asan_stats.h \ + asan_thread.h \ + asan_thread_registry.h \ + mach_override/mach_override.h \ + sysinfo/basictypes.h \ + sysinfo/sysinfo.h + +LIBASAN_OBJ=$(BIN)/asan_rtl$(SUFF).o \ + $(BIN)/asan_allocator$(SUFF).o \ + $(BIN)/asan_globals$(SUFF).o \ + $(BIN)/asan_interceptors$(SUFF).o \ + $(BIN)/asan_linux$(SUFF).o \ + $(BIN)/asan_mac$(SUFF).o \ + $(BIN)/asan_malloc_linux$(SUFF).o \ + $(BIN)/asan_malloc_mac$(SUFF).o \ + $(BIN)/asan_poisoning$(SUFF).o \ + $(BIN)/asan_printf$(SUFF).o \ + $(BIN)/asan_stack$(SUFF).o \ + $(BIN)/asan_stats$(SUFF).o \ + $(BIN)/asan_thread$(SUFF).o \ + $(BIN)/asan_thread_registry$(SUFF).o \ + $(BIN)/mach_override/mach_override$(SUFF).o \ + $(BIN)/sysinfo/sysinfo$(SUFF).o + +GTEST_ROOT=third_party/googletest +GTEST_INCLUDE=-I$(GTEST_ROOT)/include +GTEST_MAKE_DIR=$(GTEST_ROOT)/make-$(OS)$(SUFF) +GTEST_LIB=$(GTEST_MAKE_DIR)/gtest-all.o + +all: b64 b32 + +test: t64 t32 output_tests lint + +output_tests: b32 b64 + cd tests && ./test_output.sh $(CLANG_CXX) $(CLANG_CC) + +t64: b64 + $(BIN)/asan_test64 +t32: b32 + $(BIN)/asan_test32 + +b64: | $(BIN) + $(MAKE) -f $(MAKEFILE) ARCH=x86_64 asan_test asan_benchmarks +b32: | $(BIN) + $(MAKE) -f $(MAKEFILE) ARCH=i386 asan_test asan_benchmarks + +lib64: + $(MAKE) $(MAKEFILE) ARCH=x86_64 lib +lib32: + $(MAKE) $(MAKEFILE) ARCH=i386 lib + +$(BIN): + mkdir -p $(BIN) + mkdir -p $(BIN)/sysinfo + mkdir -p $(BIN)/mach_override + +clang: + cd ../ && llvm/rebuild_clang_and_asan.sh > /dev/null + +install: install_clang + +$(INSTALL_DIR): + mkdir -p $(INSTALL_DIR) $(INSTALL_DIR)/bin $(INSTALL_DIR)/lib + +install_clang: | $(INSTALL_DIR) + cp -v $(CLANG_BUILD)/bin/clang $(INSTALL_DIR)/bin + cp -rv $(CLANG_BUILD)/lib/clang $(INSTALL_DIR)/lib + (cd $(INSTALL_DIR)/bin; ln -sf clang clang++) + +#install_lib: | $(INSTALL_DIR) +# cp -v $(CLANG_BUILD)/lib/libasan*.a $(INSTALL_DIR)/lib + +$(BIN)/asan_noinst_test$(SUFF).o: tests/asan_noinst_test.cc $(RTL_HDR) $(MAKEFILE) + $(CLEANROOM_CXX) $(PIE) $(CFLAGS) $(GTEST_INCLUDE) -I. -g -c $< -O2 -o $@ + +$(BIN)/asan_break_optimization$(SUFF).o: tests/asan_break_optimization.cc $(MAKEFILE) + $(CLEANROOM_CXX) $(PIE) $(CFLAGS) -c $< -O0 -o $@ + +$(BIN)/%_test$(SUFF).o: tests/%_test.cc $(RTL_HDR) $(MAKEFILE) + $(ASAN_CXX) $(GTEST_INCLUDE) -I. -g -c $< -O2 -o $@ $(PIE) $(CFLAGS) + +$(BIN)/%_test$(SUFF).o: tests/%_test.mm $(RTL_HDR) $(MAKEFILE) + $(ASAN_CXX) $(GTEST_INCLUDE) -I. -g -c $< -O2 -o $@ -ObjC $(PIE) $(CFLAGS) + +$(BIN)/%$(SUFF).o: %.cc $(RTL_HDR) $(MAKEFILE) + $(CXX) $(PIE) $(CFLAGS) -fPIC -c -O2 -fno-exceptions -funwind-tables \ + -o $@ -g $< -Ithird_party \ + -DASAN_USE_SYSINFO=1 \ + -DASAN_NEEDS_SEGV=$(ASAN_NEEDS_SEGV) \ + -DASAN_HAS_EXCEPTIONS=$(ASAN_HAS_EXCEPTIONS) \ + -DASAN_FLEXIBLE_MAPPING_AND_OFFSET=$(ASAN_FLEXIBLE_MAPPING_AND_OFFSET) \ + $(ASAN_FLAGS) + +$(BIN)/%$(SUFF).o: %.c $(RTL_HDR) $(MAKEFILE) + $(CC) $(PIE) $(CFLAGS) -fPIC -c -O2 -o $@ -g $< -Ithird_party \ + -DASAN_USE_SYSINFO=1 \ + $(ASAN_FLAGS) + +ifeq ($(OS),darwin) +LD_FLAGS=-framework Foundation +else +LD_FLAGS= +endif + +lib: $(LIBASAN_A) + +$(LIBASAN_A): $(BIN) $(LIBASAN_OBJ) $(MAKEFILE) + mkdir -p $(LIBASAN_INST_DIR) + ar ru $@ $(LIBASAN_OBJ) + $(CXX) -shared $(CFLAGS) $(LIBASAN_OBJ) $(LD_FLAGS) -o $(BIN)/libasan$(SUFF).so + +TEST_OBJECTS_COMMON=\ + $(BIN)/asan_test$(SUFF).o \ + $(BIN)/asan_globals_test$(SUFF).o \ + $(BIN)/asan_break_optimization$(SUFF).o \ + $(BIN)/asan_noinst_test$(SUFF).o \ + $(BIN)/asan_interface_test$(SUFF).o + +BENCHMARK_OBJECTS=\ + $(BIN)/asan_benchmarks_test$(SUFF).o \ + $(BIN)/asan_break_optimization$(SUFF).o + +ifeq ($(OS),darwin) +TEST_OBJECTS=$(TEST_OBJECTS_COMMON) \ + $(BIN)/asan_mac_test$(SUFF).o +else +TEST_OBJECTS=$(TEST_OBJECTS_COMMON) +endif + +$(BIN)/asan_test$(SUFF): $(TEST_OBJECTS) $(LIBASAN_A) $(MAKEFILE) tests/asan_test.ignore $(GTEST_LIB) + $(ASAN_LD) $(PIE) $(CFLAGS) -g -O3 $(TEST_OBJECTS) \ + $(LD_FLAGS) -o $@ $(LIBS) $(GTEST_LIB) $(ASAN_LD_TAIL) + +$(BIN)/asan_benchmarks$(SUFF): $(BENCHMARK_OBJECTS) $(LIBASAN_A) $(MAKEFILE) $(GTEST_LIB) + $(ASAN_LD) $(PIE) $(CFLAGS) -g -O3 $(BENCHMARK_OBJECTS) \ + $(LD_FLAGS) -o $@ $(LIBS) $(GTEST_LIB) $(ASAN_LD_TAIL) + +asan_test: $(BIN)/asan_test$(SUFF) + +asan_benchmarks: $(BIN)/asan_benchmarks$(SUFF) + +# for now, build gtest with clang/asan even if we use a different compiler. +$(GTEST_LIB): + mkdir -p $(GTEST_MAKE_DIR) && \ + cd $(GTEST_MAKE_DIR) && \ + $(MAKE) -f ../make/Makefile CXXFLAGS="$(PIE) $(CFLAGS) -g -w" \ + CXX="$(CLANG_ASAN_CXX)" + +RTL_LINT_FITLER=-readability/casting,-readability/check,-build/include,-build/header_guard,-build/class,-legal/copyright +# TODO(kcc): remove these filters one by one +TEST_LINT_FITLER=-readability/casting,-build/include,-legal/copyright,-whitespace/newline,-runtime/sizeof,-runtime/int,-runtime/printf + +LLVM_LINT_FILTER=-,+whitespace + +ADDRESS_SANITIZER_CPP=../../../../lib/Transforms/Instrumentation/AddressSanitizer.cpp + +lint: + third_party/cpplint/cpplint.py --filter=$(LLVM_LINT_FILTER) $(ADDRESS_SANITIZER_CPP) + third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FITLER) asan_*.cc asan_*.h + third_party/cpplint/cpplint.py --filter=$(TEST_LINT_FITLER) tests/*.cc + +get_third_party: + rm -rf third_party + mkdir third_party + (cd third_party && \ + svn co -r375 http://googletest.googlecode.com/svn/trunk googletest && \ + svn co -r69 http://google-styleguide.googlecode.com/svn/trunk/cpplint cpplint \ + ) + +clean: + rm -f *.o *.ll *.S *.a *.log asan_test64* asan_test32* a.out perf.data log + rm -rf $(BIN) + rm -rf $(GTEST_ROOT)/make-* diff --git a/lib/asan/README.txt b/lib/asan/README.txt new file mode 100644 index 000000000000..00ae3c48f745 --- /dev/null +++ b/lib/asan/README.txt @@ -0,0 +1,26 @@ +AddressSanitizer RT +================================ +This directory contains sources of the AddressSanitizer (asan) run-time library. +We are in the process of integrating AddressSanitizer with LLVM, stay tuned. + +Directory structre: + +README.txt : This file. +Makefile.mk : Currently a stub for a proper makefile. not usable. +Makefile.old : Old out-of-tree makefile, the only usable one so far. +asan_*.{cc,h} : Sources of the asan run-time lirbary. +mach_override/* : Utility to override functions on Darwin (MIT License). +sysinfo/* : Portable utility to iterate over /proc/maps (BSD License). +scripts/* : Helper scripts. + +Temporary build instructions (verified on linux): + +cd lib/asan +make -f Makefile.old get_third_party # gets googletest and cpplint +make -f Makefile.old test -j 8 CLANG_BUILD=/path/to/Release+Asserts +# Optional: +# make -f Makefile.old install # installs clang and rt to lib/asan_clang_linux + +For more info see http://code.google.com/p/address-sanitizer/ + + diff --git a/lib/asan/asan_allocator.cc b/lib/asan/asan_allocator.cc new file mode 100644 index 000000000000..f86dc0b0205d --- /dev/null +++ b/lib/asan/asan_allocator.cc @@ -0,0 +1,1020 @@ +//===-- asan_allocator.cc ---------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Implementation of ASan's memory allocator. +// Evey piece of memory (AsanChunk) allocated by the allocator +// has a left redzone of REDZONE bytes and +// a right redzone such that the end of the chunk is aligned by REDZONE +// (i.e. the right redzone is between 0 and REDZONE-1). +// The left redzone is always poisoned. +// The right redzone is poisoned on malloc, the body is poisoned on free. +// Once freed, a chunk is moved to a quarantine (fifo list). +// After quarantine, a chunk is returned to freelists. +// +// The left redzone contains ASan's internal data and the stack trace of +// the malloc call. +// Once freed, the body of the chunk contains the stack trace of the free call. +// +//===----------------------------------------------------------------------===// + +#include "asan_allocator.h" +#include "asan_interceptors.h" +#include "asan_interface.h" +#include "asan_internal.h" +#include "asan_lock.h" +#include "asan_mapping.h" +#include "asan_stats.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" + +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +namespace __asan { + +#define REDZONE FLAG_redzone +static const size_t kMinAllocSize = REDZONE * 2; +static const size_t kMinMmapSize = 4UL << 20; // 4M +static const uint64_t kMaxAvailableRam = 128ULL << 30; // 128G +static const size_t kMaxThreadLocalQuarantine = 1 << 20; // 1M +static const size_t kMaxSizeForThreadLocalFreeList = 1 << 17; + +// Size classes less than kMallocSizeClassStep are powers of two. +// All other size classes are multiples of kMallocSizeClassStep. +static const size_t kMallocSizeClassStepLog = 26; +static const size_t kMallocSizeClassStep = 1UL << kMallocSizeClassStepLog; + +#if __WORDSIZE == 32 +static const size_t kMaxAllowedMallocSize = 3UL << 30; // 3G +#else +static const size_t kMaxAllowedMallocSize = 8UL << 30; // 8G +#endif + +static inline bool IsAligned(uintptr_t a, uintptr_t alignment) { + return (a & (alignment - 1)) == 0; +} + +static inline size_t Log2(size_t x) { + CHECK(IsPowerOfTwo(x)); + return __builtin_ctzl(x); +} + +static inline size_t RoundUpToPowerOfTwo(size_t size) { + CHECK(size); + if (IsPowerOfTwo(size)) return size; + size_t up = __WORDSIZE - __builtin_clzl(size); + CHECK(size < (1ULL << up)); + CHECK(size > (1ULL << (up - 1))); + return 1UL << up; +} + +static inline size_t SizeClassToSize(uint8_t size_class) { + CHECK(size_class < kNumberOfSizeClasses); + if (size_class <= kMallocSizeClassStepLog) { + return 1UL << size_class; + } else { + return (size_class - kMallocSizeClassStepLog) * kMallocSizeClassStep; + } +} + +static inline uint8_t SizeToSizeClass(size_t size) { + uint8_t res = 0; + if (size <= kMallocSizeClassStep) { + size_t rounded = RoundUpToPowerOfTwo(size); + res = Log2(rounded); + } else { + res = ((size + kMallocSizeClassStep - 1) / kMallocSizeClassStep) + + kMallocSizeClassStepLog; + } + CHECK(res < kNumberOfSizeClasses); + CHECK(size <= SizeClassToSize(res)); + return res; +} + +// Given REDZONE bytes, we need to mark first size bytes +// as addressable and the rest REDZONE-size bytes as unaddressable. +static void PoisonHeapPartialRightRedzone(uintptr_t mem, size_t size) { + CHECK(size <= REDZONE); + CHECK(IsAligned(mem, REDZONE)); + CHECK(IsPowerOfTwo(SHADOW_GRANULARITY)); + CHECK(IsPowerOfTwo(REDZONE)); + CHECK(REDZONE >= SHADOW_GRANULARITY); + PoisonShadowPartialRightRedzone(mem, size, REDZONE, + kAsanHeapRightRedzoneMagic); +} + +static uint8_t *MmapNewPagesAndPoisonShadow(size_t size) { + CHECK(IsAligned(size, kPageSize)); + uint8_t *res = (uint8_t*)AsanMmapSomewhereOrDie(size, __FUNCTION__); + PoisonShadow((uintptr_t)res, size, kAsanHeapLeftRedzoneMagic); + if (FLAG_debug) { + Printf("ASAN_MMAP: [%p, %p)\n", res, res + size); + } + return res; +} + +// Every chunk of memory allocated by this allocator can be in one of 3 states: +// CHUNK_AVAILABLE: the chunk is in the free list and ready to be allocated. +// CHUNK_ALLOCATED: the chunk is allocated and not yet freed. +// CHUNK_QUARANTINE: the chunk was freed and put into quarantine zone. +// +// The pseudo state CHUNK_MEMALIGN is used to mark that the address is not +// the beginning of a AsanChunk (in which case 'next' contains the address +// of the AsanChunk). +// +// The magic numbers for the enum values are taken randomly. +enum { + CHUNK_AVAILABLE = 0x573B, + CHUNK_ALLOCATED = 0x3204, + CHUNK_QUARANTINE = 0x1978, + CHUNK_MEMALIGN = 0xDC68, +}; + +struct ChunkBase { + uint16_t chunk_state; + uint8_t size_class; + uint32_t offset; // User-visible memory starts at this+offset (beg()). + int32_t alloc_tid; + int32_t free_tid; + size_t used_size; // Size requested by the user. + AsanChunk *next; + + uintptr_t beg() { return (uintptr_t)this + offset; } + size_t Size() { return SizeClassToSize(size_class); } + uint8_t SizeClass() { return size_class; } +}; + +struct AsanChunk: public ChunkBase { + uint32_t *compressed_alloc_stack() { + CHECK(REDZONE >= sizeof(ChunkBase)); + return (uint32_t*)((uintptr_t)this + sizeof(ChunkBase)); + } + uint32_t *compressed_free_stack() { + CHECK(REDZONE >= sizeof(ChunkBase)); + return (uint32_t*)((uintptr_t)this + REDZONE); + } + + // The left redzone after the ChunkBase is given to the alloc stack trace. + size_t compressed_alloc_stack_size() { + return (REDZONE - sizeof(ChunkBase)) / sizeof(uint32_t); + } + size_t compressed_free_stack_size() { + return (REDZONE) / sizeof(uint32_t); + } + + bool AddrIsInside(uintptr_t addr, size_t access_size, size_t *offset) { + if (addr >= beg() && (addr + access_size) <= (beg() + used_size)) { + *offset = addr - beg(); + return true; + } + return false; + } + + bool AddrIsAtLeft(uintptr_t addr, size_t access_size, size_t *offset) { + if (addr < beg()) { + *offset = beg() - addr; + return true; + } + return false; + } + + bool AddrIsAtRight(uintptr_t addr, size_t access_size, size_t *offset) { + if (addr + access_size >= beg() + used_size) { + if (addr <= beg() + used_size) + *offset = 0; + else + *offset = addr - (beg() + used_size); + return true; + } + return false; + } + + void DescribeAddress(uintptr_t addr, size_t access_size) { + size_t offset; + Printf("%p is located ", addr); + if (AddrIsInside(addr, access_size, &offset)) { + Printf("%ld bytes inside of", offset); + } else if (AddrIsAtLeft(addr, access_size, &offset)) { + Printf("%ld bytes to the left of", offset); + } else if (AddrIsAtRight(addr, access_size, &offset)) { + Printf("%ld bytes to the right of", offset); + } else { + Printf(" somewhere around (this is AddressSanitizer bug!)"); + } + Printf(" %lu-byte region [%p,%p)\n", + used_size, beg(), beg() + used_size); + } +}; + +static AsanChunk *PtrToChunk(uintptr_t ptr) { + AsanChunk *m = (AsanChunk*)(ptr - REDZONE); + if (m->chunk_state == CHUNK_MEMALIGN) { + m = m->next; + } + return m; +} + + +void AsanChunkFifoList::PushList(AsanChunkFifoList *q) { + if (last_) { + CHECK(first_); + CHECK(!last_->next); + last_->next = q->first_; + last_ = q->last_; + } else { + CHECK(!first_); + last_ = q->last_; + first_ = q->first_; + } + size_ += q->size(); + q->clear(); +} + +void AsanChunkFifoList::Push(AsanChunk *n) { + CHECK(n->next == NULL); + if (last_) { + CHECK(first_); + CHECK(!last_->next); + last_->next = n; + last_ = n; + } else { + CHECK(!first_); + last_ = first_ = n; + } + size_ += n->Size(); +} + +// Interesting performance observation: this function takes up to 15% of overal +// allocator time. That's because *first_ has been evicted from cache long time +// ago. Not sure if we can or want to do anything with this. +AsanChunk *AsanChunkFifoList::Pop() { + CHECK(first_); + AsanChunk *res = first_; + first_ = first_->next; + if (first_ == NULL) + last_ = NULL; + CHECK(size_ >= res->Size()); + size_ -= res->Size(); + if (last_) { + CHECK(!last_->next); + } + return res; +} + +// All pages we ever allocated. +struct PageGroup { + uintptr_t beg; + uintptr_t end; + size_t size_of_chunk; + uintptr_t last_chunk; + bool InRange(uintptr_t addr) { + return addr >= beg && addr < end; + } +}; + +class MallocInfo { + public: + + explicit MallocInfo(LinkerInitialized x) : mu_(x) { } + + AsanChunk *AllocateChunks(uint8_t size_class, size_t n_chunks) { + AsanChunk *m = NULL; + AsanChunk **fl = &free_lists_[size_class]; + { + ScopedLock lock(&mu_); + for (size_t i = 0; i < n_chunks; i++) { + if (!(*fl)) { + *fl = GetNewChunks(size_class); + } + AsanChunk *t = *fl; + *fl = t->next; + t->next = m; + CHECK(t->chunk_state == CHUNK_AVAILABLE); + m = t; + } + } + return m; + } + + void SwallowThreadLocalMallocStorage(AsanThreadLocalMallocStorage *x, + bool eat_free_lists) { + CHECK(FLAG_quarantine_size > 0); + ScopedLock lock(&mu_); + AsanChunkFifoList *q = &x->quarantine_; + if (q->size() > 0) { + quarantine_.PushList(q); + while (quarantine_.size() > FLAG_quarantine_size) { + QuarantinePop(); + } + } + if (eat_free_lists) { + for (size_t size_class = 0; size_class < kNumberOfSizeClasses; + size_class++) { + AsanChunk *m = x->free_lists_[size_class]; + while (m) { + AsanChunk *t = m->next; + m->next = free_lists_[size_class]; + free_lists_[size_class] = m; + m = t; + } + x->free_lists_[size_class] = 0; + } + } + } + + void BypassThreadLocalQuarantine(AsanChunk *chunk) { + ScopedLock lock(&mu_); + quarantine_.Push(chunk); + } + + AsanChunk *FindMallocedOrFreed(uintptr_t addr, size_t access_size) { + ScopedLock lock(&mu_); + return FindChunkByAddr(addr); + } + + // TODO(glider): AllocationSize() may become very slow if the size of + // page_groups_ grows. This can be fixed by increasing kMinMmapSize, + // but a better solution is to speed up the search somehow. + size_t AllocationSize(uintptr_t ptr) { + ScopedLock lock(&mu_); + + // first, check if this is our memory + PageGroup *g = FindPageGroupUnlocked(ptr); + if (!g) return 0; + AsanChunk *m = PtrToChunk(ptr); + if (m->chunk_state == CHUNK_ALLOCATED) { + return m->used_size; + } else { + return 0; + } + } + + void ForceLock() { + mu_.Lock(); + } + + void ForceUnlock() { + mu_.Unlock(); + } + + void PrintStatus() { + ScopedLock lock(&mu_); + size_t malloced = 0; + + Printf(" MallocInfo: in quarantine: %ld malloced: %ld; ", + quarantine_.size() >> 20, malloced >> 20); + for (size_t j = 1; j < kNumberOfSizeClasses; j++) { + AsanChunk *i = free_lists_[j]; + if (!i) continue; + size_t t = 0; + for (; i; i = i->next) { + t += i->Size(); + } + Printf("%ld:%ld ", j, t >> 20); + } + Printf("\n"); + } + + PageGroup *FindPageGroup(uintptr_t addr) { + ScopedLock lock(&mu_); + return FindPageGroupUnlocked(addr); + } + + private: + PageGroup *FindPageGroupUnlocked(uintptr_t addr) { + for (int i = 0; i < n_page_groups_; i++) { + PageGroup *g = page_groups_[i]; + if (g->InRange(addr)) { + return g; + } + } + return NULL; + } + + // We have an address between two chunks, and we want to report just one. + AsanChunk *ChooseChunk(uintptr_t addr, + AsanChunk *left_chunk, AsanChunk *right_chunk) { + // Prefer an allocated chunk or a chunk from quarantine. + if (left_chunk->chunk_state == CHUNK_AVAILABLE && + right_chunk->chunk_state != CHUNK_AVAILABLE) + return right_chunk; + if (right_chunk->chunk_state == CHUNK_AVAILABLE && + left_chunk->chunk_state != CHUNK_AVAILABLE) + return left_chunk; + // Choose based on offset. + size_t l_offset = 0, r_offset = 0; + CHECK(left_chunk->AddrIsAtRight(addr, 1, &l_offset)); + CHECK(right_chunk->AddrIsAtLeft(addr, 1, &r_offset)); + if (l_offset < r_offset) + return left_chunk; + return right_chunk; + } + + AsanChunk *FindChunkByAddr(uintptr_t addr) { + PageGroup *g = FindPageGroupUnlocked(addr); + if (!g) return 0; + CHECK(g->size_of_chunk); + uintptr_t offset_from_beg = addr - g->beg; + uintptr_t this_chunk_addr = g->beg + + (offset_from_beg / g->size_of_chunk) * g->size_of_chunk; + CHECK(g->InRange(this_chunk_addr)); + AsanChunk *m = (AsanChunk*)this_chunk_addr; + CHECK(m->chunk_state == CHUNK_ALLOCATED || + m->chunk_state == CHUNK_AVAILABLE || + m->chunk_state == CHUNK_QUARANTINE); + size_t offset = 0; + if (m->AddrIsInside(addr, 1, &offset)) + return m; + + if (m->AddrIsAtRight(addr, 1, &offset)) { + if (this_chunk_addr == g->last_chunk) // rightmost chunk + return m; + uintptr_t right_chunk_addr = this_chunk_addr + g->size_of_chunk; + CHECK(g->InRange(right_chunk_addr)); + return ChooseChunk(addr, m, (AsanChunk*)right_chunk_addr); + } else { + CHECK(m->AddrIsAtLeft(addr, 1, &offset)); + if (this_chunk_addr == g->beg) // leftmost chunk + return m; + uintptr_t left_chunk_addr = this_chunk_addr - g->size_of_chunk; + CHECK(g->InRange(left_chunk_addr)); + return ChooseChunk(addr, (AsanChunk*)left_chunk_addr, m); + } + } + + void QuarantinePop() { + CHECK(quarantine_.size() > 0); + AsanChunk *m = quarantine_.Pop(); + CHECK(m); + // if (F_v >= 2) Printf("MallocInfo::pop %p\n", m); + + CHECK(m->chunk_state == CHUNK_QUARANTINE); + m->chunk_state = CHUNK_AVAILABLE; + CHECK(m->alloc_tid >= 0); + CHECK(m->free_tid >= 0); + + size_t size_class = m->SizeClass(); + m->next = free_lists_[size_class]; + free_lists_[size_class] = m; + + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.real_frees++; + thread_stats.really_freed += m->used_size; + thread_stats.really_freed_redzones += m->Size() - m->used_size; + thread_stats.really_freed_by_size[m->SizeClass()]++; + } + + // Get a list of newly allocated chunks. + AsanChunk *GetNewChunks(uint8_t size_class) { + size_t size = SizeClassToSize(size_class); + CHECK(IsPowerOfTwo(kMinMmapSize)); + CHECK(size < kMinMmapSize || (size % kMinMmapSize) == 0); + size_t mmap_size = Max(size, kMinMmapSize); + size_t n_chunks = mmap_size / size; + CHECK(n_chunks * size == mmap_size); + if (size < kPageSize) { + // Size is small, just poison the last chunk. + n_chunks--; + } else { + // Size is large, allocate an extra page at right and poison it. + mmap_size += kPageSize; + } + CHECK(n_chunks > 0); + uint8_t *mem = MmapNewPagesAndPoisonShadow(mmap_size); + + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.mmaps++; + thread_stats.mmaped += mmap_size; + thread_stats.mmaped_by_size[size_class] += n_chunks; + + AsanChunk *res = NULL; + for (size_t i = 0; i < n_chunks; i++) { + AsanChunk *m = (AsanChunk*)(mem + i * size); + m->chunk_state = CHUNK_AVAILABLE; + m->size_class = size_class; + m->next = res; + res = m; + } + PageGroup *pg = (PageGroup*)(mem + n_chunks * size); + // This memory is already poisoned, no need to poison it again. + pg->beg = (uintptr_t)mem; + pg->end = pg->beg + mmap_size; + pg->size_of_chunk = size; + pg->last_chunk = (uintptr_t)(mem + size * (n_chunks - 1)); + int page_group_idx = AtomicInc(&n_page_groups_) - 1; + CHECK(page_group_idx < (int)ASAN_ARRAY_SIZE(page_groups_)); + page_groups_[page_group_idx] = pg; + return res; + } + + AsanChunk *free_lists_[kNumberOfSizeClasses]; + AsanChunkFifoList quarantine_; + AsanLock mu_; + + PageGroup *page_groups_[kMaxAvailableRam / kMinMmapSize]; + int n_page_groups_; // atomic +}; + +static MallocInfo malloc_info(LINKER_INITIALIZED); + +void AsanThreadLocalMallocStorage::CommitBack() { + malloc_info.SwallowThreadLocalMallocStorage(this, true); +} + +static void Describe(uintptr_t addr, size_t access_size) { + AsanChunk *m = malloc_info.FindMallocedOrFreed(addr, access_size); + if (!m) return; + m->DescribeAddress(addr, access_size); + CHECK(m->alloc_tid >= 0); + AsanThreadSummary *alloc_thread = + asanThreadRegistry().FindByTid(m->alloc_tid); + AsanStackTrace alloc_stack; + AsanStackTrace::UncompressStack(&alloc_stack, m->compressed_alloc_stack(), + m->compressed_alloc_stack_size()); + AsanThread *t = asanThreadRegistry().GetCurrent(); + CHECK(t); + if (m->free_tid >= 0) { + AsanThreadSummary *free_thread = + asanThreadRegistry().FindByTid(m->free_tid); + Printf("freed by thread T%d here:\n", free_thread->tid()); + AsanStackTrace free_stack; + AsanStackTrace::UncompressStack(&free_stack, m->compressed_free_stack(), + m->compressed_free_stack_size()); + free_stack.PrintStack(); + Printf("previously allocated by thread T%d here:\n", + alloc_thread->tid()); + + alloc_stack.PrintStack(); + t->summary()->Announce(); + free_thread->Announce(); + alloc_thread->Announce(); + } else { + Printf("allocated by thread T%d here:\n", alloc_thread->tid()); + alloc_stack.PrintStack(); + t->summary()->Announce(); + alloc_thread->Announce(); + } +} + +static uint8_t *Allocate(size_t alignment, size_t size, AsanStackTrace *stack) { + __asan_init(); + CHECK(stack); + if (size == 0) { + size = 1; // TODO(kcc): do something smarter + } + CHECK(IsPowerOfTwo(alignment)); + size_t rounded_size = RoundUpTo(size, REDZONE); + size_t needed_size = rounded_size + REDZONE; + if (alignment > REDZONE) { + needed_size += alignment; + } + CHECK(IsAligned(needed_size, REDZONE)); + if (size > kMaxAllowedMallocSize || needed_size > kMaxAllowedMallocSize) { + Report("WARNING: AddressSanitizer failed to allocate %p bytes\n", size); + return 0; + } + + uint8_t size_class = SizeToSizeClass(needed_size); + size_t size_to_allocate = SizeClassToSize(size_class); + CHECK(size_to_allocate >= kMinAllocSize); + CHECK(size_to_allocate >= needed_size); + CHECK(IsAligned(size_to_allocate, REDZONE)); + + if (FLAG_v >= 2) { + Printf("Allocate align: %ld size: %ld class: %d real: %ld\n", + alignment, size, size_class, size_to_allocate); + } + + AsanThread *t = asanThreadRegistry().GetCurrent(); + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + // Statistics + thread_stats.mallocs++; + thread_stats.malloced += size; + thread_stats.malloced_redzones += size_to_allocate - size; + thread_stats.malloced_by_size[size_class]++; + + AsanChunk *m = NULL; + if (!t || size_to_allocate >= kMaxSizeForThreadLocalFreeList) { + // get directly from global storage. + m = malloc_info.AllocateChunks(size_class, 1); + thread_stats.malloc_large++; + } else { + // get from the thread-local storage. + AsanChunk **fl = &t->malloc_storage().free_lists_[size_class]; + if (!*fl) { + size_t n_new_chunks = kMaxSizeForThreadLocalFreeList / size_to_allocate; + *fl = malloc_info.AllocateChunks(size_class, n_new_chunks); + thread_stats.malloc_small_slow++; + } + m = *fl; + *fl = (*fl)->next; + } + CHECK(m); + CHECK(m->chunk_state == CHUNK_AVAILABLE); + m->chunk_state = CHUNK_ALLOCATED; + m->next = NULL; + CHECK(m->Size() == size_to_allocate); + uintptr_t addr = (uintptr_t)m + REDZONE; + CHECK(addr == (uintptr_t)m->compressed_free_stack()); + + if (alignment > REDZONE && (addr & (alignment - 1))) { + addr = RoundUpTo(addr, alignment); + CHECK((addr & (alignment - 1)) == 0); + AsanChunk *p = (AsanChunk*)(addr - REDZONE); + p->chunk_state = CHUNK_MEMALIGN; + p->next = m; + } + CHECK(m == PtrToChunk(addr)); + m->used_size = size; + m->offset = addr - (uintptr_t)m; + CHECK(m->beg() == addr); + m->alloc_tid = t ? t->tid() : 0; + m->free_tid = AsanThread::kInvalidTid; + AsanStackTrace::CompressStack(stack, m->compressed_alloc_stack(), + m->compressed_alloc_stack_size()); + PoisonShadow(addr, rounded_size, 0); + if (size < rounded_size) { + PoisonHeapPartialRightRedzone(addr + rounded_size - REDZONE, + size & (REDZONE - 1)); + } + if (size <= FLAG_max_malloc_fill_size) { + real_memset((void*)addr, 0, rounded_size); + } + return (uint8_t*)addr; +} + +static void Deallocate(uint8_t *ptr, AsanStackTrace *stack) { + if (!ptr) return; + CHECK(stack); + + if (FLAG_debug) { + CHECK(malloc_info.FindPageGroup((uintptr_t)ptr)); + } + + // Printf("Deallocate %p\n", ptr); + AsanChunk *m = PtrToChunk((uintptr_t)ptr); + if (m->chunk_state == CHUNK_QUARANTINE) { + Report("ERROR: AddressSanitizer attempting double-free on %p:\n", ptr); + stack->PrintStack(); + m->DescribeAddress((uintptr_t)ptr, 1); + ShowStatsAndAbort(); + } else if (m->chunk_state != CHUNK_ALLOCATED) { + Report("ERROR: AddressSanitizer attempting free on address which was not" + " malloc()-ed: %p\n", ptr); + stack->PrintStack(); + ShowStatsAndAbort(); + } + CHECK(m->chunk_state == CHUNK_ALLOCATED); + CHECK(m->free_tid == AsanThread::kInvalidTid); + CHECK(m->alloc_tid >= 0); + AsanThread *t = asanThreadRegistry().GetCurrent(); + m->free_tid = t ? t->tid() : 0; + AsanStackTrace::CompressStack(stack, m->compressed_free_stack(), + m->compressed_free_stack_size()); + size_t rounded_size = RoundUpTo(m->used_size, REDZONE); + PoisonShadow((uintptr_t)ptr, rounded_size, kAsanHeapFreeMagic); + + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.frees++; + thread_stats.freed += m->used_size; + thread_stats.freed_by_size[m->SizeClass()]++; + + m->chunk_state = CHUNK_QUARANTINE; + if (t) { + AsanThreadLocalMallocStorage *ms = &t->malloc_storage(); + CHECK(!m->next); + ms->quarantine_.Push(m); + + if (ms->quarantine_.size() > kMaxThreadLocalQuarantine) { + malloc_info.SwallowThreadLocalMallocStorage(ms, false); + } + } else { + CHECK(!m->next); + malloc_info.BypassThreadLocalQuarantine(m); + } +} + +static uint8_t *Reallocate(uint8_t *old_ptr, size_t new_size, + AsanStackTrace *stack) { + CHECK(old_ptr && new_size); + + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.reallocs++; + thread_stats.realloced += new_size; + + AsanChunk *m = PtrToChunk((uintptr_t)old_ptr); + CHECK(m->chunk_state == CHUNK_ALLOCATED); + size_t old_size = m->used_size; + size_t memcpy_size = Min(new_size, old_size); + uint8_t *new_ptr = Allocate(0, new_size, stack); + if (new_ptr) { + real_memcpy(new_ptr, old_ptr, memcpy_size); + Deallocate(old_ptr, stack); + } + return new_ptr; +} + +} // namespace __asan + +// Malloc hooks declaration. +// ASAN_NEW_HOOK(ptr, size) is called immediately after +// allocation of "size" bytes, which returned "ptr". +// ASAN_DELETE_HOOK(ptr) is called immediately before +// deallocation of "ptr". +// If ASAN_NEW_HOOK or ASAN_DELETE_HOOK is defined, user +// program must provide implementation of this hook. +// If macro is undefined, the hook is no-op. +#ifdef ASAN_NEW_HOOK +extern "C" void ASAN_NEW_HOOK(void *ptr, size_t size); +#else +static inline void ASAN_NEW_HOOK(void *ptr, size_t size) { } +#endif + +#ifdef ASAN_DELETE_HOOK +extern "C" void ASAN_DELETE_HOOK(void *ptr); +#else +static inline void ASAN_DELETE_HOOK(void *ptr) { } +#endif + +namespace __asan { + +void *asan_memalign(size_t alignment, size_t size, AsanStackTrace *stack) { + void *ptr = (void*)Allocate(alignment, size, stack); + ASAN_NEW_HOOK(ptr, size); + return ptr; +} + +void asan_free(void *ptr, AsanStackTrace *stack) { + ASAN_DELETE_HOOK(ptr); + Deallocate((uint8_t*)ptr, stack); +} + +void *asan_malloc(size_t size, AsanStackTrace *stack) { + void *ptr = (void*)Allocate(0, size, stack); + ASAN_NEW_HOOK(ptr, size); + return ptr; +} + +void *asan_calloc(size_t nmemb, size_t size, AsanStackTrace *stack) { + void *ptr = (void*)Allocate(0, nmemb * size, stack); + if (ptr) + real_memset(ptr, 0, nmemb * size); + ASAN_NEW_HOOK(ptr, nmemb * size); + return ptr; +} + +void *asan_realloc(void *p, size_t size, AsanStackTrace *stack) { + if (p == NULL) { + void *ptr = (void*)Allocate(0, size, stack); + ASAN_NEW_HOOK(ptr, size); + return ptr; + } else if (size == 0) { + ASAN_DELETE_HOOK(p); + Deallocate((uint8_t*)p, stack); + return NULL; + } + return Reallocate((uint8_t*)p, size, stack); +} + +void *asan_valloc(size_t size, AsanStackTrace *stack) { + void *ptr = (void*)Allocate(kPageSize, size, stack); + ASAN_NEW_HOOK(ptr, size); + return ptr; +} + +void *asan_pvalloc(size_t size, AsanStackTrace *stack) { + size = RoundUpTo(size, kPageSize); + if (size == 0) { + // pvalloc(0) should allocate one page. + size = kPageSize; + } + void *ptr = (void*)Allocate(kPageSize, size, stack); + ASAN_NEW_HOOK(ptr, size); + return ptr; +} + +int asan_posix_memalign(void **memptr, size_t alignment, size_t size, + AsanStackTrace *stack) { + void *ptr = Allocate(alignment, size, stack); + CHECK(IsAligned((uintptr_t)ptr, alignment)); + ASAN_NEW_HOOK(ptr, size); + *memptr = ptr; + return 0; +} + +size_t __asan_mz_size(const void *ptr) { + return malloc_info.AllocationSize((uintptr_t)ptr); +} + +void DescribeHeapAddress(uintptr_t addr, uintptr_t access_size) { + Describe(addr, access_size); +} + +void __asan_mz_force_lock() { + malloc_info.ForceLock(); +} + +void __asan_mz_force_unlock() { + malloc_info.ForceUnlock(); +} + +// ---------------------- Fake stack-------------------- {{{1 +FakeStack::FakeStack() { + CHECK(real_memset); + real_memset(this, 0, sizeof(*this)); +} + +bool FakeStack::AddrIsInSizeClass(uintptr_t addr, size_t size_class) { + uintptr_t mem = allocated_size_classes_[size_class]; + uintptr_t size = ClassMmapSize(size_class); + bool res = mem && addr >= mem && addr < mem + size; + return res; +} + +uintptr_t FakeStack::AddrIsInFakeStack(uintptr_t addr) { + for (size_t i = 0; i < kNumberOfSizeClasses; i++) { + if (AddrIsInSizeClass(addr, i)) return allocated_size_classes_[i]; + } + return 0; +} + +// We may want to compute this during compilation. +inline size_t FakeStack::ComputeSizeClass(size_t alloc_size) { + size_t rounded_size = RoundUpToPowerOfTwo(alloc_size); + size_t log = Log2(rounded_size); + CHECK(alloc_size <= (1UL << log)); + if (!(alloc_size > (1UL << (log-1)))) { + Printf("alloc_size %ld log %ld\n", alloc_size, log); + } + CHECK(alloc_size > (1UL << (log-1))); + size_t res = log < kMinStackFrameSizeLog ? 0 : log - kMinStackFrameSizeLog; + CHECK(res < kNumberOfSizeClasses); + CHECK(ClassSize(res) >= rounded_size); + return res; +} + +void FakeFrameFifo::FifoPush(FakeFrame *node) { + CHECK(node); + node->next = 0; + if (first_ == 0 && last_ == 0) { + first_ = last_ = node; + } else { + CHECK(first_); + CHECK(last_); + last_->next = node; + last_ = node; + } +} + +FakeFrame *FakeFrameFifo::FifoPop() { + CHECK(first_ && last_ && "Exhausted fake stack"); + FakeFrame *res = 0; + if (first_ == last_) { + res = first_; + first_ = last_ = 0; + } else { + res = first_; + first_ = first_->next; + } + return res; +} + +void FakeStack::Init(size_t stack_size) { + stack_size_ = stack_size; + alive_ = true; +} + +void FakeStack::Cleanup() { + alive_ = false; + for (size_t i = 0; i < kNumberOfSizeClasses; i++) { + uintptr_t mem = allocated_size_classes_[i]; + if (mem) { + PoisonShadow(mem, ClassMmapSize(i), 0); + allocated_size_classes_[i] = 0; + AsanUnmapOrDie((void*)mem, ClassMmapSize(i)); + } + } +} + +size_t FakeStack::ClassMmapSize(size_t size_class) { + return RoundUpToPowerOfTwo(stack_size_); +} + +void FakeStack::AllocateOneSizeClass(size_t size_class) { + CHECK(ClassMmapSize(size_class) >= kPageSize); + uintptr_t new_mem = (uintptr_t)AsanMmapSomewhereOrDie( + ClassMmapSize(size_class), __FUNCTION__); + // Printf("T%d new_mem[%ld]: %p-%p mmap %ld\n", + // asanThreadRegistry().GetCurrent()->tid(), + // size_class, new_mem, new_mem + ClassMmapSize(size_class), + // ClassMmapSize(size_class)); + size_t i; + for (i = 0; i < ClassMmapSize(size_class); + i += ClassSize(size_class)) { + size_classes_[size_class].FifoPush((FakeFrame*)(new_mem + i)); + } + CHECK(i == ClassMmapSize(size_class)); + allocated_size_classes_[size_class] = new_mem; +} + +uintptr_t FakeStack::AllocateStack(size_t size, size_t real_stack) { + if (!alive_) return real_stack; + CHECK(size <= kMaxStackMallocSize && size > 1); + size_t size_class = ComputeSizeClass(size); + if (!allocated_size_classes_[size_class]) { + AllocateOneSizeClass(size_class); + } + FakeFrame *fake_frame = size_classes_[size_class].FifoPop(); + CHECK(fake_frame); + fake_frame->size_minus_one = size - 1; + fake_frame->real_stack = real_stack; + while (FakeFrame *top = call_stack_.top()) { + if (top->real_stack > real_stack) break; + call_stack_.LifoPop(); + DeallocateFrame(top); + } + call_stack_.LifoPush(fake_frame); + uintptr_t ptr = (uintptr_t)fake_frame; + PoisonShadow(ptr, size, 0); + return ptr; +} + +void FakeStack::DeallocateFrame(FakeFrame *fake_frame) { + CHECK(alive_); + size_t size = fake_frame->size_minus_one + 1; + size_t size_class = ComputeSizeClass(size); + CHECK(allocated_size_classes_[size_class]); + uintptr_t ptr = (uintptr_t)fake_frame; + CHECK(AddrIsInSizeClass(ptr, size_class)); + CHECK(AddrIsInSizeClass(ptr + size - 1, size_class)); + size_classes_[size_class].FifoPush(fake_frame); +} + +void FakeStack::OnFree(size_t ptr, size_t size, size_t real_stack) { + FakeFrame *fake_frame = (FakeFrame*)ptr; + CHECK(fake_frame->magic = kRetiredStackFrameMagic); + CHECK(fake_frame->descr != 0); + CHECK(fake_frame->size_minus_one == size - 1); + PoisonShadow(ptr, size, kAsanStackAfterReturnMagic); +} + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +size_t __asan_stack_malloc(size_t size, size_t real_stack) { + if (!FLAG_use_fake_stack) return real_stack; + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (!t) { + // TSD is gone, use the real stack. + return real_stack; + } + size_t ptr = t->fake_stack().AllocateStack(size, real_stack); + // Printf("__asan_stack_malloc %p %ld %p\n", ptr, size, real_stack); + return ptr; +} + +void __asan_stack_free(size_t ptr, size_t size, size_t real_stack) { + if (!FLAG_use_fake_stack) return; + if (ptr != real_stack) { + FakeStack::OnFree(ptr, size, real_stack); + } +} + +// ASan allocator doesn't reserve extra bytes, so normally we would +// just return "size". +size_t __asan_get_estimated_allocated_size(size_t size) { + if (size == 0) return 1; + return Min(size, kMaxAllowedMallocSize); +} + +bool __asan_get_ownership(const void *p) { + return (p == NULL) || + (malloc_info.AllocationSize((uintptr_t)p) > 0); +} + +size_t __asan_get_allocated_size(const void *p) { + if (p == NULL) return 0; + size_t allocated_size = malloc_info.AllocationSize((uintptr_t)p); + // Die if p is not malloced or if it is already freed. + if (allocated_size == 0) { + Printf("__asan_get_allocated_size failed, ptr=%p is not owned\n", p); + PRINT_CURRENT_STACK(); + ShowStatsAndAbort(); + } + return allocated_size; +} diff --git a/lib/asan/asan_allocator.h b/lib/asan/asan_allocator.h new file mode 100644 index 000000000000..9b691e04ff40 --- /dev/null +++ b/lib/asan/asan_allocator.h @@ -0,0 +1,157 @@ +//===-- asan_allocator.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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header for asan_allocator.cc. +//===----------------------------------------------------------------------===// + +#ifndef ASAN_ALLOCATOR_H +#define ASAN_ALLOCATOR_H + +#include "asan_internal.h" +#include "asan_interceptors.h" + +namespace __asan { + +static const size_t kNumberOfSizeClasses = 255; +struct AsanChunk; + +class AsanChunkFifoList { + public: + explicit AsanChunkFifoList(LinkerInitialized) { } + AsanChunkFifoList() { clear(); } + void Push(AsanChunk *n); + void PushList(AsanChunkFifoList *q); + AsanChunk *Pop(); + size_t size() { return size_; } + void clear() { + first_ = last_ = NULL; + size_ = 0; + } + private: + AsanChunk *first_; + AsanChunk *last_; + size_t size_; +}; + +struct AsanThreadLocalMallocStorage { + explicit AsanThreadLocalMallocStorage(LinkerInitialized x) + : quarantine_(x) { } + AsanThreadLocalMallocStorage() { + CHECK(real_memset); + real_memset(this, 0, sizeof(AsanThreadLocalMallocStorage)); + } + + AsanChunkFifoList quarantine_; + AsanChunk *free_lists_[kNumberOfSizeClasses]; + void CommitBack(); +}; + +// Fake stack frame contains local variables of one function. +// This struct should fit into a stack redzone (32 bytes). +struct FakeFrame { + uintptr_t magic; // Modified by the instrumented code. + uintptr_t descr; // Modified by the instrumented code. + FakeFrame *next; + uint64_t real_stack : 48; + uint64_t size_minus_one : 16; +}; + +struct FakeFrameFifo { + public: + void FifoPush(FakeFrame *node); + FakeFrame *FifoPop(); + private: + FakeFrame *first_, *last_; +}; + +class FakeFrameLifo { + public: + void LifoPush(FakeFrame *node) { + node->next = top_; + top_ = node; + } + void LifoPop() { + CHECK(top_); + top_ = top_->next; + } + FakeFrame *top() { return top_; } + private: + FakeFrame *top_; +}; + +// For each thread we create a fake stack and place stack objects on this fake +// stack instead of the real stack. The fake stack is not really a stack but +// a fast malloc-like allocator so that when a function exits the fake stack +// is not poped but remains there for quite some time until gets used again. +// So, we poison the objects on the fake stack when function returns. +// It helps us find use-after-return bugs. +// We can not rely on __asan_stack_free being called on every function exit, +// so we maintain a lifo list of all current fake frames and update it on every +// call to __asan_stack_malloc. +class FakeStack { + public: + FakeStack(); + explicit FakeStack(LinkerInitialized) {} + void Init(size_t stack_size); + void StopUsingFakeStack() { alive_ = false; } + void Cleanup(); + uintptr_t AllocateStack(size_t size, size_t real_stack); + static void OnFree(size_t ptr, size_t size, size_t real_stack); + // Return the bottom of the maped region. + uintptr_t AddrIsInFakeStack(uintptr_t addr); + private: + static const size_t kMinStackFrameSizeLog = 9; // Min frame is 512B. + static const size_t kMaxStackFrameSizeLog = 16; // Max stack frame is 64K. + static const size_t kMaxStackMallocSize = 1 << kMaxStackFrameSizeLog; + static const size_t kNumberOfSizeClasses = + kMaxStackFrameSizeLog - kMinStackFrameSizeLog + 1; + + bool AddrIsInSizeClass(uintptr_t addr, size_t size_class); + + // Each size class should be large enough to hold all frames. + size_t ClassMmapSize(size_t size_class); + + size_t ClassSize(size_t size_class) { + return 1UL << (size_class + kMinStackFrameSizeLog); + } + + void DeallocateFrame(FakeFrame *fake_frame); + + size_t ComputeSizeClass(size_t alloc_size); + void AllocateOneSizeClass(size_t size_class); + + size_t stack_size_; + bool alive_; + + uintptr_t allocated_size_classes_[kNumberOfSizeClasses]; + FakeFrameFifo size_classes_[kNumberOfSizeClasses]; + FakeFrameLifo call_stack_; +}; + +void *asan_memalign(size_t alignment, size_t size, AsanStackTrace *stack); +void asan_free(void *ptr, AsanStackTrace *stack); + +void *asan_malloc(size_t size, AsanStackTrace *stack); +void *asan_calloc(size_t nmemb, size_t size, AsanStackTrace *stack); +void *asan_realloc(void *p, size_t size, AsanStackTrace *stack); +void *asan_valloc(size_t size, AsanStackTrace *stack); +void *asan_pvalloc(size_t size, AsanStackTrace *stack); + +int asan_posix_memalign(void **memptr, size_t alignment, size_t size, + AsanStackTrace *stack); + +size_t __asan_mz_size(const void *ptr); +void __asan_mz_force_lock(); +void __asan_mz_force_unlock(); +void DescribeHeapAddress(uintptr_t addr, size_t access_size); + +} // namespace __asan +#endif // ASAN_ALLOCATOR_H diff --git a/lib/asan/asan_globals.cc b/lib/asan/asan_globals.cc new file mode 100644 index 000000000000..f53bf38db4c2 --- /dev/null +++ b/lib/asan/asan_globals.cc @@ -0,0 +1,171 @@ +//===-- asan_globals.cc -----------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Handle globals. +//===----------------------------------------------------------------------===// +#include "asan_interceptors.h" +#include "asan_interface.h" +#include "asan_internal.h" +#include "asan_lock.h" +#include "asan_mapping.h" +#include "asan_stack.h" +#include "asan_stats.h" +#include "asan_thread.h" + +#include <ctype.h> + +namespace __asan { + +typedef __asan_global Global; + +struct ListOfGlobals { + const Global *g; + ListOfGlobals *next; +}; + +static AsanLock mu_for_globals(LINKER_INITIALIZED); +static ListOfGlobals *list_of_globals; +static LowLevelAllocator allocator_for_globals(LINKER_INITIALIZED); + +void PoisonRedZones(const Global &g) { + size_t shadow_rz_size = kGlobalAndStackRedzone >> SHADOW_SCALE; + CHECK(shadow_rz_size == 1 || shadow_rz_size == 2 || shadow_rz_size == 4); + // full right redzone + size_t g_aligned_size = kGlobalAndStackRedzone * + ((g.size + kGlobalAndStackRedzone - 1) / kGlobalAndStackRedzone); + PoisonShadow(g.beg + g_aligned_size, + kGlobalAndStackRedzone, kAsanGlobalRedzoneMagic); + if ((g.size % kGlobalAndStackRedzone) != 0) { + // partial right redzone + uint64_t g_aligned_down_size = kGlobalAndStackRedzone * + (g.size / kGlobalAndStackRedzone); + CHECK(g_aligned_down_size == g_aligned_size - kGlobalAndStackRedzone); + PoisonShadowPartialRightRedzone(g.beg + g_aligned_down_size, + g.size % kGlobalAndStackRedzone, + kGlobalAndStackRedzone, + kAsanGlobalRedzoneMagic); + } +} + +static size_t GetAlignedSize(size_t size) { + return ((size + kGlobalAndStackRedzone - 1) / kGlobalAndStackRedzone) + * kGlobalAndStackRedzone; +} + + // Check if the global is a zero-terminated ASCII string. If so, print it. +void PrintIfASCII(const Global &g) { + for (size_t p = g.beg; p < g.beg + g.size - 1; p++) { + if (!isascii(*(char*)p)) return; + } + if (*(char*)(g.beg + g.size - 1) != 0) return; + Printf(" '%s' is ascii string '%s'\n", g.name, g.beg); +} + +bool DescribeAddrIfMyRedZone(const Global &g, uintptr_t addr) { + if (addr < g.beg - kGlobalAndStackRedzone) return false; + if (addr >= g.beg + g.size_with_redzone) return false; + Printf("%p is located ", addr); + if (addr < g.beg) { + Printf("%d bytes to the left", g.beg - addr); + } else if (addr >= g.beg + g.size) { + Printf("%d bytes to the right", addr - (g.beg + g.size)); + } else { + Printf("%d bytes inside", addr - g.beg); // Can it happen? + } + Printf(" of global variable '%s' (0x%lx) of size %ld\n", + g.name, g.beg, g.size); + PrintIfASCII(g); + return true; +} + + +bool DescribeAddrIfGlobal(uintptr_t addr) { + if (!FLAG_report_globals) return false; + ScopedLock lock(&mu_for_globals); + bool res = false; + for (ListOfGlobals *l = list_of_globals; l; l = l->next) { + const Global &g = *l->g; + if (FLAG_report_globals >= 2) + Printf("Search Global: beg=%p size=%ld name=%s\n", + g.beg, g.size, g.name); + res |= DescribeAddrIfMyRedZone(g, addr); + } + return res; +} + +// Register a global variable. +// This function may be called more than once for every global +// so we store the globals in a map. +static void RegisterGlobal(const Global *g) { + CHECK(asan_inited); + CHECK(FLAG_report_globals); + CHECK(AddrIsInMem(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->size_with_redzone)); + PoisonRedZones(*g); + ListOfGlobals *l = + (ListOfGlobals*)allocator_for_globals.Allocate(sizeof(ListOfGlobals)); + l->g = g; + l->next = list_of_globals; + list_of_globals = l; + if (FLAG_report_globals >= 2) + Report("Added Global: beg=%p size=%ld name=%s\n", + g->beg, g->size, g->name); +} + +static void UnregisterGlobal(const Global *g) { + CHECK(asan_inited); + CHECK(FLAG_report_globals); + CHECK(AddrIsInMem(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->size_with_redzone)); + PoisonShadow(g->beg, g->size_with_redzone, 0); + // We unpoison the shadow memory for the global but we do not remove it from + // the list because that would require O(n^2) time with the current list + // implementation. It might not be worth doing anyway. +} + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +// Register one global with a default redzone. +void __asan_register_global(uintptr_t addr, size_t size, + const char *name) { + if (!FLAG_report_globals) return; + ScopedLock lock(&mu_for_globals); + Global *g = (Global *)allocator_for_globals.Allocate(sizeof(Global)); + g->beg = addr; + g->size = size; + g->size_with_redzone = GetAlignedSize(size) + kGlobalAndStackRedzone; + g->name = name; + RegisterGlobal(g); +} + +// Register an array of globals. +void __asan_register_globals(__asan_global *globals, size_t n) { + if (!FLAG_report_globals) return; + ScopedLock lock(&mu_for_globals); + for (size_t i = 0; i < n; i++) { + RegisterGlobal(&globals[i]); + } +} + +// Unregister an array of globals. +// We must do it when a shared objects gets dlclosed. +void __asan_unregister_globals(__asan_global *globals, size_t n) { + if (!FLAG_report_globals) return; + ScopedLock lock(&mu_for_globals); + for (size_t i = 0; i < n; i++) { + UnregisterGlobal(&globals[i]); + } +} diff --git a/lib/asan/asan_interceptors.cc b/lib/asan/asan_interceptors.cc new file mode 100644 index 000000000000..53ef91ae857b --- /dev/null +++ b/lib/asan/asan_interceptors.cc @@ -0,0 +1,391 @@ +//===-- asan_interceptors.cc ------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Intercept various libc functions to catch buggy memory accesses there. +//===----------------------------------------------------------------------===// +#include "asan_interceptors.h" + +#include "asan_allocator.h" +#include "asan_interface.h" +#include "asan_internal.h" +#include "asan_mapping.h" +#include "asan_stack.h" +#include "asan_stats.h" + +#include <ctype.h> +#include <dlfcn.h> +#include <string.h> +#include <strings.h> + +namespace __asan { + +index_f real_index; +memcmp_f real_memcmp; +memcpy_f real_memcpy; +memmove_f real_memmove; +memset_f real_memset; +strcasecmp_f real_strcasecmp; +strcat_f real_strcat; +strchr_f real_strchr; +strcmp_f real_strcmp; +strcpy_f real_strcpy; +strdup_f real_strdup; +strlen_f real_strlen; +strncasecmp_f real_strncasecmp; +strncmp_f real_strncmp; +strncpy_f real_strncpy; +strnlen_f real_strnlen; + +// Instruments read/write access to a single byte in memory. +// On error calls __asan_report_error, which aborts the program. +__attribute__((noinline)) +static void AccessAddress(uintptr_t address, bool isWrite) { + if (__asan_address_is_poisoned((void*)address)) { + GET_BP_PC_SP; + __asan_report_error(pc, bp, sp, address, isWrite, /* access_size */ 1); + } +} + +// We implement ACCESS_MEMORY_RANGE, ASAN_READ_RANGE, +// and ASAN_WRITE_RANGE as macro instead of function so +// that no extra frames are created, and stack trace contains +// relevant information only. + +// Instruments read/write access to a memory range. +// More complex implementation is possible, for now just +// checking the first and the last byte of a range. +#define ACCESS_MEMORY_RANGE(offset, size, isWrite) do { \ + if (size > 0) { \ + uintptr_t ptr = (uintptr_t)(offset); \ + AccessAddress(ptr, isWrite); \ + AccessAddress(ptr + (size) - 1, isWrite); \ + } \ +} while (0) + +#define ASAN_READ_RANGE(offset, size) do { \ + ACCESS_MEMORY_RANGE(offset, size, false); \ +} while (0) + +#define ASAN_WRITE_RANGE(offset, size) do { \ + ACCESS_MEMORY_RANGE(offset, size, true); \ +} while (0) + +// Behavior of functions like "memcpy" or "strcpy" is undefined +// if memory intervals overlap. We report error in this case. +// Macro is used to avoid creation of new frames. +static inline bool RangesOverlap(const char *offset1, size_t length1, + const char *offset2, size_t length2) { + return !((offset1 + length1 <= offset2) || (offset2 + length2 <= offset1)); +} +#define CHECK_RANGES_OVERLAP(name, _offset1, length1, _offset2, length2) do { \ + const char *offset1 = (const char*)_offset1; \ + const char *offset2 = (const char*)_offset2; \ + if (RangesOverlap(offset1, length1, offset2, length2)) { \ + Report("ERROR: AddressSanitizer %s-param-overlap: " \ + "memory ranges [%p,%p) and [%p, %p) overlap\n", \ + name, offset1, offset1 + length1, offset2, offset2 + length2); \ + PRINT_CURRENT_STACK(); \ + ShowStatsAndAbort(); \ + } \ +} while (0) + +#define ENSURE_ASAN_INITED() do { \ + CHECK(!asan_init_is_running); \ + if (!asan_inited) { \ + __asan_init(); \ + } \ +} while (0) + +size_t internal_strlen(const char *s) { + size_t i = 0; + while (s[i]) i++; + return i; +} + +size_t internal_strnlen(const char *s, size_t maxlen) { + if (real_strnlen != NULL) { + return real_strnlen(s, maxlen); + } + size_t i = 0; + while (i < maxlen && s[i]) i++; + return i; +} + +void* internal_memchr(const void* s, int c, size_t n) { + const char* t = (char*)s; + for (size_t i = 0; i < n; ++i, ++t) + if (*t == c) + return (void*)t; + return NULL; +} + +int internal_memcmp(const void* s1, const void* s2, size_t n) { + const char* t1 = (char*)s1; + const char* t2 = (char*)s2; + for (size_t i = 0; i < n; ++i, ++t1, ++t2) + if (*t1 != *t2) + return *t1 < *t2 ? -1 : 1; + return 0; +} + +void InitializeAsanInterceptors() { +#ifndef __APPLE__ + INTERCEPT_FUNCTION(index); +#else + OVERRIDE_FUNCTION(index, WRAP(strchr)); +#endif + INTERCEPT_FUNCTION(memcmp); + INTERCEPT_FUNCTION(memcpy); + INTERCEPT_FUNCTION(memmove); + INTERCEPT_FUNCTION(memset); + INTERCEPT_FUNCTION(strcasecmp); + INTERCEPT_FUNCTION(strcat); // NOLINT + INTERCEPT_FUNCTION(strchr); + INTERCEPT_FUNCTION(strcmp); + INTERCEPT_FUNCTION(strcpy); // NOLINT + INTERCEPT_FUNCTION(strdup); + INTERCEPT_FUNCTION(strlen); + INTERCEPT_FUNCTION(strncasecmp); + INTERCEPT_FUNCTION(strncmp); + INTERCEPT_FUNCTION(strncpy); +#ifndef __APPLE__ + INTERCEPT_FUNCTION(strnlen); +#endif + if (FLAG_v > 0) { + Printf("AddressSanitizer: libc interceptors initialized\n"); + } +} + +} // namespace __asan + +// ---------------------- Wrappers ---------------- {{{1 +using namespace __asan; // NOLINT + +static inline int CharCmp(unsigned char c1, unsigned char c2) { + return (c1 == c2) ? 0 : (c1 < c2) ? -1 : 1; +} + +static inline int CharCaseCmp(unsigned char c1, unsigned char c2) { + int c1_low = tolower(c1); + int c2_low = tolower(c2); + return c1_low - c2_low; +} + +int WRAP(memcmp)(const void *a1, const void *a2, size_t size) { + ENSURE_ASAN_INITED(); + unsigned char c1 = 0, c2 = 0; + const unsigned char *s1 = (const unsigned char*)a1; + const unsigned char *s2 = (const unsigned char*)a2; + size_t i; + for (i = 0; i < size; i++) { + c1 = s1[i]; + c2 = s2[i]; + if (c1 != c2) break; + } + ASAN_READ_RANGE(s1, Min(i + 1, size)); + ASAN_READ_RANGE(s2, Min(i + 1, size)); + return CharCmp(c1, c2); +} + +void *WRAP(memcpy)(void *to, const void *from, size_t size) { + // memcpy is called during __asan_init() from the internals + // of printf(...). + if (asan_init_is_running) { + return real_memcpy(to, from, size); + } + ENSURE_ASAN_INITED(); + if (FLAG_replace_intrin) { + CHECK_RANGES_OVERLAP("memcpy", to, size, from, size); + ASAN_WRITE_RANGE(from, size); + ASAN_READ_RANGE(to, size); + } + return real_memcpy(to, from, size); +} + +void *WRAP(memmove)(void *to, const void *from, size_t size) { + ENSURE_ASAN_INITED(); + if (FLAG_replace_intrin) { + ASAN_WRITE_RANGE(from, size); + ASAN_READ_RANGE(to, size); + } + return real_memmove(to, from, size); +} + +void *WRAP(memset)(void *block, int c, size_t size) { + // memset is called inside INTERCEPT_FUNCTION on Mac. + if (asan_init_is_running) { + return real_memset(block, c, size); + } + ENSURE_ASAN_INITED(); + if (FLAG_replace_intrin) { + ASAN_WRITE_RANGE(block, size); + } + return real_memset(block, c, size); +} + +// Note that on Linux index and strchr are definined differently depending on +// the compiler (gcc vs clang). +// see __CORRECT_ISO_CPP_STRING_H_PROTO in /usr/include/string.h + +#ifndef __APPLE__ +char *WRAP(index)(const char *str, int c) + __attribute__((alias(WRAPPER_NAME(strchr)))); +#endif + +char *WRAP(strchr)(const char *str, int c) { + ENSURE_ASAN_INITED(); + char *result = real_strchr(str, c); + if (FLAG_replace_str) { + size_t bytes_read = (result ? result - str : real_strlen(str)) + 1; + ASAN_READ_RANGE(str, bytes_read); + } + return result; +} + +int WRAP(strcasecmp)(const char *s1, const char *s2) { + ENSURE_ASAN_INITED(); + unsigned char c1, c2; + size_t i; + for (i = 0; ; i++) { + c1 = (unsigned char)s1[i]; + c2 = (unsigned char)s2[i]; + if (CharCaseCmp(c1, c2) != 0 || c1 == '\0') break; + } + ASAN_READ_RANGE(s1, i + 1); + ASAN_READ_RANGE(s2, i + 1); + return CharCaseCmp(c1, c2); +} + +char *WRAP(strcat)(char *to, const char *from) { // NOLINT + ENSURE_ASAN_INITED(); + if (FLAG_replace_str) { + size_t from_length = real_strlen(from); + ASAN_READ_RANGE(from, from_length + 1); + if (from_length > 0) { + size_t to_length = real_strlen(to); + ASAN_READ_RANGE(to, to_length); + ASAN_WRITE_RANGE(to + to_length, from_length + 1); + CHECK_RANGES_OVERLAP("strcat", to, to_length + 1, from, from_length + 1); + } + } + return real_strcat(to, from); +} + +int WRAP(strcmp)(const char *s1, const char *s2) { + // strcmp is called from malloc_default_purgeable_zone() + // in __asan::ReplaceSystemAlloc() on Mac. + if (asan_init_is_running) { + return real_strcmp(s1, s2); + } + unsigned char c1, c2; + size_t i; + for (i = 0; ; i++) { + c1 = (unsigned char)s1[i]; + c2 = (unsigned char)s2[i]; + if (c1 != c2 || c1 == '\0') break; + } + ASAN_READ_RANGE(s1, i + 1); + ASAN_READ_RANGE(s2, i + 1); + return CharCmp(c1, c2); +} + +char *WRAP(strcpy)(char *to, const char *from) { // NOLINT + // strcpy is called from malloc_default_purgeable_zone() + // in __asan::ReplaceSystemAlloc() on Mac. + if (asan_init_is_running) { + return real_strcpy(to, from); + } + ENSURE_ASAN_INITED(); + if (FLAG_replace_str) { + size_t from_size = real_strlen(from) + 1; + CHECK_RANGES_OVERLAP("strcpy", to, from_size, from, from_size); + ASAN_READ_RANGE(from, from_size); + ASAN_WRITE_RANGE(to, from_size); + } + return real_strcpy(to, from); +} + +char *WRAP(strdup)(const char *s) { + ENSURE_ASAN_INITED(); + if (FLAG_replace_str) { + size_t length = real_strlen(s); + ASAN_READ_RANGE(s, length + 1); + } + return real_strdup(s); +} + +size_t WRAP(strlen)(const char *s) { + // strlen is called from malloc_default_purgeable_zone() + // in __asan::ReplaceSystemAlloc() on Mac. + if (asan_init_is_running) { + return real_strlen(s); + } + ENSURE_ASAN_INITED(); + size_t length = real_strlen(s); + if (FLAG_replace_str) { + ASAN_READ_RANGE(s, length + 1); + } + return length; +} + +int WRAP(strncasecmp)(const char *s1, const char *s2, size_t size) { + ENSURE_ASAN_INITED(); + unsigned char c1 = 0, c2 = 0; + size_t i; + for (i = 0; i < size; i++) { + c1 = (unsigned char)s1[i]; + c2 = (unsigned char)s2[i]; + if (CharCaseCmp(c1, c2) != 0 || c1 == '\0') break; + } + ASAN_READ_RANGE(s1, Min(i + 1, size)); + ASAN_READ_RANGE(s2, Min(i + 1, size)); + return CharCaseCmp(c1, c2); +} + +int WRAP(strncmp)(const char *s1, const char *s2, size_t size) { + // strncmp is called from malloc_default_purgeable_zone() + // in __asan::ReplaceSystemAlloc() on Mac. + if (asan_init_is_running) { + return real_strncmp(s1, s2, size); + } + unsigned char c1 = 0, c2 = 0; + size_t i; + for (i = 0; i < size; i++) { + c1 = (unsigned char)s1[i]; + c2 = (unsigned char)s2[i]; + if (c1 != c2 || c1 == '\0') break; + } + ASAN_READ_RANGE(s1, Min(i + 1, size)); + ASAN_READ_RANGE(s2, Min(i + 1, size)); + return CharCmp(c1, c2); +} + +char *WRAP(strncpy)(char *to, const char *from, size_t size) { + ENSURE_ASAN_INITED(); + if (FLAG_replace_str) { + size_t from_size = Min(size, internal_strnlen(from, size) + 1); + CHECK_RANGES_OVERLAP("strncpy", to, from_size, from, from_size); + ASAN_READ_RANGE(from, from_size); + ASAN_WRITE_RANGE(to, size); + } + return real_strncpy(to, from, size); +} + +#ifndef __APPLE__ +size_t WRAP(strnlen)(const char *s, size_t maxlen) { + ENSURE_ASAN_INITED(); + size_t length = real_strnlen(s, maxlen); + if (FLAG_replace_str) { + ASAN_READ_RANGE(s, Min(length + 1, maxlen)); + } + return length; +} +#endif diff --git a/lib/asan/asan_interceptors.h b/lib/asan/asan_interceptors.h new file mode 100644 index 000000000000..07b94208197d --- /dev/null +++ b/lib/asan/asan_interceptors.h @@ -0,0 +1,134 @@ +//===-- asan_interceptors.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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header for asan_interceptors.cc +//===----------------------------------------------------------------------===// +#ifndef ASAN_INTERCEPTORS_H +#define ASAN_INTERCEPTORS_H + +#include "asan_internal.h" + +// To replace weak system functions on Linux we just need to declare functions +// with same names in our library and then obtain the real function pointers +// using dlsym(). This is not so on Mac OS, where the two-level namespace makes +// our replacement functions invisible to other libraries. This may be overcomed +// using the DYLD_FORCE_FLAT_NAMESPACE, but some errors loading the shared +// libraries in Chromium were noticed when doing so. +// Instead we use mach_override, a handy framework for patching functions at +// runtime. To avoid possible name clashes, our replacement functions have +// the "wrap_" prefix on Mac. +// +// After interception, the calls to system functions will be substituted by +// calls to our interceptors. We store pointers to system function f() +// in __asan::real_f(). +// +// TODO(glider): mach_override_ptr() tends to spend too much time +// in allocateBranchIsland(). This should be ok for real-word +// application, but slows down our tests which fork too many children. +#ifdef __APPLE__ +#include "mach_override/mach_override.h" +#define WRAP(x) wrap_##x +#define WRAPPER_NAME(x) "wrap_"#x + +#define OVERRIDE_FUNCTION(oldfunc, newfunc) \ + CHECK(0 == __asan_mach_override_ptr((void*)(oldfunc), \ + (void*)(newfunc), \ + (void**)&real_##oldfunc)); \ + CHECK(real_##oldfunc != NULL); + +#define OVERRIDE_FUNCTION_IF_EXISTS(oldfunc, newfunc) \ + do { __asan_mach_override_ptr((void*)(oldfunc), \ + (void*)(newfunc), \ + (void**)&real_##oldfunc); } while (0) + +#define INTERCEPT_FUNCTION(func) \ + OVERRIDE_FUNCTION(func, WRAP(func)) + +#define INTERCEPT_FUNCTION_IF_EXISTS(func) \ + OVERRIDE_FUNCTION_IF_EXISTS(func, WRAP(func)) + +#else // __linux__ +#define WRAP(x) x +#define WRAPPER_NAME(x) #x + +#define INTERCEPT_FUNCTION(func) \ + CHECK((real_##func = (func##_f)dlsym(RTLD_NEXT, #func))); + +#define INTERCEPT_FUNCTION_IF_EXISTS(func) \ + do { real_##func = (func##_f)dlsym(RTLD_NEXT, #func); } while (0) +#endif + +#ifdef __APPLE__ +int WRAP(memcmp)(const void *a1, const void *a2, size_t size); +void *WRAP(memcpy)(void *to, const void *from, size_t size); +void *WRAP(memmove)(void *to, const void *from, size_t size); +void *WRAP(memset)(void *block, int c, size_t size); +int WRAP(strcasecmp)(const char *s1, const char *s2); +char *WRAP(strcat)(char *to, const char *from); // NOLINT +char *WRAP(strchr)(const char *string, int c); +int WRAP(strcmp)(const char *s1, const char *s2); +char *WRAP(strcpy)(char *to, const char *from); // NOLINT +char *WRAP(strdup)(const char *s); +size_t WRAP(strlen)(const char *s); +int WRAP(strncasecmp)(const char *s1, const char *s2, size_t n); +int WRAP(strncmp)(const char *s1, const char *s2, size_t size); +char *WRAP(strncpy)(char *to, const char *from, size_t size); +#endif + +namespace __asan { + +typedef void* (*index_f)(const char *string, int c); +typedef int (*memcmp_f)(const void *a1, const void *a2, size_t size); +typedef void* (*memcpy_f)(void *to, const void *from, size_t size); +typedef void* (*memmove_f)(void *to, const void *from, size_t size); +typedef void* (*memset_f)(void *block, int c, size_t size); +typedef int (*strcasecmp_f)(const char *s1, const char *s2); +typedef char* (*strcat_f)(char *to, const char *from); +typedef char* (*strchr_f)(const char *str, int c); +typedef int (*strcmp_f)(const char *s1, const char *s2); +typedef char* (*strcpy_f)(char *to, const char *from); +typedef char* (*strdup_f)(const char *s); +typedef size_t (*strlen_f)(const char *s); +typedef int (*strncasecmp_f)(const char *s1, const char *s2, size_t n); +typedef int (*strncmp_f)(const char *s1, const char *s2, size_t size); +typedef char* (*strncpy_f)(char *to, const char *from, size_t size); +typedef size_t (*strnlen_f)(const char *s, size_t maxlen); + +// __asan::real_X() holds pointer to library implementation of X(). +extern index_f real_index; +extern memcmp_f real_memcmp; +extern memcpy_f real_memcpy; +extern memmove_f real_memmove; +extern memset_f real_memset; +extern strcasecmp_f real_strcasecmp; +extern strcat_f real_strcat; +extern strchr_f real_strchr; +extern strcmp_f real_strcmp; +extern strcpy_f real_strcpy; +extern strdup_f real_strdup; +extern strlen_f real_strlen; +extern strncasecmp_f real_strncasecmp; +extern strncmp_f real_strncmp; +extern strncpy_f real_strncpy; +extern strnlen_f real_strnlen; + +// __asan::internal_X() is the implementation of X() for use in RTL. +size_t internal_strlen(const char *s); +size_t internal_strnlen(const char *s, size_t maxlen); +void* internal_memchr(const void* s, int c, size_t n); +int internal_memcmp(const void* s1, const void* s2, size_t n); + +// Initializes pointers to str*/mem* functions. +void InitializeAsanInterceptors(); + +} // namespace __asan + +#endif // ASAN_INTERCEPTORS_H diff --git a/lib/asan/asan_interface.h b/lib/asan/asan_interface.h new file mode 100644 index 000000000000..7506586fefeb --- /dev/null +++ b/lib/asan/asan_interface.h @@ -0,0 +1,135 @@ +//===-- asan_interface.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 a part of AddressSanitizer, an address sanity checker. +// +// This header can be included by the instrumented program to fetch +// data (mostly allocator statistics) from ASan runtime library. +//===----------------------------------------------------------------------===// +#ifndef ASAN_INTERFACE_H +#define ASAN_INTERFACE_H + +#include <stdint.h> // for __WORDSIZE +#include <stdlib.h> // for size_t + +// This header should NOT include any other headers from ASan runtime. +// All functions in this header are extern "C" and start with __asan_. + +extern "C" { + // This function should be called at the very beginning of the process, + // before any instrumented code is executed and before any call to malloc. + void __asan_init() + __attribute__((visibility("default"))); + + // This function should be called by the instrumented code. + // 'addr' is the address of a global variable called 'name' of 'size' bytes. + void __asan_register_global(uintptr_t addr, size_t size, const char *name) + __attribute__((visibility("default"))); + + // This structure describes an instrumented global variable. + struct __asan_global { + size_t beg; // The address of the global. + size_t size; // The original size of the global. + size_t size_with_redzone; // The size with the redzone. + const char *name; // Name as a C string. + }; + + // These two functions should be called by the instrumented code. + // 'globals' is an array of structures describing 'n' globals. + void __asan_register_globals(__asan_global *globals, size_t n) + __attribute__((visibility("default"))); + void __asan_unregister_globals(__asan_global *globals, size_t n) + __attribute__((visibility("default"))); + + // These two functions are used by the instrumented code in the + // use-after-return mode. __asan_stack_malloc allocates size bytes of + // fake stack and __asan_stack_free poisons it. real_stack is a pointer to + // the real stack region. + size_t __asan_stack_malloc(size_t size, size_t real_stack) + __attribute__((visibility("default"))); + void __asan_stack_free(size_t ptr, size_t size, size_t real_stack) + __attribute__((visibility("default"))); + + // Marks memory region [addr, addr+size) as unaddressable. + // This memory must be previously allocated by the user program. Accessing + // addresses in this region from instrumented code is forbidden until + // this region is unpoisoned. This function is not guaranteed to poison + // the whole region - it may poison only subregion of [addr, addr+size) due + // to ASan alignment restrictions. + // Method is NOT thread-safe in the sense that no two threads can + // (un)poison memory in the same memory region simultaneously. + void __asan_poison_memory_region(void const volatile *addr, size_t size); + // Marks memory region [addr, addr+size) as addressable. + // This memory must be previously allocated by the user program. Accessing + // addresses in this region is allowed until this region is poisoned again. + // This function may unpoison a superregion of [addr, addr+size) due to + // ASan alignment restrictions. + // Method is NOT thread-safe in the sense that no two threads can + // (un)poison memory in the same memory region simultaneously. + void __asan_unpoison_memory_region(void const volatile *addr, size_t size); + +// User code should use macro instead of functions. +#if defined(__has_feature) && __has_feature(address_sanitizer) +#define ASAN_POISON_MEMORY_REGION(addr, size) \ + __asan_poison_memory_region((addr), (size)) +#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + __asan_unpoison_memory_region((addr), (size)) +#else +#define ASAN_POISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) +#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) +#endif + + // Returns true iff addr is poisoned (i.e. 1-byte read/write access to this + // address will result in error report from AddressSanitizer). + bool __asan_address_is_poisoned(void const volatile *addr); + + // This is an internal function that is called to report an error. + // However it is still a part of the interface because users may want to + // set a breakpoint on this function in a debugger. + void __asan_report_error(uintptr_t pc, uintptr_t bp, uintptr_t sp, + uintptr_t addr, bool is_write, size_t access_size) + __attribute__((visibility("default"))); + + // Sets the exit code to use when reporting an error. + // Returns the old value. + int __asan_set_error_exit_code(int exit_code); + + // Returns the estimated number of bytes that will be reserved by allocator + // for request of "size" bytes. If ASan allocator can't allocate that much + // memory, returns the maximal possible allocation size, otherwise returns + // "size". + size_t __asan_get_estimated_allocated_size(size_t size); + // Returns true if p is NULL or if p was returned by the ASan allocator and + // is not yet freed. + bool __asan_get_ownership(const void *p); + // Returns the number of bytes reserved for the pointer p. + // Requires (get_ownership(p) == true). + size_t __asan_get_allocated_size(const void *p); + // Number of bytes, allocated and not yet freed by the application. + size_t __asan_get_current_allocated_bytes(); + // Number of bytes, mmaped by asan allocator to fulfill allocation requests. + // Generally, for request of X bytes, allocator can reserve and add to free + // lists a large number of chunks of size X to use them for future requests. + // All these chunks count toward the heap size. Currently, allocator never + // releases memory to OS (instead, it just puts freed chunks to free lists). + size_t __asan_get_heap_size(); + // Number of bytes, mmaped by asan allocator, which can be used to fulfill + // allocation requests. When a user program frees memory chunk, it can first + // fall into quarantine and will count toward __asan_get_free_bytes() later. + size_t __asan_get_free_bytes(); + // Number of bytes in unmapped pages, that are released to OS. Currently, + // always returns 0. + size_t __asan_get_unmapped_bytes(); + // Prints accumulated stats to stderr. Used for debugging. + void __asan_print_accumulated_stats(); +} // namespace + +#endif // ASAN_INTERFACE_H diff --git a/lib/asan/asan_internal.h b/lib/asan/asan_internal.h new file mode 100644 index 000000000000..d0790a7c8b7b --- /dev/null +++ b/lib/asan/asan_internal.h @@ -0,0 +1,239 @@ +//===-- asan_internal.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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header which defines various general utilities. +//===----------------------------------------------------------------------===// +#ifndef ASAN_INTERNAL_H +#define ASAN_INTERNAL_H + +#if !defined(__linux__) && !defined(__APPLE__) +# error "This operating system is not supported by AddressSanitizer" +#endif + +#include <stdint.h> // for __WORDSIZE +#include <stdlib.h> // for size_t +#include <unistd.h> // for _exit + +// If __WORDSIZE was undefined by the platform, define it in terms of the +// compiler built-in __LP64__. +#ifndef __WORDSIZE +#if __LP64__ +#define __WORDSIZE 64 +#else +#define __WORDSIZE 32 +#endif +#endif + +#ifdef ANDROID +#include <sys/atomics.h> +#endif + +#if defined(__has_feature) && __has_feature(address_sanitizer) +# error "The AddressSanitizer run-time should not be" + " instrumented by AddressSanitizer" +#endif + +// Build-time configuration options. + +// If set, sysinfo/sysinfo.h will be used to iterate over /proc/maps. +#ifndef ASAN_USE_SYSINFO +# define ASAN_USE_SYSINFO 1 +#endif + +// If set, asan will install its own SEGV signal handler. +#ifndef ASAN_NEEDS_SEGV +# define ASAN_NEEDS_SEGV 1 +#endif + +// If set, asan will intercept C++ exception api call(s). +#ifndef ASAN_HAS_EXCEPTIONS +# define ASAN_HAS_EXCEPTIONS 1 +#endif + +// If set, asan uses the values of SHADOW_SCALE and SHADOW_OFFSET +// provided by the instrumented objects. Otherwise constants are used. +#ifndef ASAN_FLEXIBLE_MAPPING_AND_OFFSET +# define ASAN_FLEXIBLE_MAPPING_AND_OFFSET 0 +#endif + +// All internal functions in asan reside inside the __asan namespace +// to avoid namespace collisions with the user programs. +// Seperate namespace also makes it simpler to distinguish the asan run-time +// functions from the instrumented user code in a profile. +namespace __asan { + +class AsanThread; +struct AsanStackTrace; + +// asan_rtl.cc +void CheckFailed(const char *cond, const char *file, int line); +void ShowStatsAndAbort(); + +// asan_globals.cc +bool DescribeAddrIfGlobal(uintptr_t addr); + +// asan_malloc_linux.cc / asan_malloc_mac.cc +void ReplaceSystemMalloc(); + +void OutOfMemoryMessageAndDie(const char *mem_type, size_t size); + +// asan_linux.cc / asan_mac.cc +void *AsanDoesNotSupportStaticLinkage(); +int AsanOpenReadonly(const char* filename); + +void *AsanMmapFixedNoReserve(uintptr_t fixed_addr, size_t size); +void *AsanMmapFixedReserve(uintptr_t fixed_addr, size_t size); +void *AsanMprotect(uintptr_t fixed_addr, size_t size); +void *AsanMmapSomewhereOrDie(size_t size, const char *where); +void AsanUnmapOrDie(void *ptr, size_t size); + +ssize_t AsanRead(int fd, void *buf, size_t count); +ssize_t AsanWrite(int fd, const void *buf, size_t count); +int AsanClose(int fd); + +// asan_printf.cc +void RawWrite(const char *buffer); +int SNPrint(char *buffer, size_t length, const char *format, ...); +void Printf(const char *format, ...); +void Report(const char *format, ...); + +// Don't use std::min and std::max, to minimize dependency on libstdc++. +template<class T> T Min(T a, T b) { return a < b ? a : b; } +template<class T> T Max(T a, T b) { return a > b ? a : b; } + +// asan_poisoning.cc +// Poisons the shadow memory for "size" bytes starting from "addr". +void PoisonShadow(uintptr_t addr, size_t size, uint8_t value); +// Poisons the shadow memory for "redzone_size" bytes starting from +// "addr + size". +void PoisonShadowPartialRightRedzone(uintptr_t addr, + uintptr_t size, + uintptr_t redzone_size, + uint8_t value); + +extern size_t FLAG_quarantine_size; +extern int FLAG_demangle; +extern bool FLAG_symbolize; +extern int FLAG_v; +extern size_t FLAG_redzone; +extern int FLAG_debug; +extern bool FLAG_poison_shadow; +extern int FLAG_report_globals; +extern size_t FLAG_malloc_context_size; +extern bool FLAG_replace_str; +extern bool FLAG_replace_intrin; +extern bool FLAG_replace_cfallocator; +extern bool FLAG_fast_unwind; +extern bool FLAG_use_fake_stack; +extern size_t FLAG_max_malloc_fill_size; +extern int FLAG_exitcode; +extern bool FLAG_allow_user_poisoning; + +extern int asan_inited; +// Used to avoid infinite recursion in __asan_init(). +extern bool asan_init_is_running; + +enum LinkerInitialized { LINKER_INITIALIZED = 0 }; + +#ifndef ASAN_DIE +#define ASAN_DIE _exit(FLAG_exitcode) +#endif // ASAN_DIE + +#define CHECK(cond) do { if (!(cond)) { \ + CheckFailed(#cond, __FILE__, __LINE__); \ +}}while(0) + +#define RAW_CHECK_MSG(expr, msg) do { \ + if (!(expr)) { \ + RawWrite(msg); \ + ASAN_DIE; \ + } \ +} while (0) + +#define RAW_CHECK(expr) RAW_CHECK_MSG(expr, #expr) + +#define UNIMPLEMENTED() CHECK("unimplemented" && 0) + +#define ASAN_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) + +const size_t kWordSize = __WORDSIZE / 8; +const size_t kWordSizeInBits = 8 * kWordSize; +const size_t kPageSizeBits = 12; +const size_t kPageSize = 1UL << kPageSizeBits; + +#define GET_CALLER_PC() (uintptr_t)__builtin_return_address(0) +#define GET_CURRENT_FRAME() (uintptr_t)__builtin_frame_address(0) + +#define GET_BP_PC_SP \ + uintptr_t bp = GET_CURRENT_FRAME(); \ + uintptr_t pc = GET_CALLER_PC(); \ + uintptr_t local_stack; \ + uintptr_t sp = (uintptr_t)&local_stack; + +// These magic values are written to shadow for better error reporting. +const int kAsanHeapLeftRedzoneMagic = 0xfa; +const int kAsanHeapRightRedzoneMagic = 0xfb; +const int kAsanHeapFreeMagic = 0xfd; +const int kAsanStackLeftRedzoneMagic = 0xf1; +const int kAsanStackMidRedzoneMagic = 0xf2; +const int kAsanStackRightRedzoneMagic = 0xf3; +const int kAsanStackPartialRedzoneMagic = 0xf4; +const int kAsanStackAfterReturnMagic = 0xf5; +const int kAsanUserPoisonedMemoryMagic = 0xf7; +const int kAsanGlobalRedzoneMagic = 0xf9; +const int kAsanInternalHeapMagic = 0xfe; + +static const uintptr_t kCurrentStackFrameMagic = 0x41B58AB3; +static const uintptr_t kRetiredStackFrameMagic = 0x45E0360E; + +// --------------------------- Bit twiddling ------- {{{1 +inline bool IsPowerOfTwo(size_t x) { + return (x & (x - 1)) == 0; +} + +inline size_t RoundUpTo(size_t size, size_t boundary) { + CHECK(IsPowerOfTwo(boundary)); + return (size + boundary - 1) & ~(boundary - 1); +} + +// -------------------------- LowLevelAllocator ----- {{{1 +// A simple low-level memory allocator for internal use. +class LowLevelAllocator { + public: + explicit LowLevelAllocator(LinkerInitialized) {} + // 'size' must be a power of two. + // Requires an external lock. + void *Allocate(size_t size); + private: + char *allocated_end_; + char *allocated_current_; +}; + +// -------------------------- Atomic ---------------- {{{1 +static inline int AtomicInc(int *a) { +#ifdef ANDROID + return __atomic_inc(a) + 1; +#else + return __sync_add_and_fetch(a, 1); +#endif +} + +static inline int AtomicDec(int *a) { +#ifdef ANDROID + return __atomic_dec(a) - 1; +#else + return __sync_add_and_fetch(a, -1); +#endif +} + +} // namespace __asan + +#endif // ASAN_INTERNAL_H diff --git a/lib/asan/asan_linux.cc b/lib/asan/asan_linux.cc new file mode 100644 index 000000000000..26a08ae8f5b6 --- /dev/null +++ b/lib/asan/asan_linux.cc @@ -0,0 +1,101 @@ +//===-- asan_linux.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 a part of AddressSanitizer, an address sanity checker. +// +// Linux-specific details. +//===----------------------------------------------------------------------===// +#ifdef __linux__ + +#include "asan_internal.h" + +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> + +extern char _DYNAMIC[]; + +namespace __asan { + +void *AsanDoesNotSupportStaticLinkage() { + // This will fail to link with -static. + return &_DYNAMIC; +} + +static void *asan_mmap(void *addr, size_t length, int prot, int flags, + int fd, uint64_t offset) { +# if __WORDSIZE == 64 + return (void *)syscall(__NR_mmap, addr, length, prot, flags, fd, offset); +# else + return (void *)syscall(__NR_mmap2, addr, length, prot, flags, fd, offset); +# endif +} + +void *AsanMmapSomewhereOrDie(size_t size, const char *mem_type) { + size = RoundUpTo(size, kPageSize); + void *res = asan_mmap(0, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (res == (void*)-1) { + OutOfMemoryMessageAndDie(mem_type, size); + } + return res; +} + +void *AsanMmapFixedNoReserve(uintptr_t fixed_addr, size_t size) { + return asan_mmap((void*)fixed_addr, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, + 0, 0); +} + +void *AsanMmapFixedReserve(uintptr_t fixed_addr, size_t size) { + return asan_mmap((void*)fixed_addr, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + 0, 0); +} + +void *AsanMprotect(uintptr_t fixed_addr, size_t size) { + return asan_mmap((void*)fixed_addr, size, + PROT_NONE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, + 0, 0); +} + +void AsanUnmapOrDie(void *addr, size_t size) { + if (!addr || !size) return; + int res = syscall(__NR_munmap, addr, size); + if (res != 0) { + Report("Failed to unmap\n"); + ASAN_DIE; + } +} + +ssize_t AsanWrite(int fd, const void *buf, size_t count) { + return (ssize_t)syscall(__NR_write, fd, buf, count); +} + +int AsanOpenReadonly(const char* filename) { + return open(filename, O_RDONLY); +} + +ssize_t AsanRead(int fd, void *buf, size_t count) { + return (ssize_t)syscall(__NR_read, fd, buf, count); +} + +int AsanClose(int fd) { + return close(fd); +} + +} // namespace __asan + +#endif // __linux__ diff --git a/lib/asan/asan_lock.h b/lib/asan/asan_lock.h new file mode 100644 index 000000000000..030fae613c23 --- /dev/null +++ b/lib/asan/asan_lock.h @@ -0,0 +1,100 @@ +//===-- asan_lock.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 a part of AddressSanitizer, an address sanity checker. +// +// A wrapper for a simple lock. +//===----------------------------------------------------------------------===// +#ifndef ASAN_LOCK_H +#define ASAN_LOCK_H + +#include "asan_internal.h" + +// The locks in ASan are global objects and they are never destroyed to avoid +// at-exit races (that is, a lock is being used by other threads while the main +// thread is doing atexit destructors). + +#ifdef __APPLE__ +#include <pthread.h> + +#include <libkern/OSAtomic.h> +namespace __asan { +class AsanLock { + public: + explicit AsanLock(LinkerInitialized) : + mu_(OS_SPINLOCK_INIT), + owner_(0), + is_locked_(false) {} + + void Lock() { + CHECK(owner_ != pthread_self()); + OSSpinLockLock(&mu_); + is_locked_ = true; + owner_ = pthread_self(); + } + void Unlock() { + owner_ = 0; + is_locked_ = false; + OSSpinLockUnlock(&mu_); + } + + bool IsLocked() { + // This is not atomic, e.g. one thread may get different values if another + // one is about to release the lock. + return is_locked_; + } + private: + OSSpinLock mu_; + volatile pthread_t owner_; // for debugging purposes + bool is_locked_; // for silly malloc_introspection_t interface +}; +} // namespace __asan + +#else // assume linux +#include <pthread.h> +namespace __asan { +class AsanLock { + public: + explicit AsanLock(LinkerInitialized) { + // We assume that pthread_mutex_t initialized to all zeroes is a valid + // unlocked mutex. We can not use PTHREAD_MUTEX_INITIALIZER as it triggers + // a gcc warning: + // extended initializer lists only available with -std=c++0x or -std=gnu++0x + } + void Lock() { + pthread_mutex_lock(&mu_); + // pthread_spin_lock(&mu_); + } + void Unlock() { + pthread_mutex_unlock(&mu_); + // pthread_spin_unlock(&mu_); + } + private: + pthread_mutex_t mu_; + // pthread_spinlock_t mu_; +}; +} // namespace __asan +#endif + +namespace __asan { +class ScopedLock { + public: + explicit ScopedLock(AsanLock *mu) : mu_(mu) { + mu_->Lock(); + } + ~ScopedLock() { + mu_->Unlock(); + } + private: + AsanLock *mu_; +}; + +} // namespace __asan + +#endif // ASAN_LOCK_H diff --git a/lib/asan/asan_mac.cc b/lib/asan/asan_mac.cc new file mode 100644 index 000000000000..b202e639b511 --- /dev/null +++ b/lib/asan/asan_mac.cc @@ -0,0 +1,311 @@ +//===-- asan_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 a part of AddressSanitizer, an address sanity checker. +// +// Mac-specific details. +//===----------------------------------------------------------------------===// + +#ifdef __APPLE__ + +#include "asan_mac.h" + +#include "asan_internal.h" +#include "asan_stack.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" + +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> + +#include <new> + +namespace __asan { + +extern dispatch_async_f_f real_dispatch_async_f; +extern dispatch_sync_f_f real_dispatch_sync_f; +extern dispatch_after_f_f real_dispatch_after_f; +extern dispatch_barrier_async_f_f real_dispatch_barrier_async_f; +extern dispatch_group_async_f_f real_dispatch_group_async_f; +extern pthread_workqueue_additem_np_f real_pthread_workqueue_additem_np; + +// No-op. Mac does not support static linkage anyway. +void *AsanDoesNotSupportStaticLinkage() { + return NULL; +} + +static void *asan_mmap(void *addr, size_t length, int prot, int flags, + int fd, uint64_t offset) { + return mmap(addr, length, prot, flags, fd, offset); +} + +ssize_t AsanWrite(int fd, const void *buf, size_t count) { + return write(fd, buf, count); +} + +void *AsanMmapSomewhereOrDie(size_t size, const char *mem_type) { + size = RoundUpTo(size, kPageSize); + void *res = asan_mmap(0, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (res == (void*)-1) { + OutOfMemoryMessageAndDie(mem_type, size); + } + return res; +} + +void *AsanMmapFixedNoReserve(uintptr_t fixed_addr, size_t size) { + return asan_mmap((void*)fixed_addr, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, + 0, 0); +} + +void *AsanMmapFixedReserve(uintptr_t fixed_addr, size_t size) { + return asan_mmap((void*)fixed_addr, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + 0, 0); +} + +void *AsanMprotect(uintptr_t fixed_addr, size_t size) { + return asan_mmap((void*)fixed_addr, size, + PROT_NONE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, + 0, 0); +} + +void AsanUnmapOrDie(void *addr, size_t size) { + if (!addr || !size) return; + int res = munmap(addr, size); + if (res != 0) { + Report("Failed to unmap\n"); + ASAN_DIE; + } +} + +int AsanOpenReadonly(const char* filename) { + return open(filename, O_RDONLY); +} + +ssize_t AsanRead(int fd, void *buf, size_t count) { + return read(fd, buf, count); +} + +int AsanClose(int fd) { + return close(fd); +} + +// Support for the following functions from libdispatch on Mac OS: +// dispatch_async_f() +// dispatch_async() +// dispatch_sync_f() +// dispatch_sync() +// dispatch_after_f() +// dispatch_after() +// dispatch_group_async_f() +// dispatch_group_async() +// TODO(glider): libdispatch API contains other functions that we don't support +// yet. +// +// dispatch_sync() and dispatch_sync_f() are synchronous, although chances are +// they can cause jobs to run on a thread different from the current one. +// TODO(glider): if so, we need a test for this (otherwise we should remove +// them). +// +// The following functions use dispatch_barrier_async_f() (which isn't a library +// function but is exported) and are thus supported: +// dispatch_source_set_cancel_handler_f() +// dispatch_source_set_cancel_handler() +// dispatch_source_set_event_handler_f() +// dispatch_source_set_event_handler() +// +// The reference manual for Grand Central Dispatch is available at +// http://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html +// The implementation details are at +// http://libdispatch.macosforge.org/trac/browser/trunk/src/queue.c + +extern "C" +void asan_dispatch_call_block_and_release(void *block) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_block_context_t *context = (asan_block_context_t*)block; + if (FLAG_v >= 2) { + Report("asan_dispatch_call_block_and_release(): " + "context: %p, pthread_self: %p\n", + block, pthread_self()); + } + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (t) { + // We've already executed a job on this worker thread. Let's reuse the + // AsanThread object. + if (t != asanThreadRegistry().GetMain()) { + // Flush the statistics and update the current thread's tid. + asanThreadRegistry().UnregisterThread(t); + asanThreadRegistry().RegisterThread(t, context->parent_tid, &stack); + } + // Otherwise the worker is being executed on the main thread -- we are + // draining the dispatch queue. + // TODO(glider): any checks for that? + } else { + // It's incorrect to assert that the current thread is not dying: at least + // the callbacks from dispatch_sync() are sometimes called after the TSD is + // destroyed. + t = (AsanThread*)asan_malloc(sizeof(AsanThread), &stack); + new(t) AsanThread(context->parent_tid, + /*start_routine*/NULL, /*arg*/NULL, &stack); + t->Init(); + asanThreadRegistry().SetCurrent(t); + } + // Call the original dispatcher for the block. + context->func(context->block); + asan_free(context, &stack); +} + +} // namespace __asan + +using namespace __asan; // NOLINT + +// Wrap |ctxt| and |func| into an asan_block_context_t. +// The caller retains control of the allocated context. +extern "C" +asan_block_context_t *alloc_asan_context(void *ctxt, dispatch_function_t func, + AsanStackTrace *stack) { + asan_block_context_t *asan_ctxt = + (asan_block_context_t*) asan_malloc(sizeof(asan_block_context_t), stack); + asan_ctxt->block = ctxt; + asan_ctxt->func = func; + AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); + if (FLAG_debug) { + // Sometimes at Chromium teardown this assertion is violated: + // -- a task is created via dispatch_async() on the "CFMachPort" + // thread while doing _dispatch_queue_drain(); + // -- a task is created via dispatch_async_f() on the + // "com.apple.root.default-overcommit-priority" thread while doing + // _dispatch_dispose(). + // TODO(glider): find out what's going on. + CHECK(curr_thread || asanThreadRegistry().IsCurrentThreadDying()); + } + asan_ctxt->parent_tid = asanThreadRegistry().GetCurrentTidOrMinusOne(); + return asan_ctxt; +} + +// TODO(glider): can we reduce code duplication by introducing a macro? +extern "C" +int WRAP(dispatch_async_f)(dispatch_queue_t dq, + void *ctxt, + dispatch_function_t func) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); + if (FLAG_v >= 2) { + Report("dispatch_async_f(): context: %p, pthread_self: %p\n", + asan_ctxt, pthread_self()); + PRINT_CURRENT_STACK(); + } + return real_dispatch_async_f(dq, (void*)asan_ctxt, + asan_dispatch_call_block_and_release); +} + +extern "C" +int WRAP(dispatch_sync_f)(dispatch_queue_t dq, + void *ctxt, + dispatch_function_t func) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); + if (FLAG_v >= 2) { + Report("dispatch_sync_f(): context: %p, pthread_self: %p\n", + asan_ctxt, pthread_self()); + PRINT_CURRENT_STACK(); + } + return real_dispatch_sync_f(dq, (void*)asan_ctxt, + asan_dispatch_call_block_and_release); +} + +extern "C" +int WRAP(dispatch_after_f)(dispatch_time_t when, + dispatch_queue_t dq, + void *ctxt, + dispatch_function_t func) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); + if (FLAG_v >= 2) { + Report("dispatch_after_f: %p\n", asan_ctxt); + PRINT_CURRENT_STACK(); + } + return real_dispatch_after_f(when, dq, (void*)asan_ctxt, + asan_dispatch_call_block_and_release); +} + +extern "C" +void WRAP(dispatch_barrier_async_f)(dispatch_queue_t dq, + void *ctxt, dispatch_function_t func) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); + if (FLAG_v >= 2) { + Report("dispatch_barrier_async_f(): context: %p, pthread_self: %p\n", + asan_ctxt, pthread_self()); + PRINT_CURRENT_STACK(); + } + real_dispatch_barrier_async_f(dq, (void*)asan_ctxt, + asan_dispatch_call_block_and_release); +} + +extern "C" +void WRAP(dispatch_group_async_f)(dispatch_group_t group, + dispatch_queue_t dq, + void *ctxt, dispatch_function_t func) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); + if (FLAG_v >= 2) { + Report("dispatch_group_async_f(): context: %p, pthread_self: %p\n", + asan_ctxt, pthread_self()); + PRINT_CURRENT_STACK(); + } + real_dispatch_group_async_f(group, dq, (void*)asan_ctxt, + asan_dispatch_call_block_and_release); +} + +// The following stuff has been extremely helpful while looking for the +// unhandled functions that spawned jobs on Chromium shutdown. If the verbosity +// level is 2 or greater, we wrap pthread_workqueue_additem_np() in order to +// find the points of worker thread creation (each of such threads may be used +// to run several tasks, that's why this is not enough to support the whole +// libdispatch API. +extern "C" +void *wrap_workitem_func(void *arg) { + if (FLAG_v >= 2) { + Report("wrap_workitem_func: %p, pthread_self: %p\n", arg, pthread_self()); + } + asan_block_context_t *ctxt = (asan_block_context_t*)arg; + worker_t fn = (worker_t)(ctxt->func); + void *result = fn(ctxt->block); + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_free(arg, &stack); + return result; +} + +extern "C" +int WRAP(pthread_workqueue_additem_np)(pthread_workqueue_t workq, + void *(*workitem_func)(void *), void * workitem_arg, + pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + asan_block_context_t *asan_ctxt = + (asan_block_context_t*) asan_malloc(sizeof(asan_block_context_t), &stack); + asan_ctxt->block = workitem_arg; + asan_ctxt->func = (dispatch_function_t)workitem_func; + asan_ctxt->parent_tid = asanThreadRegistry().GetCurrentTidOrMinusOne(); + if (FLAG_v >= 2) { + Report("pthread_workqueue_additem_np: %p\n", asan_ctxt); + PRINT_CURRENT_STACK(); + } + return real_pthread_workqueue_additem_np(workq, wrap_workitem_func, asan_ctxt, + itemhandlep, gencountp); +} + +#endif // __APPLE__ diff --git a/lib/asan/asan_mac.h b/lib/asan/asan_mac.h new file mode 100644 index 000000000000..32739e766cc7 --- /dev/null +++ b/lib/asan/asan_mac.h @@ -0,0 +1,87 @@ +//===-- asan_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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header for asan_mac.cc +//===----------------------------------------------------------------------===// +#ifdef __APPLE__ + +#ifndef ASAN_MAC_H +#define ASAN_MAC_H + +#include "asan_interceptors.h" + +// TODO(glider): need to check if the OS X version is 10.6 or greater. +#include <dispatch/dispatch.h> +#include <setjmp.h> + +typedef void* pthread_workqueue_t; +typedef void* pthread_workitem_handle_t; + +typedef void (*dispatch_function_t)(void *block); +typedef void* (*worker_t)(void *block); +typedef int (*dispatch_async_f_f)(dispatch_queue_t dq, void *ctxt, + dispatch_function_t func); +typedef int (*dispatch_sync_f_f)(dispatch_queue_t dq, void *ctxt, + dispatch_function_t func); +typedef int (*dispatch_after_f_f)(dispatch_time_t when, + dispatch_queue_t dq, void *ctxt, + dispatch_function_t func); +typedef void (*dispatch_barrier_async_f_f)(dispatch_queue_t dq, + void *ctxt, + dispatch_function_t func); +typedef void (*dispatch_group_async_f_f)(dispatch_group_t group, + dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +typedef int (*pthread_workqueue_additem_np_f)(pthread_workqueue_t workq, + void *(*workitem_func)(void *), void * workitem_arg, + pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp); + + +// A wrapper for the ObjC blocks used to support libdispatch. +typedef struct { + void *block; + dispatch_function_t func; + int parent_tid; +} asan_block_context_t; + + +extern "C" { +// dispatch_barrier_async_f() is not declared in <dispatch/dispatch.h>. +void dispatch_barrier_async_f(dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +// Neither is pthread_workqueue_additem_np(). +int pthread_workqueue_additem_np(pthread_workqueue_t workq, + void *(*workitem_func)(void *), void * workitem_arg, + pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp); + +int WRAP(dispatch_async_f)(dispatch_queue_t dq, + void *ctxt, + dispatch_function_t func); +int WRAP(dispatch_sync_f)(dispatch_queue_t dq, + void *ctxt, + dispatch_function_t func); +int WRAP(dispatch_after_f)(dispatch_time_t when, + dispatch_queue_t dq, + void *ctxt, + dispatch_function_t func); +void WRAP(dispatch_barrier_async_f)(dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +void WRAP(dispatch_group_async_f)(dispatch_group_t group, + dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +int WRAP(pthread_workqueue_additem_np)(pthread_workqueue_t workq, + void *(*workitem_func)(void *), void * workitem_arg, + pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp); +} + +#endif // ASAN_MAC_H + +#endif // __APPLE__ diff --git a/lib/asan/asan_malloc_linux.cc b/lib/asan/asan_malloc_linux.cc new file mode 100644 index 000000000000..9dbc7a127fcc --- /dev/null +++ b/lib/asan/asan_malloc_linux.cc @@ -0,0 +1,142 @@ +//===-- asan_malloc_linux.cc ------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Linux-specific malloc interception. +// We simply define functions like malloc, free, realloc, etc. +// They will replace the corresponding libc functions automagically. +//===----------------------------------------------------------------------===// +#ifdef __linux__ + +#include "asan_allocator.h" +#include "asan_interceptors.h" +#include "asan_internal.h" +#include "asan_stack.h" + +#include <malloc.h> + +#define INTERCEPTOR_ATTRIBUTE __attribute__((visibility("default"))) + +#ifdef ANDROID +struct MallocDebug { + void* (*malloc)(size_t bytes); + void (*free)(void* mem); + void* (*calloc)(size_t n_elements, size_t elem_size); + void* (*realloc)(void* oldMem, size_t bytes); + void* (*memalign)(size_t alignment, size_t bytes); +}; + +const MallocDebug asan_malloc_dispatch __attribute__((aligned(32))) = { + malloc, free, calloc, realloc, memalign +}; + +extern "C" const MallocDebug* __libc_malloc_dispatch; + +namespace __asan { +void ReplaceSystemMalloc() { + __libc_malloc_dispatch = &asan_malloc_dispatch; +} +} // namespace __asan + +#else // ANDROID + +namespace __asan { +void ReplaceSystemMalloc() { +} +} // namespace __asan +#endif // ANDROID + +// ---------------------- Replacement functions ---------------- {{{1 +using namespace __asan; // NOLINT + +extern "C" { +INTERCEPTOR_ATTRIBUTE +void free(void *ptr) { + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + asan_free(ptr, &stack); +} + +INTERCEPTOR_ATTRIBUTE +void cfree(void *ptr) { + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + asan_free(ptr, &stack); +} + +INTERCEPTOR_ATTRIBUTE +void *malloc(size_t size) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_malloc(size, &stack); +} + +INTERCEPTOR_ATTRIBUTE +void *calloc(size_t nmemb, size_t size) { + if (!asan_inited) { + // Hack: dlsym calls calloc before real_calloc is retrieved from dlsym. + const size_t kCallocPoolSize = 1024; + static uintptr_t calloc_memory_for_dlsym[kCallocPoolSize]; + static size_t allocated; + size_t size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize; + void *mem = (void*)&calloc_memory_for_dlsym[allocated]; + allocated += size_in_words; + CHECK(allocated < kCallocPoolSize); + return mem; + } + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_calloc(nmemb, size, &stack); +} + +INTERCEPTOR_ATTRIBUTE +void *realloc(void *ptr, size_t size) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_realloc(ptr, size, &stack); +} + +INTERCEPTOR_ATTRIBUTE +void *memalign(size_t boundary, size_t size) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_memalign(boundary, size, &stack); +} + +void* __libc_memalign(size_t align, size_t s) + __attribute__((alias("memalign"))); + +INTERCEPTOR_ATTRIBUTE +struct mallinfo mallinfo() { + struct mallinfo res; + real_memset(&res, 0, sizeof(res)); + return res; +} + +INTERCEPTOR_ATTRIBUTE +int mallopt(int cmd, int value) { + return -1; +} + +INTERCEPTOR_ATTRIBUTE +int posix_memalign(void **memptr, size_t alignment, size_t size) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + // Printf("posix_memalign: %lx %ld\n", alignment, size); + return asan_posix_memalign(memptr, alignment, size, &stack); +} + +INTERCEPTOR_ATTRIBUTE +void *valloc(size_t size) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_valloc(size, &stack); +} + +INTERCEPTOR_ATTRIBUTE +void *pvalloc(size_t size) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_pvalloc(size, &stack); +} +} // extern "C" + +#endif // __linux__ diff --git a/lib/asan/asan_malloc_mac.cc b/lib/asan/asan_malloc_mac.cc new file mode 100644 index 000000000000..8a6f1bc41ff8 --- /dev/null +++ b/lib/asan/asan_malloc_mac.cc @@ -0,0 +1,390 @@ +//===-- asan_rtl.cc ---------------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Mac-specific malloc interception. +//===----------------------------------------------------------------------===// + +#ifdef __APPLE__ + +#include <AvailabilityMacros.h> +#include <CoreFoundation/CFBase.h> +#include <malloc/malloc.h> +#include <setjmp.h> + +#include "asan_allocator.h" +#include "asan_interceptors.h" +#include "asan_internal.h" +#include "asan_stack.h" + +// Similar code is used in Google Perftools, +// http://code.google.com/p/google-perftools. + +// ---------------------- Replacement functions ---------------- {{{1 +using namespace __asan; // NOLINT + +// The free() implementation provided by OS X calls malloc_zone_from_ptr() +// to find the owner of |ptr|. If the result is NULL, an invalid free() is +// reported. Our implementation falls back to asan_free() in this case +// in order to print an ASan-style report. +extern "C" +void free(void *ptr) { + malloc_zone_t *zone = malloc_zone_from_ptr(ptr); + if (zone) { +#if defined(MAC_OS_X_VERSION_10_6) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if ((zone->version >= 6) && (zone->free_definite_size)) { + zone->free_definite_size(zone, ptr, malloc_size(ptr)); + } else { + malloc_zone_free(zone, ptr); + } +#else + malloc_zone_free(zone, ptr); +#endif + } else { + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + asan_free(ptr, &stack); + } +} + +// TODO(glider): do we need both zones? +static malloc_zone_t *system_malloc_zone = NULL; +static malloc_zone_t *system_purgeable_zone = NULL; + +// We need to provide wrappers around all the libc functions. +namespace { +// TODO(glider): the mz_* functions should be united with the Linux wrappers, +// as they are basically copied from there. +size_t mz_size(malloc_zone_t* zone, const void* ptr) { + // Fast path: check whether this pointer belongs to the original malloc zone. + // We cannot just call malloc_zone_from_ptr(), because it in turn + // calls our mz_size(). + if (system_malloc_zone) { + if ((system_malloc_zone->size)(system_malloc_zone, ptr)) return 0; + } + return __asan_mz_size(ptr); +} + +void *mz_malloc(malloc_zone_t *zone, size_t size) { + if (!asan_inited) { + CHECK(system_malloc_zone); + return malloc_zone_malloc(system_malloc_zone, size); + } + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_malloc(size, &stack); +} + +void *cf_malloc(CFIndex size, CFOptionFlags hint, void *info) { + if (!asan_inited) { + CHECK(system_malloc_zone); + return malloc_zone_malloc(system_malloc_zone, size); + } + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_malloc(size, &stack); +} + +void *mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) { + if (!asan_inited) { + // Hack: dlsym calls calloc before real_calloc is retrieved from dlsym. + const size_t kCallocPoolSize = 1024; + static uintptr_t calloc_memory_for_dlsym[kCallocPoolSize]; + static size_t allocated; + size_t size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize; + void *mem = (void*)&calloc_memory_for_dlsym[allocated]; + allocated += size_in_words; + CHECK(allocated < kCallocPoolSize); + return mem; + } + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_calloc(nmemb, size, &stack); +} + +void *mz_valloc(malloc_zone_t *zone, size_t size) { + if (!asan_inited) { + CHECK(system_malloc_zone); + return malloc_zone_valloc(system_malloc_zone, size); + } + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_memalign(kPageSize, size, &stack); +} + +void print_zone_for_ptr(void *ptr) { + malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr); + if (orig_zone) { + if (orig_zone->zone_name) { + Printf("malloc_zone_from_ptr(%p) = %p, which is %s\n", + ptr, orig_zone, orig_zone->zone_name); + } else { + Printf("malloc_zone_from_ptr(%p) = %p, which doesn't have a name\n", + ptr, orig_zone); + } + } else { + Printf("malloc_zone_from_ptr(%p) = NULL\n", ptr); + } +} + +// TODO(glider): the allocation callbacks need to be refactored. +void mz_free(malloc_zone_t *zone, void *ptr) { + if (!ptr) return; + malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr); + // For some reason Chromium calls mz_free() for pointers that belong to + // DefaultPurgeableMallocZone instead of asan_zone. We might want to + // fix this someday. + if (orig_zone == system_purgeable_zone) { + system_purgeable_zone->free(system_purgeable_zone, ptr); + return; + } + if (__asan_mz_size(ptr)) { + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + asan_free(ptr, &stack); + } else { + // Let us just leak this memory for now. + Printf("mz_free(%p) -- attempting to free unallocated memory.\n" + "AddressSanitizer is ignoring this error on Mac OS now.\n", ptr); + print_zone_for_ptr(ptr); + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + stack.PrintStack(); + return; + } +} + +void cf_free(void *ptr, void *info) { + if (!ptr) return; + malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr); + // For some reason Chromium calls mz_free() for pointers that belong to + // DefaultPurgeableMallocZone instead of asan_zone. We might want to + // fix this someday. + if (orig_zone == system_purgeable_zone) { + system_purgeable_zone->free(system_purgeable_zone, ptr); + return; + } + if (__asan_mz_size(ptr)) { + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + asan_free(ptr, &stack); + } else { + // Let us just leak this memory for now. + Printf("cf_free(%p) -- attempting to free unallocated memory.\n" + "AddressSanitizer is ignoring this error on Mac OS now.\n", ptr); + print_zone_for_ptr(ptr); + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + stack.PrintStack(); + return; + } +} + +void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) { + if (!ptr) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_malloc(size, &stack); + } else { + if (__asan_mz_size(ptr)) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_realloc(ptr, size, &stack); + } else { + // We can't recover from reallocating an unknown address, because + // this would require reading at most |size| bytes from + // potentially unaccessible memory. + Printf("mz_realloc(%p) -- attempting to realloc unallocated memory.\n" + "This is an unrecoverable problem, exiting now.\n", ptr); + print_zone_for_ptr(ptr); + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + stack.PrintStack(); + ShowStatsAndAbort(); + return NULL; // unreachable + } + } +} + +void *cf_realloc(void *ptr, CFIndex size, CFOptionFlags hint, void *info) { + if (!ptr) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_malloc(size, &stack); + } else { + if (__asan_mz_size(ptr)) { + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_realloc(ptr, size, &stack); + } else { + // We can't recover from reallocating an unknown address, because + // this would require reading at most |size| bytes from + // potentially unaccessible memory. + Printf("cf_realloc(%p) -- attempting to realloc unallocated memory.\n" + "This is an unrecoverable problem, exiting now.\n", ptr); + print_zone_for_ptr(ptr); + GET_STACK_TRACE_HERE_FOR_FREE(ptr); + stack.PrintStack(); + ShowStatsAndAbort(); + return NULL; // unreachable + } + } +} + +void mz_destroy(malloc_zone_t* zone) { + // A no-op -- we will not be destroyed! + Printf("mz_destroy() called -- ignoring\n"); +} + // from AvailabilityMacros.h +#if defined(MAC_OS_X_VERSION_10_6) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 +void *mz_memalign(malloc_zone_t *zone, size_t align, size_t size) { + if (!asan_inited) { + CHECK(system_malloc_zone); + return malloc_zone_memalign(system_malloc_zone, align, size); + } + GET_STACK_TRACE_HERE_FOR_MALLOC; + return asan_memalign(align, size, &stack); +} + +// This function is currently unused, and we build with -Werror. +#if 0 +void mz_free_definite_size(malloc_zone_t* zone, void *ptr, size_t size) { + // TODO(glider): check that |size| is valid. + UNIMPLEMENTED(); +} +#endif +#endif + +// malloc_introspection callbacks. I'm not clear on what all of these do. +kern_return_t mi_enumerator(task_t task, void *, + unsigned type_mask, vm_address_t zone_address, + memory_reader_t reader, + vm_range_recorder_t recorder) { + // Should enumerate all the pointers we have. Seems like a lot of work. + return KERN_FAILURE; +} + +size_t mi_good_size(malloc_zone_t *zone, size_t size) { + // I think it's always safe to return size, but we maybe could do better. + return size; +} + +boolean_t mi_check(malloc_zone_t *zone) { + UNIMPLEMENTED(); + return true; +} + +void mi_print(malloc_zone_t *zone, boolean_t verbose) { + UNIMPLEMENTED(); + return; +} + +void mi_log(malloc_zone_t *zone, void *address) { + // I don't think we support anything like this +} + +void mi_force_lock(malloc_zone_t *zone) { + __asan_mz_force_lock(); +} + +void mi_force_unlock(malloc_zone_t *zone) { + __asan_mz_force_unlock(); +} + +// This function is currently unused, and we build with -Werror. +#if 0 +void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { + // TODO(csilvers): figure out how to fill these out + // TODO(glider): port this from tcmalloc when ready. + stats->blocks_in_use = 0; + stats->size_in_use = 0; + stats->max_size_in_use = 0; + stats->size_allocated = 0; +} +#endif + +boolean_t mi_zone_locked(malloc_zone_t *zone) { + // UNIMPLEMENTED(); + return false; +} + +} // unnamed namespace + +extern bool kCFUseCollectableAllocator; // is GC on? + +namespace __asan { +void ReplaceSystemMalloc() { + static malloc_introspection_t asan_introspection; + __asan::real_memset(&asan_introspection, 0, sizeof(asan_introspection)); + + asan_introspection.enumerator = &mi_enumerator; + asan_introspection.good_size = &mi_good_size; + asan_introspection.check = &mi_check; + asan_introspection.print = &mi_print; + asan_introspection.log = &mi_log; + asan_introspection.force_lock = &mi_force_lock; + asan_introspection.force_unlock = &mi_force_unlock; + + static malloc_zone_t asan_zone; + __asan::real_memset(&asan_zone, 0, sizeof(malloc_zone_t)); + + // Start with a version 4 zone which is used for OS X 10.4 and 10.5. + asan_zone.version = 4; + asan_zone.zone_name = "asan"; + asan_zone.size = &mz_size; + asan_zone.malloc = &mz_malloc; + asan_zone.calloc = &mz_calloc; + asan_zone.valloc = &mz_valloc; + asan_zone.free = &mz_free; + asan_zone.realloc = &mz_realloc; + asan_zone.destroy = &mz_destroy; + asan_zone.batch_malloc = NULL; + asan_zone.batch_free = NULL; + asan_zone.introspect = &asan_introspection; + + // from AvailabilityMacros.h +#if defined(MAC_OS_X_VERSION_10_6) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + // Switch to version 6 on OSX 10.6 to support memalign. + asan_zone.version = 6; + asan_zone.free_definite_size = 0; + asan_zone.memalign = &mz_memalign; + asan_introspection.zone_locked = &mi_zone_locked; + + // Request the default purgable zone to force its creation. The + // current default zone is registered with the purgable zone for + // doing tiny and small allocs. Sadly, it assumes that the default + // zone is the szone implementation from OS X and will crash if it + // isn't. By creating the zone now, this will be true and changing + // the default zone won't cause a problem. (OS X 10.6 and higher.) + system_purgeable_zone = malloc_default_purgeable_zone(); +#endif + + // Register the ASan zone. At this point, it will not be the + // default zone. + malloc_zone_register(&asan_zone); + + // Unregister and reregister the default zone. Unregistering swaps + // the specified zone with the last one registered which for the + // default zone makes the more recently registered zone the default + // zone. The default zone is then re-registered to ensure that + // allocations made from it earlier will be handled correctly. + // Things are not guaranteed to work that way, but it's how they work now. + system_malloc_zone = malloc_default_zone(); + malloc_zone_unregister(system_malloc_zone); + malloc_zone_register(system_malloc_zone); + // Make sure the default allocator was replaced. + CHECK(malloc_default_zone() == &asan_zone); + + if (FLAG_replace_cfallocator) { + static CFAllocatorContext asan_context = + { /*version*/ 0, /*info*/ &asan_zone, + /*retain*/ NULL, /*release*/ NULL, + /*copyDescription*/NULL, + /*allocate*/ &cf_malloc, + /*reallocate*/ &cf_realloc, + /*deallocate*/ &cf_free, + /*preferredSize*/ NULL }; + CFAllocatorRef cf_asan = + CFAllocatorCreate(kCFAllocatorUseContext, &asan_context); + CFAllocatorSetDefault(cf_asan); + } +} +} // namespace __asan + +#endif // __APPLE__ diff --git a/lib/asan/asan_mapping.h b/lib/asan/asan_mapping.h new file mode 100644 index 000000000000..63aba10a22f6 --- /dev/null +++ b/lib/asan/asan_mapping.h @@ -0,0 +1,100 @@ +//===-- asan_mapping.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 a part of AddressSanitizer, an address sanity checker. +// +// Defines ASan memory mapping. +//===----------------------------------------------------------------------===// +#ifndef ASAN_MAPPING_H +#define ASAN_MAPPING_H + +#include "asan_internal.h" + +// The full explanation of the memory mapping could be found here: +// http://code.google.com/p/address-sanitizer/wiki/AddressSanitizerAlgorithm + +#if ASAN_FLEXIBLE_MAPPING_AND_OFFSET == 1 +extern __attribute__((visibility("default"))) uintptr_t __asan_mapping_scale; +extern __attribute__((visibility("default"))) uintptr_t __asan_mapping_offset; +#define SHADOW_SCALE (__asan_mapping_scale) +#define SHADOW_OFFSET (__asan_mapping_offset) +#else +#define SHADOW_SCALE (3) +#if __WORDSIZE == 32 +#define SHADOW_OFFSET (1 << 29) +#else +#define SHADOW_OFFSET (1ULL << 44) +#endif +#endif // ASAN_FLEXIBLE_MAPPING_AND_OFFSET + +#define SHADOW_GRANULARITY (1ULL << SHADOW_SCALE) +#define MEM_TO_SHADOW(mem) (((mem) >> SHADOW_SCALE) | (SHADOW_OFFSET)) + +#if __WORDSIZE == 64 + static const size_t kHighMemEnd = 0x00007fffffffffffUL; +#else // __WORDSIZE == 32 + static const size_t kHighMemEnd = 0xffffffff; +#endif // __WORDSIZE + + +#define kLowMemBeg 0 +#define kLowMemEnd (SHADOW_OFFSET ? SHADOW_OFFSET - 1 : 0) + +#define kLowShadowBeg SHADOW_OFFSET +#define kLowShadowEnd MEM_TO_SHADOW(kLowMemEnd) + +#define kHighMemBeg (MEM_TO_SHADOW(kHighMemEnd) + 1) + +#define kHighShadowBeg MEM_TO_SHADOW(kHighMemBeg) +#define kHighShadowEnd MEM_TO_SHADOW(kHighMemEnd) + +#define kShadowGapBeg (kLowShadowEnd ? kLowShadowEnd + 1 : 16 * kPageSize) +#define kShadowGapEnd (kHighShadowBeg - 1) + +#define kGlobalAndStackRedzone \ + (SHADOW_GRANULARITY < 32 ? 32 : SHADOW_GRANULARITY) + +namespace __asan { + +static inline bool AddrIsInLowMem(uintptr_t a) { + return a < kLowMemEnd; +} + +static inline bool AddrIsInLowShadow(uintptr_t a) { + return a >= kLowShadowBeg && a <= kLowShadowEnd; +} + +static inline bool AddrIsInHighMem(uintptr_t a) { + return a >= kHighMemBeg && a <= kHighMemEnd; +} + +static inline bool AddrIsInMem(uintptr_t a) { + return AddrIsInLowMem(a) || AddrIsInHighMem(a); +} + +static inline uintptr_t MemToShadow(uintptr_t p) { + CHECK(AddrIsInMem(p)); + return MEM_TO_SHADOW(p); +} + +static inline bool AddrIsInHighShadow(uintptr_t a) { + return a >= kHighShadowBeg && a <= kHighMemEnd; +} + +static inline bool AddrIsInShadow(uintptr_t a) { + return AddrIsInLowShadow(a) || AddrIsInHighShadow(a); +} + +static inline bool AddrIsAlignedByGranularity(uintptr_t a) { + return (a & (SHADOW_GRANULARITY - 1)) == 0; +} + +} // namespace __asan + +#endif // ASAN_MAPPING_H diff --git a/lib/asan/asan_poisoning.cc b/lib/asan/asan_poisoning.cc new file mode 100644 index 000000000000..daa1ad68e46c --- /dev/null +++ b/lib/asan/asan_poisoning.cc @@ -0,0 +1,159 @@ +//===-- asan_poisoning.cc ---------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Shadow memory poisoning by ASan RTL and by user application. +//===----------------------------------------------------------------------===// + +#include "asan_interceptors.h" +#include "asan_interface.h" +#include "asan_internal.h" +#include "asan_mapping.h" + +namespace __asan { + +void PoisonShadow(uintptr_t addr, size_t size, uint8_t value) { + CHECK(AddrIsAlignedByGranularity(addr)); + CHECK(AddrIsAlignedByGranularity(addr + size)); + uintptr_t shadow_beg = MemToShadow(addr); + uintptr_t shadow_end = MemToShadow(addr + size); + real_memset((void*)shadow_beg, value, shadow_end - shadow_beg); +} + +void PoisonShadowPartialRightRedzone(uintptr_t addr, + uintptr_t size, + uintptr_t redzone_size, + uint8_t value) { + CHECK(AddrIsAlignedByGranularity(addr)); + uint8_t *shadow = (uint8_t*)MemToShadow(addr); + for (uintptr_t i = 0; i < redzone_size; + i += SHADOW_GRANULARITY, shadow++) { + if (i + SHADOW_GRANULARITY <= size) { + *shadow = 0; // fully addressable + } else if (i >= size) { + *shadow = (SHADOW_GRANULARITY == 128) ? 0xff : value; // unaddressable + } else { + *shadow = size - i; // first size-i bytes are addressable + } + } +} + + +struct ShadowSegmentEndpoint { + uint8_t *chunk; + int8_t offset; // in [0, SHADOW_GRANULARITY) + int8_t value; // = *chunk; + + explicit ShadowSegmentEndpoint(uintptr_t address) { + chunk = (uint8_t*)MemToShadow(address); + offset = address & (SHADOW_GRANULARITY - 1); + value = *chunk; + } +}; + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +// Current implementation of __asan_(un)poison_memory_region doesn't check +// that user program (un)poisons the memory it owns. It poisons memory +// conservatively, and unpoisons progressively to make sure asan shadow +// mapping invariant is preserved (see detailed mapping description here: +// http://code.google.com/p/address-sanitizer/wiki/AddressSanitizerAlgorithm). +// +// * if user asks to poison region [left, right), the program poisons +// at least [left, AlignDown(right)). +// * if user asks to unpoison region [left, right), the program unpoisons +// at most [AlignDown(left), right). +void __asan_poison_memory_region(void const volatile *addr, size_t size) { + if (!FLAG_allow_user_poisoning || size == 0) return; + uintptr_t beg_addr = (uintptr_t)addr; + uintptr_t end_addr = beg_addr + size; + if (FLAG_v >= 1) { + Printf("Trying to poison memory region [%p, %p)\n", beg_addr, end_addr); + } + ShadowSegmentEndpoint beg(beg_addr); + ShadowSegmentEndpoint end(end_addr); + if (beg.chunk == end.chunk) { + CHECK(beg.offset < end.offset); + int8_t value = beg.value; + CHECK(value == end.value); + // We can only poison memory if the byte in end.offset is unaddressable. + // No need to re-poison memory if it is poisoned already. + if (value > 0 && value <= end.offset) { + if (beg.offset > 0) { + *beg.chunk = Min(value, beg.offset); + } else { + *beg.chunk = kAsanUserPoisonedMemoryMagic; + } + } + return; + } + CHECK(beg.chunk < end.chunk); + if (beg.offset > 0) { + // Mark bytes from beg.offset as unaddressable. + if (beg.value == 0) { + *beg.chunk = beg.offset; + } else { + *beg.chunk = Min(beg.value, beg.offset); + } + beg.chunk++; + } + real_memset(beg.chunk, kAsanUserPoisonedMemoryMagic, end.chunk - beg.chunk); + // Poison if byte in end.offset is unaddressable. + if (end.value > 0 && end.value <= end.offset) { + *end.chunk = kAsanUserPoisonedMemoryMagic; + } +} + +void __asan_unpoison_memory_region(void const volatile *addr, size_t size) { + if (!FLAG_allow_user_poisoning || size == 0) return; + uintptr_t beg_addr = (uintptr_t)addr; + uintptr_t end_addr = beg_addr + size; + if (FLAG_v >= 1) { + Printf("Trying to unpoison memory region [%p, %p)\n", beg_addr, end_addr); + } + ShadowSegmentEndpoint beg(beg_addr); + ShadowSegmentEndpoint end(end_addr); + if (beg.chunk == end.chunk) { + CHECK(beg.offset < end.offset); + int8_t value = beg.value; + CHECK(value == end.value); + // We unpoison memory bytes up to enbytes up to end.offset if it is not + // unpoisoned already. + if (value != 0) { + *beg.chunk = Max(value, end.offset); + } + return; + } + CHECK(beg.chunk < end.chunk); + if (beg.offset > 0) { + *beg.chunk = 0; + beg.chunk++; + } + real_memset(beg.chunk, 0, end.chunk - beg.chunk); + if (end.offset > 0 && end.value != 0) { + *end.chunk = Max(end.value, end.offset); + } +} + +bool __asan_address_is_poisoned(void const volatile *addr) { + const size_t kAccessSize = 1; + uintptr_t address = (uintptr_t)addr; + uint8_t *shadow_address = (uint8_t*)MemToShadow(address); + int8_t shadow_value = *shadow_address; + if (shadow_value) { + uint8_t last_accessed_byte = (address & (SHADOW_GRANULARITY - 1)) + + kAccessSize - 1; + return (last_accessed_byte >= shadow_value); + } + return false; +} diff --git a/lib/asan/asan_printf.cc b/lib/asan/asan_printf.cc new file mode 100644 index 000000000000..a3d06ff67ad2 --- /dev/null +++ b/lib/asan/asan_printf.cc @@ -0,0 +1,181 @@ +//===-- asan_printf.cc ------------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Internal printf function, used inside ASan run-time library. +// We can't use libc printf because we intercept some of the functions used +// inside it. +//===----------------------------------------------------------------------===// + +#include "asan_internal.h" +#include "asan_interceptors.h" + +#include <stdarg.h> + +namespace __asan { + +void RawWrite(const char *buffer) { + static const char *kRawWriteError = "RawWrite can't output requested buffer!"; + ssize_t length = (ssize_t)internal_strlen(buffer); + if (length != AsanWrite(2, buffer, length)) { + AsanWrite(2, kRawWriteError, internal_strlen(kRawWriteError)); + ASAN_DIE; + } +} + +static inline int AppendChar(char **buff, const char *buff_end, char c) { + if (*buff < buff_end) { + **buff = c; + (*buff)++; + } + return 1; +} + +// Appends number in a given base to buffer. If its length is less than +// "minimal_num_length", it is padded with leading zeroes. +static int AppendUnsigned(char **buff, const char *buff_end, uint64_t num, + uint8_t base, uint8_t minimal_num_length) { + size_t const kMaxLen = 30; + RAW_CHECK(base == 10 || base == 16); + RAW_CHECK(minimal_num_length < kMaxLen); + size_t num_buffer[kMaxLen]; + size_t pos = 0; + do { + RAW_CHECK_MSG(pos < kMaxLen, "appendNumber buffer overflow"); + num_buffer[pos++] = num % base; + num /= base; + } while (num > 0); + while (pos < minimal_num_length) num_buffer[pos++] = 0; + int result = 0; + while (pos-- > 0) { + size_t digit = num_buffer[pos]; + result += AppendChar(buff, buff_end, (digit < 10) ? '0' + digit + : 'a' + digit - 10); + } + return result; +} + +static inline int AppendSignedDecimal(char **buff, const char *buff_end, + int64_t num) { + int result = 0; + if (num < 0) { + result += AppendChar(buff, buff_end, '-'); + num = -num; + } + result += AppendUnsigned(buff, buff_end, (uint64_t)num, 10, 0); + return result; +} + +static inline int AppendString(char **buff, const char *buff_end, + const char *s) { + // Avoid library functions like stpcpy here. + RAW_CHECK(s); + int result = 0; + for (; *s; s++) { + result += AppendChar(buff, buff_end, *s); + } + return result; +} + +static inline int AppendPointer(char **buff, const char *buff_end, + uint64_t ptr_value) { + int result = 0; + result += AppendString(buff, buff_end, "0x"); + result += AppendUnsigned(buff, buff_end, ptr_value, 16, + (__WORDSIZE == 64) ? 12 : 8); + return result; +} + +static int VSNPrintf(char *buff, int buff_length, + const char *format, va_list args) { + static const char *kPrintfFormatsHelp = "Supported Printf formats: " + "%%[l]{d,u,x}; %%p; %%s"; + RAW_CHECK(format); + RAW_CHECK(buff_length > 0); + const char *buff_end = &buff[buff_length - 1]; + const char *cur = format; + int result = 0; + for (; *cur; cur++) { + if (*cur == '%') { + cur++; + bool have_l = (*cur == 'l'); + cur += have_l; + int64_t dval; + uint64_t uval, xval; + switch (*cur) { + case 'd': dval = have_l ? va_arg(args, intptr_t) + : va_arg(args, int); + result += AppendSignedDecimal(&buff, buff_end, dval); + break; + case 'u': uval = have_l ? va_arg(args, uintptr_t) + : va_arg(args, unsigned int); + result += AppendUnsigned(&buff, buff_end, uval, 10, 0); + break; + case 'x': xval = have_l ? va_arg(args, uintptr_t) + : va_arg(args, unsigned int); + result += AppendUnsigned(&buff, buff_end, xval, 16, 0); + break; + case 'p': RAW_CHECK_MSG(!have_l, kPrintfFormatsHelp); + result += AppendPointer(&buff, buff_end, + va_arg(args, uintptr_t)); + break; + case 's': RAW_CHECK_MSG(!have_l, kPrintfFormatsHelp); + result += AppendString(&buff, buff_end, va_arg(args, char*)); + break; + default: RAW_CHECK_MSG(false, kPrintfFormatsHelp); + } + } else { + result += AppendChar(&buff, buff_end, *cur); + } + } + RAW_CHECK(buff <= buff_end); + AppendChar(&buff, buff_end + 1, '\0'); + return result; +} + +void Printf(const char *format, ...) { + const int kLen = 1024 * 4; + char buffer[kLen]; + va_list args; + va_start(args, format); + int needed_length = VSNPrintf(buffer, kLen, format, args); + va_end(args); + RAW_CHECK_MSG(needed_length < kLen, "Buffer in Printf is too short!\n"); + RawWrite(buffer); +} + +// Writes at most "length" symbols to "buffer" (including trailing '\0'). +// Returns the number of symbols that should have been written to buffer +// (not including trailing '\0'). Thus, the string is truncated +// iff return value is not less than "length". +int SNPrintf(char *buffer, size_t length, const char *format, ...) { + va_list args; + va_start(args, format); + int needed_length = VSNPrintf(buffer, length, format, args); + va_end(args); + return needed_length; +} + +// Like Printf, but prints the current PID before the output string. +void Report(const char *format, ...) { + const int kLen = 1024 * 4; + char buffer[kLen]; + int needed_length = SNPrintf(buffer, kLen, "==%d== ", getpid()); + RAW_CHECK_MSG(needed_length < kLen, "Buffer in Report is too short!\n"); + va_list args; + va_start(args, format); + needed_length += VSNPrintf(buffer + needed_length, kLen - needed_length, + format, args); + va_end(args); + RAW_CHECK_MSG(needed_length < kLen, "Buffer in Report is too short!\n"); + RawWrite(buffer); +} + +} // namespace __asan diff --git a/lib/asan/asan_rtl.cc b/lib/asan/asan_rtl.cc new file mode 100644 index 000000000000..c876f6d97f73 --- /dev/null +++ b/lib/asan/asan_rtl.cc @@ -0,0 +1,810 @@ +//===-- asan_rtl.cc ---------------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Main file of the ASan run-time library. +//===----------------------------------------------------------------------===// +#include "asan_allocator.h" +#include "asan_interceptors.h" +#include "asan_interface.h" +#include "asan_internal.h" +#include "asan_lock.h" +#include "asan_mac.h" +#include "asan_mapping.h" +#include "asan_stack.h" +#include "asan_stats.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" + +#include <new> +#include <dlfcn.h> +#include <execinfo.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#ifndef ANDROID +#include <sys/ucontext.h> +#endif +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +// must not include <setjmp.h> on Linux + +namespace __asan { + +// -------------------------- Flags ------------------------- {{{1 +static const size_t kMallocContextSize = 30; +static int FLAG_atexit; +bool FLAG_fast_unwind = true; + +size_t FLAG_redzone; // power of two, >= 32 +size_t FLAG_quarantine_size; +int FLAG_demangle; +bool FLAG_symbolize; +int FLAG_v; +int FLAG_debug; +bool FLAG_poison_shadow; +int FLAG_report_globals; +size_t FLAG_malloc_context_size = kMallocContextSize; +uintptr_t FLAG_large_malloc; +bool FLAG_lazy_shadow; +bool FLAG_handle_segv; +bool FLAG_handle_sigill; +bool FLAG_replace_str; +bool FLAG_replace_intrin; +bool FLAG_replace_cfallocator; // Used on Mac only. +size_t FLAG_max_malloc_fill_size = 0; +bool FLAG_use_fake_stack; +int FLAG_exitcode = EXIT_FAILURE; +bool FLAG_allow_user_poisoning; + +// -------------------------- Globals --------------------- {{{1 +int asan_inited; +bool asan_init_is_running; + +// -------------------------- Interceptors ---------------- {{{1 +typedef int (*sigaction_f)(int signum, const struct sigaction *act, + struct sigaction *oldact); +typedef sig_t (*signal_f)(int signum, sig_t handler); +typedef void (*longjmp_f)(void *env, int val); +typedef longjmp_f _longjmp_f; +typedef longjmp_f siglongjmp_f; +typedef void (*__cxa_throw_f)(void *, void *, void *); +typedef int (*pthread_create_f)(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg); +#ifdef __APPLE__ +dispatch_async_f_f real_dispatch_async_f; +dispatch_sync_f_f real_dispatch_sync_f; +dispatch_after_f_f real_dispatch_after_f; +dispatch_barrier_async_f_f real_dispatch_barrier_async_f; +dispatch_group_async_f_f real_dispatch_group_async_f; +pthread_workqueue_additem_np_f real_pthread_workqueue_additem_np; +#endif + +sigaction_f real_sigaction; +signal_f real_signal; +longjmp_f real_longjmp; +_longjmp_f real__longjmp; +siglongjmp_f real_siglongjmp; +__cxa_throw_f real___cxa_throw; +pthread_create_f real_pthread_create; + +// -------------------------- Misc ---------------- {{{1 +void ShowStatsAndAbort() { + __asan_print_accumulated_stats(); + ASAN_DIE; +} + +static void PrintBytes(const char *before, uintptr_t *a) { + uint8_t *bytes = (uint8_t*)a; + size_t byte_num = (__WORDSIZE) / 8; + Printf("%s%p:", before, (uintptr_t)a); + for (size_t i = 0; i < byte_num; i++) { + Printf(" %lx%lx", bytes[i] >> 4, bytes[i] & 15); + } + Printf("\n"); +} + +// Opens the file 'file_name" and reads up to 'max_len' bytes. +// The resulting buffer is mmaped and stored in '*buff'. +// Returns the number of read bytes or -1 if file can not be opened. +static ssize_t ReadFileToBuffer(const char *file_name, char **buff, + size_t max_len) { + const size_t kMinFileLen = kPageSize; + ssize_t read_len = -1; + *buff = 0; + size_t maped_size = 0; + // The files we usually open are not seekable, so try different buffer sizes. + for (size_t size = kMinFileLen; size <= max_len; size *= 2) { + int fd = AsanOpenReadonly(file_name); + if (fd < 0) return -1; + AsanUnmapOrDie(*buff, maped_size); + maped_size = size; + *buff = (char*)AsanMmapSomewhereOrDie(size, __FUNCTION__); + read_len = AsanRead(fd, *buff, size); + AsanClose(fd); + if (read_len < size) // We've read the whole file. + break; + } + return read_len; +} + +// Like getenv, but reads env directly from /proc and does not use libc. +// This function should be called first inside __asan_init. +static const char* GetEnvFromProcSelfEnviron(const char* name) { + static char *environ; + static ssize_t len; + static bool inited; + if (!inited) { + inited = true; + len = ReadFileToBuffer("/proc/self/environ", &environ, 1 << 20); + } + if (!environ || len <= 0) return NULL; + size_t namelen = internal_strlen(name); + const char *p = environ; + while (*p != '\0') { // will happen at the \0\0 that terminates the buffer + // proc file has the format NAME=value\0NAME=value\0NAME=value\0... + const char* endp = + (char*)internal_memchr(p, '\0', len - (p - environ)); + if (endp == NULL) // this entry isn't NUL terminated + return NULL; + else if (!internal_memcmp(p, name, namelen) && p[namelen] == '=') // Match. + return p + namelen + 1; // point after = + p = endp + 1; + } + return NULL; // Not found. +} + +// ---------------------- Thread ------------------------- {{{1 +static void *asan_thread_start(void *arg) { + AsanThread *t= (AsanThread*)arg; + asanThreadRegistry().SetCurrent(t); + return t->ThreadStart(); +} + +// ---------------------- mmap -------------------- {{{1 +void OutOfMemoryMessageAndDie(const char *mem_type, size_t size) { + Report("ERROR: AddressSanitizer failed to allocate " + "0x%lx (%ld) bytes of %s\n", + size, size, mem_type); + PRINT_CURRENT_STACK(); + ShowStatsAndAbort(); +} + +// Reserve memory range [beg, end]. +static void ReserveShadowMemoryRange(uintptr_t beg, uintptr_t end) { + CHECK((beg % kPageSize) == 0); + CHECK(((end + 1) % kPageSize) == 0); + size_t size = end - beg + 1; + void *res = AsanMmapFixedNoReserve(beg, size); + CHECK(res == (void*)beg && "ReserveShadowMemoryRange failed"); +} + +// ---------------------- LowLevelAllocator ------------- {{{1 +void *LowLevelAllocator::Allocate(size_t size) { + CHECK((size & (size - 1)) == 0 && "size must be a power of two"); + if (allocated_end_ - allocated_current_ < size) { + size_t size_to_allocate = Max(size, kPageSize); + allocated_current_ = + (char*)AsanMmapSomewhereOrDie(size_to_allocate, __FUNCTION__); + allocated_end_ = allocated_current_ + size_to_allocate; + PoisonShadow((uintptr_t)allocated_current_, size_to_allocate, + kAsanInternalHeapMagic); + } + CHECK(allocated_end_ - allocated_current_ >= size); + void *res = allocated_current_; + allocated_current_ += size; + return res; +} + +// ---------------------- DescribeAddress -------------------- {{{1 +static bool DescribeStackAddress(uintptr_t addr, uintptr_t access_size) { + AsanThread *t = asanThreadRegistry().FindThreadByStackAddress(addr); + if (!t) return false; + const intptr_t kBufSize = 4095; + char buf[kBufSize]; + uintptr_t offset = 0; + const char *frame_descr = t->GetFrameNameByAddr(addr, &offset); + // This string is created by the compiler and has the following form: + // "FunctioName n alloc_1 alloc_2 ... alloc_n" + // where alloc_i looks like "offset size len ObjectName ". + CHECK(frame_descr); + // Report the function name and the offset. + const char *name_end = real_strchr(frame_descr, ' '); + CHECK(name_end); + buf[0] = 0; + strncat(buf, frame_descr, + Min(kBufSize, static_cast<intptr_t>(name_end - frame_descr))); + Printf("Address %p is located at offset %ld " + "in frame <%s> of T%d's stack:\n", + addr, offset, buf, t->tid()); + // Report the number of stack objects. + char *p; + size_t n_objects = strtol(name_end, &p, 10); + CHECK(n_objects > 0); + Printf(" This frame has %ld object(s):\n", n_objects); + // Report all objects in this frame. + for (size_t i = 0; i < n_objects; i++) { + size_t beg, size; + intptr_t len; + beg = strtol(p, &p, 10); + size = strtol(p, &p, 10); + len = strtol(p, &p, 10); + if (beg <= 0 || size <= 0 || len < 0 || *p != ' ') { + Printf("AddressSanitizer can't parse the stack frame descriptor: |%s|\n", + frame_descr); + break; + } + p++; + buf[0] = 0; + strncat(buf, p, Min(kBufSize, len)); + p += len; + Printf(" [%ld, %ld) '%s'\n", beg, beg + size, buf); + } + Printf("HINT: this may be a false positive if your program uses " + "some custom stack unwind mechanism\n" + " (longjmp and C++ exceptions *are* supported)\n"); + t->summary()->Announce(); + return true; +} + +__attribute__((noinline)) +static void DescribeAddress(uintptr_t addr, uintptr_t access_size) { + // Check if this is a global. + if (DescribeAddrIfGlobal(addr)) + return; + + if (DescribeStackAddress(addr, access_size)) + return; + + // finally, check if this is a heap. + DescribeHeapAddress(addr, access_size); +} + +// -------------------------- Run-time entry ------------------- {{{1 +void GetPcSpBpAx(void *context, + uintptr_t *pc, uintptr_t *sp, uintptr_t *bp, uintptr_t *ax) { +#ifndef ANDROID + ucontext_t *ucontext = (ucontext_t*)context; +#endif +#ifdef __APPLE__ +# if __WORDSIZE == 64 + *pc = ucontext->uc_mcontext->__ss.__rip; + *bp = ucontext->uc_mcontext->__ss.__rbp; + *sp = ucontext->uc_mcontext->__ss.__rsp; + *ax = ucontext->uc_mcontext->__ss.__rax; +# else + *pc = ucontext->uc_mcontext->__ss.__eip; + *bp = ucontext->uc_mcontext->__ss.__ebp; + *sp = ucontext->uc_mcontext->__ss.__esp; + *ax = ucontext->uc_mcontext->__ss.__eax; +# endif // __WORDSIZE +#else // assume linux +# if defined (ANDROID) + *pc = *sp = *bp = *ax = 0; +# elif defined(__arm__) + *pc = ucontext->uc_mcontext.arm_pc; + *bp = ucontext->uc_mcontext.arm_fp; + *sp = ucontext->uc_mcontext.arm_sp; + *ax = ucontext->uc_mcontext.arm_r0; +# elif __WORDSIZE == 64 + *pc = ucontext->uc_mcontext.gregs[REG_RIP]; + *bp = ucontext->uc_mcontext.gregs[REG_RBP]; + *sp = ucontext->uc_mcontext.gregs[REG_RSP]; + *ax = ucontext->uc_mcontext.gregs[REG_RAX]; +# else + *pc = ucontext->uc_mcontext.gregs[REG_EIP]; + *bp = ucontext->uc_mcontext.gregs[REG_EBP]; + *sp = ucontext->uc_mcontext.gregs[REG_ESP]; + *ax = ucontext->uc_mcontext.gregs[REG_EAX]; +# endif // __WORDSIZE +#endif +} + +static void ASAN_OnSIGSEGV(int, siginfo_t *siginfo, void *context) { + uintptr_t addr = (uintptr_t)siginfo->si_addr; + if (AddrIsInShadow(addr) && FLAG_lazy_shadow) { + // We traped on access to a shadow address. Just map a large chunk around + // this address. + const uintptr_t chunk_size = kPageSize << 10; // 4M + uintptr_t chunk = addr & ~(chunk_size - 1); + AsanMmapFixedReserve(chunk, chunk_size); + return; + } + // Write the first message using the bullet-proof write. + if (13 != AsanWrite(2, "ASAN:SIGSEGV\n", 13)) ASAN_DIE; + uintptr_t pc, sp, bp, ax; + GetPcSpBpAx(context, &pc, &sp, &bp, &ax); + Report("ERROR: AddressSanitizer crashed on unknown address %p" + " (pc %p sp %p bp %p ax %p T%d)\n", + addr, pc, sp, bp, ax, + asanThreadRegistry().GetCurrentTidOrMinusOne()); + Printf("AddressSanitizer can not provide additional info. ABORTING\n"); + GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, false, pc, bp); + stack.PrintStack(); + ShowStatsAndAbort(); +} + +static void ASAN_OnSIGILL(int, siginfo_t *siginfo, void *context) { + // Write the first message using the bullet-proof write. + if (12 != AsanWrite(2, "ASAN:SIGILL\n", 12)) ASAN_DIE; + uintptr_t pc, sp, bp, ax; + GetPcSpBpAx(context, &pc, &sp, &bp, &ax); + + uintptr_t addr = ax; + + uint8_t *insn = (uint8_t*)pc; + CHECK(insn[0] == 0x0f && insn[1] == 0x0b); // ud2 + unsigned access_size_and_type = insn[2] - 0x50; + CHECK(access_size_and_type < 16); + bool is_write = access_size_and_type & 8; + int access_size = 1 << (access_size_and_type & 7); + __asan_report_error(pc, bp, sp, addr, is_write, access_size); +} + +// exported functions +#define ASAN_REPORT_ERROR(type, is_write, size) \ +extern "C" void __asan_report_ ## type ## size(uintptr_t addr) \ + __attribute__((visibility("default"))) __attribute__((noinline)); \ +extern "C" void __asan_report_ ## type ## size(uintptr_t addr) { \ + GET_BP_PC_SP; \ + __asan_report_error(pc, bp, sp, addr, is_write, size); \ +} + +ASAN_REPORT_ERROR(load, false, 1) +ASAN_REPORT_ERROR(load, false, 2) +ASAN_REPORT_ERROR(load, false, 4) +ASAN_REPORT_ERROR(load, false, 8) +ASAN_REPORT_ERROR(load, false, 16) +ASAN_REPORT_ERROR(store, true, 1) +ASAN_REPORT_ERROR(store, true, 2) +ASAN_REPORT_ERROR(store, true, 4) +ASAN_REPORT_ERROR(store, true, 8) +ASAN_REPORT_ERROR(store, true, 16) + +// Force the linker to keep the symbols for various ASan interface functions. +// We want to keep those in the executable in order to let the instrumented +// dynamic libraries access the symbol even if it is not used by the executable +// itself. This should help if the build system is removing dead code at link +// time. +static void force_interface_symbols() { + volatile int fake_condition = 0; // prevent dead condition elimination. + if (fake_condition) { + __asan_report_load1(NULL); + __asan_report_load2(NULL); + __asan_report_load4(NULL); + __asan_report_load8(NULL); + __asan_report_load16(NULL); + __asan_report_store1(NULL); + __asan_report_store2(NULL); + __asan_report_store4(NULL); + __asan_report_store8(NULL); + __asan_report_store16(NULL); + __asan_register_global(0, 0, NULL); + __asan_register_globals(NULL, 0); + __asan_unregister_globals(NULL, 0); + } +} + +// -------------------------- Init ------------------- {{{1 +static int64_t IntFlagValue(const char *flags, const char *flag, + int64_t default_val) { + if (!flags) return default_val; + const char *str = strstr(flags, flag); + if (!str) return default_val; + return atoll(str + internal_strlen(flag)); +} + +static void asan_atexit() { + Printf("AddressSanitizer exit stats:\n"); + __asan_print_accumulated_stats(); +} + +void CheckFailed(const char *cond, const char *file, int line) { + Report("CHECK failed: %s at %s:%d, pthread_self=%p\n", + cond, file, line, pthread_self()); + PRINT_CURRENT_STACK(); + ShowStatsAndAbort(); +} + +} // namespace __asan + +// -------------------------- Interceptors ------------------- {{{1 +using namespace __asan; // NOLINT + +#define OPERATOR_NEW_BODY \ + GET_STACK_TRACE_HERE_FOR_MALLOC;\ + return asan_memalign(0, size, &stack); + +#ifdef ANDROID +void *operator new(size_t size) { OPERATOR_NEW_BODY; } +void *operator new[](size_t size) { OPERATOR_NEW_BODY; } +#else +void *operator new(size_t size) throw(std::bad_alloc) { OPERATOR_NEW_BODY; } +void *operator new[](size_t size) throw(std::bad_alloc) { OPERATOR_NEW_BODY; } +void *operator new(size_t size, std::nothrow_t const&) throw() +{ OPERATOR_NEW_BODY; } +void *operator new[](size_t size, std::nothrow_t const&) throw() +{ OPERATOR_NEW_BODY; } +#endif + +#define OPERATOR_DELETE_BODY \ + GET_STACK_TRACE_HERE_FOR_FREE(ptr);\ + asan_free(ptr, &stack); + +void operator delete(void *ptr) throw() { OPERATOR_DELETE_BODY; } +void operator delete[](void *ptr) throw() { OPERATOR_DELETE_BODY; } +void operator delete(void *ptr, std::nothrow_t const&) throw() +{ OPERATOR_DELETE_BODY; } +void operator delete[](void *ptr, std::nothrow_t const&) throw() +{ OPERATOR_DELETE_BODY;} + +extern "C" +#ifndef __APPLE__ +__attribute__((visibility("default"))) +#endif +int WRAP(pthread_create)(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg) { + GET_STACK_TRACE_HERE(kStackTraceMax, /*fast_unwind*/false); + AsanThread *t = (AsanThread*)asan_malloc(sizeof(AsanThread), &stack); + AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); + CHECK(curr_thread || asanThreadRegistry().IsCurrentThreadDying()); + new(t) AsanThread(asanThreadRegistry().GetCurrentTidOrMinusOne(), + start_routine, arg, &stack); + return real_pthread_create(thread, attr, asan_thread_start, t); +} + +static bool MySignal(int signum) { + if (FLAG_handle_sigill && signum == SIGILL) return true; + if (FLAG_handle_segv && signum == SIGSEGV) return true; +#ifdef __APPLE__ + if (FLAG_handle_segv && signum == SIGBUS) return true; +#endif + return false; +} + +static void MaybeInstallSigaction(int signum, + void (*handler)(int, siginfo_t *, void *)) { + if (!MySignal(signum)) + return; + struct sigaction sigact; + real_memset(&sigact, 0, sizeof(sigact)); + sigact.sa_sigaction = handler; + sigact.sa_flags = SA_SIGINFO; + CHECK(0 == real_sigaction(signum, &sigact, 0)); +} + +extern "C" +sig_t WRAP(signal)(int signum, sig_t handler) { + if (!MySignal(signum)) { + return real_signal(signum, handler); + } + return NULL; +} + +extern "C" +int WRAP(sigaction)(int signum, const struct sigaction *act, + struct sigaction *oldact) { + if (!MySignal(signum)) { + return real_sigaction(signum, act, oldact); + } + return 0; +} + + +static void UnpoisonStackFromHereToTop() { + int local_stack; + AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); + CHECK(curr_thread); + uintptr_t top = curr_thread->stack_top(); + uintptr_t bottom = ((uintptr_t)&local_stack - kPageSize) & ~(kPageSize-1); + PoisonShadow(bottom, top - bottom, 0); +} + +extern "C" void WRAP(longjmp)(void *env, int val) { + UnpoisonStackFromHereToTop(); + real_longjmp(env, val); +} + +extern "C" void WRAP(_longjmp)(void *env, int val) { + UnpoisonStackFromHereToTop(); + real__longjmp(env, val); +} + +extern "C" void WRAP(siglongjmp)(void *env, int val) { + UnpoisonStackFromHereToTop(); + real_siglongjmp(env, val); +} + +extern "C" void __cxa_throw(void *a, void *b, void *c); + +#if ASAN_HAS_EXCEPTIONS == 1 +extern "C" void WRAP(__cxa_throw)(void *a, void *b, void *c) { + CHECK(&real___cxa_throw); + UnpoisonStackFromHereToTop(); + real___cxa_throw(a, b, c); +} +#endif + +extern "C" { +// intercept mlock and friends. +// Since asan maps 16T of RAM, mlock is completely unfriendly to asan. +// All functions return 0 (success). +static void MlockIsUnsupported() { + static bool printed = 0; + if (printed) return; + printed = true; + Printf("INFO: AddressSanitizer ignores mlock/mlockall/munlock/munlockall\n"); +} +int mlock(const void *addr, size_t len) { + MlockIsUnsupported(); + return 0; +} +int munlock(const void *addr, size_t len) { + MlockIsUnsupported(); + return 0; +} +int mlockall(int flags) { + MlockIsUnsupported(); + return 0; +} +int munlockall(void) { + MlockIsUnsupported(); + return 0; +} +} // extern "C" + +// ---------------------- Interface ---------------- {{{1 +int __asan_set_error_exit_code(int exit_code) { + int old = FLAG_exitcode; + FLAG_exitcode = exit_code; + return old; +} + +void __asan_report_error(uintptr_t pc, uintptr_t bp, uintptr_t sp, + uintptr_t addr, bool is_write, size_t access_size) { + // Do not print more than one report, otherwise they will mix up. + static int num_calls = 0; + if (AtomicInc(&num_calls) > 1) return; + + Printf("=================================================================\n"); + const char *bug_descr = "unknown-crash"; + if (AddrIsInMem(addr)) { + uint8_t *shadow_addr = (uint8_t*)MemToShadow(addr); + // If we are accessing 16 bytes, look at the second shadow byte. + if (*shadow_addr == 0 && access_size > SHADOW_GRANULARITY) + shadow_addr++; + // If we are in the partial right redzone, look at the next shadow byte. + if (*shadow_addr > 0 && *shadow_addr < 128) + shadow_addr++; + switch (*shadow_addr) { + case kAsanHeapLeftRedzoneMagic: + case kAsanHeapRightRedzoneMagic: + bug_descr = "heap-buffer-overflow"; + break; + case kAsanHeapFreeMagic: + bug_descr = "heap-use-after-free"; + break; + case kAsanStackLeftRedzoneMagic: + bug_descr = "stack-buffer-underflow"; + break; + case kAsanStackMidRedzoneMagic: + case kAsanStackRightRedzoneMagic: + case kAsanStackPartialRedzoneMagic: + bug_descr = "stack-buffer-overflow"; + break; + case kAsanStackAfterReturnMagic: + bug_descr = "stack-use-after-return"; + break; + case kAsanUserPoisonedMemoryMagic: + bug_descr = "use-after-poison"; + break; + case kAsanGlobalRedzoneMagic: + bug_descr = "global-buffer-overflow"; + break; + } + } + + AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); + int curr_tid = asanThreadRegistry().GetCurrentTidOrMinusOne(); + + if (curr_thread) { + // We started reporting an error message. Stop using the fake stack + // in case we will call an instrumented function from a symbolizer. + curr_thread->fake_stack().StopUsingFakeStack(); + } + + Report("ERROR: AddressSanitizer %s on address " + "%p at pc 0x%lx bp 0x%lx sp 0x%lx\n", + bug_descr, addr, pc, bp, sp); + + Printf("%s of size %d at %p thread T%d\n", + access_size ? (is_write ? "WRITE" : "READ") : "ACCESS", + access_size, addr, curr_tid); + + if (FLAG_debug) { + PrintBytes("PC: ", (uintptr_t*)pc); + } + + GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, + false, // FLAG_fast_unwind, + pc, bp); + stack.PrintStack(); + + CHECK(AddrIsInMem(addr)); + + DescribeAddress(addr, access_size); + + uintptr_t shadow_addr = MemToShadow(addr); + Report("ABORTING\n"); + __asan_print_accumulated_stats(); + Printf("Shadow byte and word:\n"); + Printf(" %p: %x\n", shadow_addr, *(unsigned char*)shadow_addr); + uintptr_t aligned_shadow = shadow_addr & ~(kWordSize - 1); + PrintBytes(" ", (uintptr_t*)(aligned_shadow)); + Printf("More shadow bytes:\n"); + PrintBytes(" ", (uintptr_t*)(aligned_shadow-4*kWordSize)); + PrintBytes(" ", (uintptr_t*)(aligned_shadow-3*kWordSize)); + PrintBytes(" ", (uintptr_t*)(aligned_shadow-2*kWordSize)); + PrintBytes(" ", (uintptr_t*)(aligned_shadow-1*kWordSize)); + PrintBytes("=>", (uintptr_t*)(aligned_shadow+0*kWordSize)); + PrintBytes(" ", (uintptr_t*)(aligned_shadow+1*kWordSize)); + PrintBytes(" ", (uintptr_t*)(aligned_shadow+2*kWordSize)); + PrintBytes(" ", (uintptr_t*)(aligned_shadow+3*kWordSize)); + PrintBytes(" ", (uintptr_t*)(aligned_shadow+4*kWordSize)); + ASAN_DIE; +} + +void __asan_init() { + if (asan_inited) return; + asan_init_is_running = true; + + // Make sure we are not statically linked. + AsanDoesNotSupportStaticLinkage(); + + // flags + const char *options = GetEnvFromProcSelfEnviron("ASAN_OPTIONS"); + FLAG_malloc_context_size = + IntFlagValue(options, "malloc_context_size=", kMallocContextSize); + CHECK(FLAG_malloc_context_size <= kMallocContextSize); + + FLAG_max_malloc_fill_size = + IntFlagValue(options, "max_malloc_fill_size=", 0); + + FLAG_v = IntFlagValue(options, "verbosity=", 0); + + FLAG_redzone = IntFlagValue(options, "redzone=", 128); + CHECK(FLAG_redzone >= 32); + CHECK((FLAG_redzone & (FLAG_redzone - 1)) == 0); + + FLAG_atexit = IntFlagValue(options, "atexit=", 0); + FLAG_poison_shadow = IntFlagValue(options, "poison_shadow=", 1); + FLAG_report_globals = IntFlagValue(options, "report_globals=", 1); + FLAG_lazy_shadow = IntFlagValue(options, "lazy_shadow=", 0); + FLAG_handle_segv = IntFlagValue(options, "handle_segv=", ASAN_NEEDS_SEGV); + FLAG_handle_sigill = IntFlagValue(options, "handle_sigill=", 0); + FLAG_symbolize = IntFlagValue(options, "symbolize=", 1); + FLAG_demangle = IntFlagValue(options, "demangle=", 1); + FLAG_debug = IntFlagValue(options, "debug=", 0); + FLAG_replace_cfallocator = IntFlagValue(options, "replace_cfallocator=", 1); + FLAG_fast_unwind = IntFlagValue(options, "fast_unwind=", 1); + FLAG_replace_str = IntFlagValue(options, "replace_str=", 1); + FLAG_replace_intrin = IntFlagValue(options, "replace_intrin=", 1); + FLAG_use_fake_stack = IntFlagValue(options, "use_fake_stack=", 1); + FLAG_exitcode = IntFlagValue(options, "exitcode=", EXIT_FAILURE); + FLAG_allow_user_poisoning = IntFlagValue(options, + "allow_user_poisoning=", 1); + + if (FLAG_atexit) { + atexit(asan_atexit); + } + + FLAG_quarantine_size = + IntFlagValue(options, "quarantine_size=", 1UL << 28); + + // interceptors + InitializeAsanInterceptors(); + + ReplaceSystemMalloc(); + + INTERCEPT_FUNCTION(sigaction); + INTERCEPT_FUNCTION(signal); + INTERCEPT_FUNCTION(longjmp); + INTERCEPT_FUNCTION(_longjmp); + INTERCEPT_FUNCTION_IF_EXISTS(__cxa_throw); + INTERCEPT_FUNCTION(pthread_create); +#ifdef __APPLE__ + INTERCEPT_FUNCTION(dispatch_async_f); + INTERCEPT_FUNCTION(dispatch_sync_f); + INTERCEPT_FUNCTION(dispatch_after_f); + INTERCEPT_FUNCTION(dispatch_barrier_async_f); + INTERCEPT_FUNCTION(dispatch_group_async_f); + // We don't need to intercept pthread_workqueue_additem_np() to support the + // libdispatch API, but it helps us to debug the unsupported functions. Let's + // intercept it only during verbose runs. + if (FLAG_v >= 2) { + INTERCEPT_FUNCTION(pthread_workqueue_additem_np); + } +#else + // On Darwin siglongjmp tailcalls longjmp, so we don't want to intercept it + // there. + INTERCEPT_FUNCTION(siglongjmp); +#endif + + MaybeInstallSigaction(SIGSEGV, ASAN_OnSIGSEGV); + MaybeInstallSigaction(SIGBUS, ASAN_OnSIGSEGV); + MaybeInstallSigaction(SIGILL, ASAN_OnSIGILL); + + if (FLAG_v) { + Printf("|| `[%p, %p]` || HighMem ||\n", kHighMemBeg, kHighMemEnd); + Printf("|| `[%p, %p]` || HighShadow ||\n", + kHighShadowBeg, kHighShadowEnd); + Printf("|| `[%p, %p]` || ShadowGap ||\n", + kShadowGapBeg, kShadowGapEnd); + Printf("|| `[%p, %p]` || LowShadow ||\n", + kLowShadowBeg, kLowShadowEnd); + Printf("|| `[%p, %p]` || LowMem ||\n", kLowMemBeg, kLowMemEnd); + Printf("MemToShadow(shadow): %p %p %p %p\n", + MEM_TO_SHADOW(kLowShadowBeg), + MEM_TO_SHADOW(kLowShadowEnd), + MEM_TO_SHADOW(kHighShadowBeg), + MEM_TO_SHADOW(kHighShadowEnd)); + Printf("red_zone=%ld\n", FLAG_redzone); + Printf("malloc_context_size=%ld\n", (int)FLAG_malloc_context_size); + Printf("fast_unwind=%d\n", (int)FLAG_fast_unwind); + + Printf("SHADOW_SCALE: %lx\n", SHADOW_SCALE); + Printf("SHADOW_GRANULARITY: %lx\n", SHADOW_GRANULARITY); + Printf("SHADOW_OFFSET: %lx\n", SHADOW_OFFSET); + CHECK(SHADOW_SCALE >= 3 && SHADOW_SCALE <= 7); + } + + if (__WORDSIZE == 64) { + // Disable core dumper -- it makes little sense to dump 16T+ core. + struct rlimit nocore; + nocore.rlim_cur = 0; + nocore.rlim_max = 0; + setrlimit(RLIMIT_CORE, &nocore); + } + + { + if (!FLAG_lazy_shadow) { + if (kLowShadowBeg != kLowShadowEnd) { + // mmap the low shadow plus one page. + ReserveShadowMemoryRange(kLowShadowBeg - kPageSize, kLowShadowEnd); + } + // mmap the high shadow. + ReserveShadowMemoryRange(kHighShadowBeg, kHighShadowEnd); + } + // protect the gap + void *prot = AsanMprotect(kShadowGapBeg, kShadowGapEnd - kShadowGapBeg + 1); + CHECK(prot == (void*)kShadowGapBeg); + } + + // On Linux AsanThread::ThreadStart() calls malloc() that's why asan_inited + // should be set to 1 prior to initializing the threads. + asan_inited = 1; + asan_init_is_running = false; + + asanThreadRegistry().Init(); + asanThreadRegistry().GetMain()->ThreadStart(); + force_interface_symbols(); // no-op. + + if (FLAG_v) { + Report("AddressSanitizer Init done\n"); + } +} diff --git a/lib/asan/asan_stack.cc b/lib/asan/asan_stack.cc new file mode 100644 index 000000000000..8163983bce7d --- /dev/null +++ b/lib/asan/asan_stack.cc @@ -0,0 +1,280 @@ +//===-- asan_stack.cc -------------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Code for ASan stack trace. +//===----------------------------------------------------------------------===// +#include "asan_interceptors.h" +#include "asan_lock.h" +#include "asan_stack.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" + +#include <string.h> + +#if ASAN_USE_SYSINFO == 1 +#include "sysinfo/sysinfo.h" +#endif + +#ifdef ASAN_USE_EXTERNAL_SYMBOLIZER +extern bool +ASAN_USE_EXTERNAL_SYMBOLIZER(const void *pc, char *out, int out_size); +#endif + +namespace __asan { + +// ----------------------- ProcSelfMaps ----------------------------- {{{1 +#if ASAN_USE_SYSINFO == 1 +class ProcSelfMaps { + public: + void Init() { + ScopedLock lock(&mu_); + if (map_size_ != 0) return; // already inited + if (FLAG_v >= 2) { + Printf("ProcSelfMaps::Init()\n"); + } + ProcMapsIterator it(0, &proc_self_maps_); // 0 means "current pid" + + uint64 start, end, offset; + int64 inode; + char *flags, *filename; + CHECK(map_size_ == 0); + while (it.Next(&start, &end, &flags, &offset, &inode, &filename)) { + CHECK(map_size_ < kMaxProcSelfMapsSize); + Mapping &mapping = memory_map[map_size_]; + mapping.beg = start; + mapping.end = end; + mapping.offset = offset; + real_strncpy(mapping.name, + filename, ASAN_ARRAY_SIZE(mapping.name)); + mapping.name[ASAN_ARRAY_SIZE(mapping.name) - 1] = 0; + if (FLAG_v >= 2) { + Printf("[%ld] [%p,%p] off %p %s\n", map_size_, + mapping.beg, mapping.end, mapping.offset, mapping.name); + } + map_size_++; + } + } + + void Print() { + Printf("%s\n", proc_self_maps_); + } + + void PrintPc(uintptr_t pc, int idx) { + for (size_t i = 0; i < map_size_; i++) { + Mapping &m = memory_map[i]; + if (pc >= m.beg && pc < m.end) { + uintptr_t offset = pc - m.beg; + if (i == 0) offset = pc; + Printf(" #%d 0x%lx (%s+0x%lx)\n", idx, pc, m.name, offset); + return; + } + } + Printf(" #%d 0x%lx\n", idx, pc); + } + + private: + void copy_until_new_line(const char *str, char *dest, size_t max_size) { + size_t i = 0; + for (; str[i] && str[i] != '\n' && i < max_size - 1; i++) { + dest[i] = str[i]; + } + dest[i] = 0; + } + + + struct Mapping { + uintptr_t beg, end, offset; + char name[1000]; + }; + static const size_t kMaxNumMapEntries = 4096; + static const size_t kMaxProcSelfMapsSize = 1 << 20; + ProcMapsIterator::Buffer proc_self_maps_; + size_t map_size_; + Mapping memory_map[kMaxNumMapEntries]; + + static AsanLock mu_; +}; + +static ProcSelfMaps proc_self_maps; +AsanLock ProcSelfMaps::mu_(LINKER_INITIALIZED); + + +void AsanStackTrace::PrintStack(uintptr_t *addr, size_t size) { + proc_self_maps.Init(); + for (size_t i = 0; i < size && addr[i]; i++) { + uintptr_t pc = addr[i]; + // int line; + proc_self_maps.PrintPc(pc, i); + // Printf(" #%ld 0x%lx %s\n", i, pc, rtn.c_str()); + } +} +#elif defined(ASAN_USE_EXTERNAL_SYMBOLIZER) +void AsanStackTrace::PrintStack(uintptr_t *addr, size_t size) { + for (size_t i = 0; i < size && addr[i]; i++) { + uintptr_t pc = addr[i]; + char buff[4096]; + ASAN_USE_EXTERNAL_SYMBOLIZER((void*)pc, buff, sizeof(buff)); + Printf(" #%ld 0x%lx %s\n", i, pc, buff); + } +} + +#else // ASAN_USE_SYSINFO +void AsanStackTrace::PrintStack(uintptr_t *addr, size_t size) { + for (size_t i = 0; i < size && addr[i]; i++) { + uintptr_t pc = addr[i]; + Printf(" #%ld 0x%lx\n", i, pc); + } +} +#endif // ASAN_USE_SYSINFO + +#ifdef __arm__ +#define UNWIND_STOP _URC_END_OF_STACK +#define UNWIND_CONTINUE _URC_OK +#else +#define UNWIND_STOP _URC_NORMAL_STOP +#define UNWIND_CONTINUE _URC_NO_REASON +#endif + +// ----------------------- AsanStackTrace ----------------------------- {{{1 +uintptr_t AsanStackTrace::GetCurrentPc() { + return GET_CALLER_PC(); +} + +void AsanStackTrace::FastUnwindStack(uintptr_t pc, uintptr_t bp) { + CHECK(size == 0 && trace[0] == pc); + size = 1; + if (!asan_inited) return; + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (!t) return; + uintptr_t *frame = (uintptr_t*)bp; + uintptr_t *prev_frame = frame; + uintptr_t *top = (uintptr_t*)t->stack_top(); + uintptr_t *bottom = (uintptr_t*)t->stack_bottom(); + while (frame >= prev_frame && + frame < top && + frame > bottom && + size < max_size) { + uintptr_t pc1 = frame[1]; + if (pc1 != pc) { + trace[size++] = pc1; + } + prev_frame = frame; + frame = (uintptr_t*)frame[0]; + } +} + +// On 32-bits we don't compress stack traces. +// On 64-bits we compress stack traces: if a given pc differes slightly from +// the previous one, we record a 31-bit offset instead of the full pc. +size_t AsanStackTrace::CompressStack(AsanStackTrace *stack, + uint32_t *compressed, size_t size) { +#if __WORDSIZE == 32 + // Don't compress, just copy. + size_t res = 0; + for (size_t i = 0; i < stack->size && i < size; i++) { + compressed[i] = stack->trace[i]; + res++; + } + if (stack->size < size) + compressed[stack->size] = 0; +#else // 64 bits, compress. + uintptr_t prev_pc = 0; + const uintptr_t kMaxOffset = (1ULL << 30) - 1; + uintptr_t c_index = 0; + size_t res = 0; + for (size_t i = 0, n = stack->size; i < n; i++) { + uintptr_t pc = stack->trace[i]; + if (!pc) break; + if ((int64_t)pc < 0) break; + // Printf("C pc[%ld] %lx\n", i, pc); + if (prev_pc - pc < kMaxOffset || pc - prev_pc < kMaxOffset) { + uintptr_t offset = (int64_t)(pc - prev_pc); + offset |= (1U << 31); + if (c_index >= size) break; + // Printf("C co[%ld] offset %lx\n", i, offset); + compressed[c_index++] = offset; + } else { + uintptr_t hi = pc >> 32; + uintptr_t lo = (pc << 32) >> 32; + CHECK((hi & (1 << 31)) == 0); + if (c_index + 1 >= size) break; + // Printf("C co[%ld] hi/lo: %lx %lx\n", c_index, hi, lo); + compressed[c_index++] = hi; + compressed[c_index++] = lo; + } + res++; + prev_pc = pc; + } + if (c_index < size) + compressed[c_index] = 0; + if (c_index + 1 < size) + compressed[c_index + 1] = 0; +#endif // __WORDSIZE + + // debug-only code +#if 0 + AsanStackTrace check_stack; + UncompressStack(&check_stack, compressed, size); + if (res < check_stack.size) { + Printf("res %ld check_stack.size %ld; c_size %ld\n", res, + check_stack.size, size); + } + // |res| may be greater than check_stack.size, because + // UncompressStack(CompressStack(stack)) eliminates the 0x0 frames. + CHECK(res >= check_stack.size); + CHECK(0 == real_memcmp(check_stack.trace, stack->trace, + check_stack.size * sizeof(uintptr_t))); +#endif + + return res; +} + +void AsanStackTrace::UncompressStack(AsanStackTrace *stack, + uint32_t *compressed, size_t size) { +#if __WORDSIZE == 32 + // Don't uncompress, just copy. + stack->size = 0; + for (size_t i = 0; i < size && i < kStackTraceMax; i++) { + if (!compressed[i]) break; + stack->size++; + stack->trace[i] = compressed[i]; + } +#else // 64 bits, uncompress + uintptr_t prev_pc = 0; + stack->size = 0; + for (size_t i = 0; i < size && stack->size < kStackTraceMax; i++) { + uint32_t x = compressed[i]; + uintptr_t pc = 0; + if (x & (1U << 31)) { + // Printf("U co[%ld] offset: %x\n", i, x); + // this is an offset + int32_t offset = x; + offset = (offset << 1) >> 1; // remove the 31-byte and sign-extend. + pc = prev_pc + offset; + CHECK(pc); + } else { + // CHECK(i + 1 < size); + if (i + 1 >= size) break; + uintptr_t hi = x; + uintptr_t lo = compressed[i+1]; + // Printf("U co[%ld] hi/lo: %lx %lx\n", i, hi, lo); + i++; + pc = (hi << 32) | lo; + if (!pc) break; + } + // Printf("U pc[%ld] %lx\n", stack->size, pc); + stack->trace[stack->size++] = pc; + prev_pc = pc; + } +#endif // __WORDSIZE +} + +} // namespace __asan diff --git a/lib/asan/asan_stack.h b/lib/asan/asan_stack.h new file mode 100644 index 000000000000..97aefd6d4218 --- /dev/null +++ b/lib/asan/asan_stack.h @@ -0,0 +1,94 @@ +//===-- asan_stack.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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header for asan_stack.cc. +//===----------------------------------------------------------------------===// +#ifndef ASAN_STACK_H +#define ASAN_STACK_H + +#include "asan_internal.h" + +namespace __asan { + +static const size_t kStackTraceMax = 64; + +struct AsanStackTrace { + size_t size; + size_t max_size; + uintptr_t trace[kStackTraceMax]; + static void PrintStack(uintptr_t *addr, size_t size); + void PrintStack() { + PrintStack(this->trace, this->size); + } + void CopyTo(uintptr_t *dst, size_t dst_size) { + for (size_t i = 0; i < size && i < dst_size; i++) + dst[i] = trace[i]; + for (size_t i = size; i < dst_size; i++) + dst[i] = 0; + } + + void CopyFrom(uintptr_t *src, size_t src_size) { + size = src_size; + if (size > kStackTraceMax) size = kStackTraceMax; + for (size_t i = 0; i < size; i++) { + trace[i] = src[i]; + } + } + + void FastUnwindStack(uintptr_t pc, uintptr_t bp); +// static _Unwind_Reason_Code Unwind_Trace( +// struct _Unwind_Context *ctx, void *param); + static uintptr_t GetCurrentPc(); + + static size_t CompressStack(AsanStackTrace *stack, + uint32_t *compressed, size_t size); + static void UncompressStack(AsanStackTrace *stack, + uint32_t *compressed, size_t size); + size_t full_frame_count; +}; + +} // namespace __asan + +// Get the stack trace with the given pc and bp. +// The pc will be in the position 0 of the resulting stack trace. +// The bp may refer to the current frame or to the caller's frame. +// fast_unwind is currently unused. +#define GET_STACK_TRACE_WITH_PC_AND_BP(max_s, fast_unwind, pc, bp) \ + AsanStackTrace stack; \ + { \ + uintptr_t saved_pc = pc; \ + uintptr_t saved_bp = bp; \ + stack.size = 0; \ + stack.full_frame_count = 0; \ + stack.trace[0] = saved_pc; \ + if ((max_s) > 1) { \ + stack.max_size = max_s; \ + stack.FastUnwindStack(saved_pc, saved_bp); \ + } \ + } \ + +#define GET_STACK_TRACE_HERE(max_size, fast_unwind) \ + GET_STACK_TRACE_WITH_PC_AND_BP(max_size, fast_unwind, \ + AsanStackTrace::GetCurrentPc(), GET_CURRENT_FRAME()) \ + +#define GET_STACK_TRACE_HERE_FOR_MALLOC \ + GET_STACK_TRACE_HERE(FLAG_malloc_context_size, FLAG_fast_unwind) + +#define GET_STACK_TRACE_HERE_FOR_FREE(ptr) \ + GET_STACK_TRACE_HERE(FLAG_malloc_context_size, FLAG_fast_unwind) + +#define PRINT_CURRENT_STACK() \ + { \ + GET_STACK_TRACE_HERE(kStackTraceMax, false); \ + stack.PrintStack(); \ + } \ + +#endif // ASAN_STACK_H diff --git a/lib/asan/asan_stats.cc b/lib/asan/asan_stats.cc new file mode 100644 index 000000000000..3e4d1b4f52d9 --- /dev/null +++ b/lib/asan/asan_stats.cc @@ -0,0 +1,88 @@ +//===-- asan_stats.cc -------------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Code related to statistics collected by AddressSanitizer. +//===----------------------------------------------------------------------===// +#include "asan_interceptors.h" +#include "asan_interface.h" +#include "asan_internal.h" +#include "asan_lock.h" +#include "asan_stats.h" +#include "asan_thread_registry.h" + +namespace __asan { + +AsanStats::AsanStats() { + CHECK(real_memset != NULL); + real_memset(this, 0, sizeof(AsanStats)); +} + +static void PrintMallocStatsArray(const char *prefix, + size_t (&array)[kNumberOfSizeClasses]) { + Printf("%s", prefix); + for (size_t i = 0; i < kNumberOfSizeClasses; i++) { + if (!array[i]) continue; + Printf("%ld:%ld; ", i, array[i]); + } + Printf("\n"); +} + +void AsanStats::Print() { + Printf("Stats: %ldM malloced (%ldM for red zones) by %ld calls\n", + malloced>>20, malloced_redzones>>20, mallocs); + Printf("Stats: %ldM realloced by %ld calls\n", realloced>>20, reallocs); + Printf("Stats: %ldM freed by %ld calls\n", freed>>20, frees); + Printf("Stats: %ldM really freed by %ld calls\n", + really_freed>>20, real_frees); + Printf("Stats: %ldM (%ld full pages) mmaped in %ld calls\n", + mmaped>>20, mmaped / kPageSize, mmaps); + + PrintMallocStatsArray(" mmaps by size class: ", mmaped_by_size); + PrintMallocStatsArray(" mallocs by size class: ", malloced_by_size); + PrintMallocStatsArray(" frees by size class: ", freed_by_size); + PrintMallocStatsArray(" rfrees by size class: ", really_freed_by_size); + Printf("Stats: malloc large: %ld small slow: %ld\n", + malloc_large, malloc_small_slow); +} + +static AsanLock print_lock(LINKER_INITIALIZED); + +static void PrintAccumulatedStats() { + AsanStats stats = asanThreadRegistry().GetAccumulatedStats(); + // Use lock to keep reports from mixing up. + ScopedLock lock(&print_lock); + stats.Print(); +} + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +size_t __asan_get_current_allocated_bytes() { + return asanThreadRegistry().GetCurrentAllocatedBytes(); +} + +size_t __asan_get_heap_size() { + return asanThreadRegistry().GetHeapSize(); +} + +size_t __asan_get_free_bytes() { + return asanThreadRegistry().GetFreeBytes(); +} + +size_t __asan_get_unmapped_bytes() { + return 0; +} + +void __asan_print_accumulated_stats() { + PrintAccumulatedStats(); +} diff --git a/lib/asan/asan_stats.h b/lib/asan/asan_stats.h new file mode 100644 index 000000000000..d6dd084c013d --- /dev/null +++ b/lib/asan/asan_stats.h @@ -0,0 +1,59 @@ +//===-- asan_stats.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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header for statistics. +//===----------------------------------------------------------------------===// +#ifndef ASAN_STATS_H +#define ASAN_STATS_H + +#include "asan_allocator.h" +#include "asan_internal.h" + +namespace __asan { + +// AsanStats struct is NOT thread-safe. +// Each AsanThread has its own AsanStats, which are sometimes flushed +// to the accumulated AsanStats. +struct AsanStats { + // AsanStats must be a struct consisting of size_t fields only. + // When merging two AsanStats structs, we treat them as arrays of size_t. + size_t mallocs; + size_t malloced; + size_t malloced_redzones; + size_t frees; + size_t freed; + size_t real_frees; + size_t really_freed; + size_t really_freed_redzones; + size_t reallocs; + size_t realloced; + size_t mmaps; + size_t mmaped; + size_t mmaped_by_size[kNumberOfSizeClasses]; + size_t malloced_by_size[kNumberOfSizeClasses]; + size_t freed_by_size[kNumberOfSizeClasses]; + size_t really_freed_by_size[kNumberOfSizeClasses]; + + size_t malloc_large; + size_t malloc_small_slow; + + // Ctor for global AsanStats (accumulated stats and main thread stats). + explicit AsanStats(LinkerInitialized) { } + // Default ctor for thread-local stats. + AsanStats(); + + // Prints formatted stats to stderr. + void Print(); +}; + +} // namespace __asan + +#endif // ASAN_STATS_H diff --git a/lib/asan/asan_thread.cc b/lib/asan/asan_thread.cc new file mode 100644 index 000000000000..329197dd4e25 --- /dev/null +++ b/lib/asan/asan_thread.cc @@ -0,0 +1,178 @@ +//===-- asan_thread.cc ------------------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Thread-related code. +//===----------------------------------------------------------------------===// +#include "asan_allocator.h" +#include "asan_interceptors.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" +#include "asan_mapping.h" + +#if ASAN_USE_SYSINFO == 1 +#include "sysinfo/sysinfo.h" +#endif + +#include <sys/time.h> +#include <sys/resource.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +namespace __asan { + +AsanThread::AsanThread(LinkerInitialized x) + : fake_stack_(x), + malloc_storage_(x), + stats_(x) { } + +AsanThread::AsanThread(int parent_tid, void *(*start_routine) (void *), + void *arg, AsanStackTrace *stack) + : start_routine_(start_routine), + arg_(arg) { + asanThreadRegistry().RegisterThread(this, parent_tid, stack); +} + +AsanThread::~AsanThread() { + asanThreadRegistry().UnregisterThread(this); + fake_stack().Cleanup(); + // We also clear the shadow on thread destruction because + // some code may still be executing in later TSD destructors + // and we don't want it to have any poisoned stack. + ClearShadowForThreadStack(); +} + +void AsanThread::ClearShadowForThreadStack() { + uintptr_t shadow_bot = MemToShadow(stack_bottom_); + uintptr_t shadow_top = MemToShadow(stack_top_); + real_memset((void*)shadow_bot, 0, shadow_top - shadow_bot); +} + +void AsanThread::Init() { + SetThreadStackTopAndBottom(); + fake_stack_.Init(stack_size()); + if (FLAG_v >= 1) { + int local = 0; + Report("T%d: stack [%p,%p) size 0x%lx; local=%p, pthread_self=%p\n", + tid(), stack_bottom_, stack_top_, + stack_top_ - stack_bottom_, &local, pthread_self()); + } + + CHECK(AddrIsInMem(stack_bottom_)); + CHECK(AddrIsInMem(stack_top_)); + + ClearShadowForThreadStack(); +} + +void *AsanThread::ThreadStart() { + Init(); + + if (!start_routine_) { + // start_routine_ == NULL if we're on the main thread or on one of the + // OS X libdispatch worker threads. But nobody is supposed to call + // ThreadStart() for the worker threads. + CHECK(tid() == 0); + return 0; + } + + void *res = start_routine_(arg_); + malloc_storage().CommitBack(); + + if (FLAG_v >= 1) { + Report("T%d exited\n", tid()); + } + + return res; +} + +const char *AsanThread::GetFrameNameByAddr(uintptr_t addr, uintptr_t *offset) { + uintptr_t bottom = 0; + bool is_fake_stack = false; + if (AddrIsInStack(addr)) { + bottom = stack_bottom(); + } else { + bottom = fake_stack().AddrIsInFakeStack(addr); + CHECK(bottom); + is_fake_stack = true; + } + uintptr_t aligned_addr = addr & ~(__WORDSIZE/8 - 1); // align addr. + uintptr_t *ptr = (uintptr_t*)aligned_addr; + while (ptr >= (uintptr_t*)bottom) { + if (ptr[0] == kCurrentStackFrameMagic || + (is_fake_stack && ptr[0] == kRetiredStackFrameMagic)) { + *offset = addr - (uintptr_t)ptr; + return (const char*)ptr[1]; + } + ptr--; + } + *offset = 0; + return "UNKNOWN"; +} + +void AsanThread::SetThreadStackTopAndBottom() { +#ifdef __APPLE__ + size_t stacksize = pthread_get_stacksize_np(pthread_self()); + void *stackaddr = pthread_get_stackaddr_np(pthread_self()); + stack_top_ = (uintptr_t)stackaddr; + stack_bottom_ = stack_top_ - stacksize; + int local; + CHECK(AddrIsInStack((uintptr_t)&local)); +#else +#if ASAN_USE_SYSINFO == 1 + if (tid() == 0) { + // This is the main thread. Libpthread may not be initialized yet. + struct rlimit rl; + CHECK(getrlimit(RLIMIT_STACK, &rl) == 0); + + // Find the mapping that contains a stack variable. + ProcMapsIterator it(0); + uint64_t start, end; + uint64_t prev_end = 0; + while (it.Next(&start, &end, NULL, NULL, NULL, NULL)) { + if ((uintptr_t)&rl < end) + break; + prev_end = end; + } + CHECK((uintptr_t)&rl >= start && (uintptr_t)&rl < end); + + // Get stacksize from rlimit, but clip it so that it does not overlap + // with other mappings. + size_t stacksize = rl.rlim_cur; + if (stacksize > end - prev_end) + stacksize = end - prev_end; + if (stacksize > kMaxThreadStackSize) + stacksize = kMaxThreadStackSize; + stack_top_ = end; + stack_bottom_ = end - stacksize; + CHECK(AddrIsInStack((uintptr_t)&rl)); + return; + } +#endif + pthread_attr_t attr; + CHECK(pthread_getattr_np(pthread_self(), &attr) == 0); + size_t stacksize = 0; + void *stackaddr = NULL; + pthread_attr_getstack(&attr, &stackaddr, &stacksize); + pthread_attr_destroy(&attr); + + stack_top_ = (uintptr_t)stackaddr + stacksize; + stack_bottom_ = (uintptr_t)stackaddr; + // When running with unlimited stack size, we still want to set some limit. + // The unlimited stack size is caused by 'ulimit -s unlimited'. + // Also, for some reason, GNU make spawns subrocesses with unlimited stack. + if (stacksize > kMaxThreadStackSize) { + stack_bottom_ = stack_top_ - kMaxThreadStackSize; + } + CHECK(AddrIsInStack((uintptr_t)&attr)); +#endif +} + +} // namespace __asan diff --git a/lib/asan/asan_thread.h b/lib/asan/asan_thread.h new file mode 100644 index 000000000000..c382c85a6a3c --- /dev/null +++ b/lib/asan/asan_thread.h @@ -0,0 +1,108 @@ +//===-- asan_thread.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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header for asan_thread.cc. +//===----------------------------------------------------------------------===// +#ifndef ASAN_THREAD_H +#define ASAN_THREAD_H + +#include "asan_allocator.h" +#include "asan_internal.h" +#include "asan_stack.h" +#include "asan_stats.h" + +namespace __asan { + +const size_t kMaxThreadStackSize = 16 * (1 << 20); // 16M + +class AsanThread; + +// These objects are created for every thread and are never deleted, +// so we can find them by tid even if the thread is long dead. +class AsanThreadSummary { + public: + explicit AsanThreadSummary(LinkerInitialized) { } // for T0. + AsanThreadSummary(int tid, int parent_tid, AsanStackTrace *stack) + : tid_(tid), + parent_tid_(parent_tid), + announced_(false) { + if (stack) { + stack_ = *stack; + } + thread_ = 0; + } + void Announce() { + if (tid_ == 0) return; // no need to announce the main thread. + if (!announced_) { + announced_ = true; + Printf("Thread T%d created by T%d here:\n", tid_, parent_tid_); + stack_.PrintStack(); + } + } + int tid() { return tid_; } + AsanThread *thread() { return thread_; } + void set_thread(AsanThread *thread) { thread_ = thread; } + private: + int tid_; + int parent_tid_; + bool announced_; + AsanStackTrace stack_; + AsanThread *thread_; +}; + +// AsanThread are stored in TSD and destroyed when the thread dies. +class AsanThread { + public: + explicit AsanThread(LinkerInitialized); // for T0. + AsanThread(int parent_tid, void *(*start_routine) (void *), + void *arg, AsanStackTrace *stack); + ~AsanThread(); + + void Init(); // Should be called from the thread itself. + void *ThreadStart(); + + uintptr_t stack_top() { return stack_top_; } + uintptr_t stack_bottom() { return stack_bottom_; } + size_t stack_size() { return stack_top_ - stack_bottom_; } + int tid() { return summary_->tid(); } + AsanThreadSummary *summary() { return summary_; } + void set_summary(AsanThreadSummary *summary) { summary_ = summary; } + + const char *GetFrameNameByAddr(uintptr_t addr, uintptr_t *offset); + + bool AddrIsInStack(uintptr_t addr) { + return addr >= stack_bottom_ && addr < stack_top_; + } + + FakeStack &fake_stack() { return fake_stack_; } + AsanThreadLocalMallocStorage &malloc_storage() { return malloc_storage_; } + AsanStats &stats() { return stats_; } + + static const int kInvalidTid = -1; + + private: + + void SetThreadStackTopAndBottom(); + void ClearShadowForThreadStack(); + AsanThreadSummary *summary_; + void *(*start_routine_) (void *param); + void *arg_; + uintptr_t stack_top_; + uintptr_t stack_bottom_; + + FakeStack fake_stack_; + AsanThreadLocalMallocStorage malloc_storage_; + AsanStats stats_; +}; + +} // namespace __asan + +#endif // ASAN_THREAD_H diff --git a/lib/asan/asan_thread_registry.cc b/lib/asan/asan_thread_registry.cc new file mode 100644 index 000000000000..39fba4401a2b --- /dev/null +++ b/lib/asan/asan_thread_registry.cc @@ -0,0 +1,227 @@ +//===-- asan_thread_registry.cc ---------------------------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// AsanThreadRegistry-related code. AsanThreadRegistry is a container +// for summaries of all created threads. +//===----------------------------------------------------------------------===// + +#include "asan_stack.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" + +#include <limits.h> + +namespace __asan { + +static AsanThreadRegistry asan_thread_registry(__asan::LINKER_INITIALIZED); + +AsanThreadRegistry &asanThreadRegistry() { + return asan_thread_registry; +} + +#ifdef ANDROID +#ifndef PTHREAD_DESTRUCTOR_ITERATIONS +#define PTHREAD_DESTRUCTOR_ITERATIONS 4 +#endif +#endif + +// Dark magic below. In order to be able to notice that we're not handling +// some thread creation routines (e.g. on Mac OS) we want to distinguish the +// thread that used to have a corresponding AsanThread object from the thread +// that never had one. That's why upon AsanThread destruction we set the +// pthread_key value to some odd number (that's not a valid pointer), instead +// of NULL. +// Because the TSD destructor for a non-NULL key value is called iteratively, +// we increase the value by two, keeping it an invalid pointer. +// Because the TSD implementations are allowed to call such a destructor +// infinitely (see +// http://pubs.opengroup.org/onlinepubs/009604499/functions/pthread_key_create.html +// ), we exit the program after a certain number of iterations. +static void DestroyAsanTsd(void *tsd) { + intptr_t iter = (intptr_t)tsd; + if (iter % 2 == 0) { + // The pointer is valid. + AsanThread *t = (AsanThread*)tsd; + if (t != asanThreadRegistry().GetMain()) { + delete t; + } + iter = 1; + } else { + // The pointer is invalid -- we've already destroyed the TSD before. + // If |iter| is too big, we're in the infinite loop. This should be + // impossible on the systems AddressSanitizer was tested on. + CHECK(iter < 4 * PTHREAD_DESTRUCTOR_ITERATIONS); + iter += 2; + } + CHECK(0 == pthread_setspecific(asanThreadRegistry().GetTlsKey(), + (void*)iter)); + if (FLAG_v >= 2) { + Report("DestroyAsanTsd: writing %p to the TSD slot of thread %p\n", + (void*)iter, pthread_self()); + } +} + +AsanThreadRegistry::AsanThreadRegistry(LinkerInitialized x) + : main_thread_(x), + main_thread_summary_(x), + accumulated_stats_(x), + mu_(x) { } + +void AsanThreadRegistry::Init() { + CHECK(0 == pthread_key_create(&tls_key_, DestroyAsanTsd)); + tls_key_created_ = true; + SetCurrent(&main_thread_); + main_thread_.set_summary(&main_thread_summary_); + main_thread_summary_.set_thread(&main_thread_); + thread_summaries_[0] = &main_thread_summary_; + n_threads_ = 1; +} + +void AsanThreadRegistry::RegisterThread(AsanThread *thread, int parent_tid, + AsanStackTrace *stack) { + ScopedLock lock(&mu_); + CHECK(n_threads_ > 0); + int tid = n_threads_; + n_threads_++; + CHECK(n_threads_ < kMaxNumberOfThreads); + AsanThreadSummary *summary = new AsanThreadSummary(tid, parent_tid, stack); + summary->set_thread(thread); + thread_summaries_[tid] = summary; + thread->set_summary(summary); +} + +void AsanThreadRegistry::UnregisterThread(AsanThread *thread) { + ScopedLock lock(&mu_); + FlushToAccumulatedStatsUnlocked(&thread->stats()); + AsanThreadSummary *summary = thread->summary(); + CHECK(summary); + summary->set_thread(NULL); +} + +AsanThread *AsanThreadRegistry::GetMain() { + return &main_thread_; +} + +AsanThread *AsanThreadRegistry::GetCurrent() { + CHECK(tls_key_created_); + AsanThread *thread = (AsanThread*)pthread_getspecific(tls_key_); + if ((!thread || (intptr_t)thread % 2) && FLAG_v >= 2) { + Report("GetCurrent: %p for thread %p\n", thread, pthread_self()); + } + if ((intptr_t)thread % 2) { + // Invalid pointer -- we've deleted the AsanThread already. Return NULL as + // if the TSD was empty. + // TODO(glider): if the code in the client TSD destructor calls + // pthread_create(), we'll set the parent tid of the spawned thread to NULL, + // although the creation stack will belong to the current thread. This may + // confuse the user, but is quite unlikely. + return NULL; + } else { + // NULL or valid pointer to AsanThread. + return thread; + } +} + +void AsanThreadRegistry::SetCurrent(AsanThread *t) { + if (FLAG_v >=2) { + Report("SetCurrent: %p for thread %p\n", t, pthread_self()); + } + // Make sure we do not reset the current AsanThread. + intptr_t old_key = (intptr_t)pthread_getspecific(tls_key_); + CHECK(!old_key || old_key % 2); + CHECK(0 == pthread_setspecific(tls_key_, t)); + CHECK(pthread_getspecific(tls_key_) == t); +} + +pthread_key_t AsanThreadRegistry::GetTlsKey() { + return tls_key_; +} + +// Returns true iff DestroyAsanTsd() was already called for this thread. +bool AsanThreadRegistry::IsCurrentThreadDying() { + CHECK(tls_key_created_); + intptr_t thread = (intptr_t)pthread_getspecific(tls_key_); + return (bool)(thread % 2); +} + +AsanStats &AsanThreadRegistry::GetCurrentThreadStats() { + AsanThread *t = GetCurrent(); + return (t) ? t->stats() : main_thread_.stats(); +} + +AsanStats AsanThreadRegistry::GetAccumulatedStats() { + ScopedLock lock(&mu_); + UpdateAccumulatedStatsUnlocked(); + return accumulated_stats_; +} + +size_t AsanThreadRegistry::GetCurrentAllocatedBytes() { + ScopedLock lock(&mu_); + UpdateAccumulatedStatsUnlocked(); + return accumulated_stats_.malloced - accumulated_stats_.freed; +} + +size_t AsanThreadRegistry::GetHeapSize() { + ScopedLock lock(&mu_); + UpdateAccumulatedStatsUnlocked(); + return accumulated_stats_.mmaped; +} + +size_t AsanThreadRegistry::GetFreeBytes() { + ScopedLock lock(&mu_); + UpdateAccumulatedStatsUnlocked(); + return accumulated_stats_.mmaped + - accumulated_stats_.malloced + - accumulated_stats_.malloced_redzones + + accumulated_stats_.really_freed + + accumulated_stats_.really_freed_redzones; +} + +AsanThreadSummary *AsanThreadRegistry::FindByTid(int tid) { + CHECK(tid >= 0); + CHECK(tid < n_threads_); + CHECK(thread_summaries_[tid]); + return thread_summaries_[tid]; +} + +AsanThread *AsanThreadRegistry::FindThreadByStackAddress(uintptr_t addr) { + ScopedLock lock(&mu_); + for (int tid = 0; tid < n_threads_; tid++) { + AsanThread *t = thread_summaries_[tid]->thread(); + if (!t) continue; + if (t->fake_stack().AddrIsInFakeStack(addr) || t->AddrIsInStack(addr)) { + return t; + } + } + return 0; +} + +void AsanThreadRegistry::UpdateAccumulatedStatsUnlocked() { + for (int tid = 0; tid < n_threads_; tid++) { + AsanThread *t = thread_summaries_[tid]->thread(); + if (t != NULL) { + FlushToAccumulatedStatsUnlocked(&t->stats()); + } + } +} + +void AsanThreadRegistry::FlushToAccumulatedStatsUnlocked(AsanStats *stats) { + // AsanStats consists of variables of type size_t only. + size_t *dst = (size_t*)&accumulated_stats_; + size_t *src = (size_t*)stats; + size_t num_fields = sizeof(AsanStats) / sizeof(size_t); + for (size_t i = 0; i < num_fields; i++) { + dst[i] += src[i]; + src[i] = 0; + } +} + +} // namespace __asan diff --git a/lib/asan/asan_thread_registry.h b/lib/asan/asan_thread_registry.h new file mode 100644 index 000000000000..b80dd4da4df7 --- /dev/null +++ b/lib/asan/asan_thread_registry.h @@ -0,0 +1,88 @@ +//===-- asan_thread_registry.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 a part of AddressSanitizer, an address sanity checker. +// +// ASan-private header for asan_thread_registry.cc +//===----------------------------------------------------------------------===// + +#ifndef ASAN_THREAD_REGISTRY_H +#define ASAN_THREAD_REGISTRY_H + +#include "asan_lock.h" +#include "asan_stack.h" +#include "asan_stats.h" +#include "asan_thread.h" + +namespace __asan { + +// Stores summaries of all created threads, returns current thread, +// thread by tid, thread by stack address. There is a single instance +// of AsanThreadRegistry for the whole program. +// AsanThreadRegistry is thread-safe. +class AsanThreadRegistry { + public: + explicit AsanThreadRegistry(LinkerInitialized); + void Init(); + void RegisterThread(AsanThread *thread, int parent_tid, + AsanStackTrace *stack); + void UnregisterThread(AsanThread *thread); + + AsanThread *GetMain(); + // Get the current thread. May return NULL. + AsanThread *GetCurrent(); + void SetCurrent(AsanThread *t); + pthread_key_t GetTlsKey(); + bool IsCurrentThreadDying(); + + int GetCurrentTidOrMinusOne() { + AsanThread *t = GetCurrent(); + return t ? t->tid() : -1; + } + + // Returns stats for GetCurrent(), or stats for + // T0 if GetCurrent() returns NULL. + AsanStats &GetCurrentThreadStats(); + // Flushes all thread-local stats to accumulated stats, and returns + // a copy of accumulated stats. + AsanStats GetAccumulatedStats(); + size_t GetCurrentAllocatedBytes(); + size_t GetHeapSize(); + size_t GetFreeBytes(); + + AsanThreadSummary *FindByTid(int tid); + AsanThread *FindThreadByStackAddress(uintptr_t addr); + + private: + void UpdateAccumulatedStatsUnlocked(); + // Adds values of all counters in "stats" to accumulated stats, + // and fills "stats" with zeroes. + void FlushToAccumulatedStatsUnlocked(AsanStats *stats); + + static const int kMaxNumberOfThreads = (1 << 22); // 4M + AsanThreadSummary *thread_summaries_[kMaxNumberOfThreads]; + AsanThread main_thread_; + AsanThreadSummary main_thread_summary_; + AsanStats accumulated_stats_; + int n_threads_; + AsanLock mu_; + // For each thread tls_key_ stores the pointer to the corresponding + // AsanThread. + pthread_key_t tls_key_; + // This flag is updated only once at program startup, and then read + // by concurrent threads. + bool tls_key_created_; +}; + +// Returns a single instance of registry. +AsanThreadRegistry &asanThreadRegistry(); + +} // namespace __asan + +#endif // ASAN_THREAD_REGISTRY_H diff --git a/lib/asan/mach_override/LICENSE.TXT b/lib/asan/mach_override/LICENSE.TXT new file mode 100644 index 000000000000..9446965176ce --- /dev/null +++ b/lib/asan/mach_override/LICENSE.TXT @@ -0,0 +1,3 @@ +Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> +Some rights reserved: <http://opensource.org/licenses/mit-license.php> + diff --git a/lib/asan/mach_override/Makefile.mk b/lib/asan/mach_override/Makefile.mk new file mode 100644 index 000000000000..78be0b383977 --- /dev/null +++ b/lib/asan/mach_override/Makefile.mk @@ -0,0 +1,22 @@ +#===- lib/asan/mach_override/Makefile.mk -------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +ModuleName := asan +SubDirs := + +Sources := $(foreach file,$(wildcard $(Dir)/*.c),$(notdir $(file))) +ObjNames := $(Sources:%.c=%.o) + +Implementation := Generic + +# FIXME: use automatic dependencies? +Dependencies := $(wildcard $(Dir)/*.h) + +# Define a convenience variable for all the asan functions. +AsanFunctions += $(Sources:%.c=%) diff --git a/lib/asan/mach_override/README.txt b/lib/asan/mach_override/README.txt new file mode 100644 index 000000000000..5f62ad7b994f --- /dev/null +++ b/lib/asan/mach_override/README.txt @@ -0,0 +1,9 @@ +-- mach_override.c is taken from upstream version at + https://github.com/rentzsch/mach_star/tree/f8e0c424b5be5cb641ded67c265e616157ae4bcf +-- Added debugging code under DEBUG_DISASM. +-- The files are guarded with #ifdef __APPLE__ +-- some opcodes are added in order to parse the library functions on Lion +-- fixupInstructions() is extended to relocate relative calls, not only jumps +-- mach_override_ptr is renamed to __asan_mach_override_ptr and + other functions are marked as hidden. + diff --git a/lib/asan/mach_override/mach_override.c b/lib/asan/mach_override/mach_override.c new file mode 100644 index 000000000000..640d03d58970 --- /dev/null +++ b/lib/asan/mach_override/mach_override.c @@ -0,0 +1,862 @@ +/******************************************************************************* + mach_override.c + Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + Some rights reserved: <http://opensource.org/licenses/mit-license.php> + + ***************************************************************************/ +#ifdef __APPLE__ + +#include "mach_override.h" + +#include <mach-o/dyld.h> +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <mach/vm_map.h> +#include <sys/mman.h> + +#include <CoreServices/CoreServices.h> + +//#define DEBUG_DISASM 1 +#undef DEBUG_DISASM + +/************************** +* +* Constants +* +**************************/ +#pragma mark - +#pragma mark (Constants) + +#if defined(__ppc__) || defined(__POWERPC__) + +long kIslandTemplate[] = { + 0x9001FFFC, // stw r0,-4(SP) + 0x3C00DEAD, // lis r0,0xDEAD + 0x6000BEEF, // ori r0,r0,0xBEEF + 0x7C0903A6, // mtctr r0 + 0x8001FFFC, // lwz r0,-4(SP) + 0x60000000, // nop ; optionally replaced + 0x4E800420 // bctr +}; + +#define kAddressHi 3 +#define kAddressLo 5 +#define kInstructionHi 10 +#define kInstructionLo 11 + +#elif defined(__i386__) + +#define kOriginalInstructionsSize 16 + +char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xE9, 0xEF, 0xBE, 0xAD, 0xDE +}; + +#define kInstructions 0 +#define kJumpAddress kInstructions + kOriginalInstructionsSize + 1 +#elif defined(__x86_64__) + +#define kOriginalInstructionsSize 32 + +#define kJumpAddress kOriginalInstructionsSize + 6 + +char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +#endif + +#define kAllocateHigh 1 +#define kAllocateNormal 0 + +/************************** +* +* Data Types +* +**************************/ +#pragma mark - +#pragma mark (Data Types) + +typedef struct { + char instructions[sizeof(kIslandTemplate)]; + int allocatedHigh; +} BranchIsland; + +/************************** +* +* Funky Protos +* +**************************/ +#pragma mark - +#pragma mark (Funky Protos) + + mach_error_t +allocateBranchIsland( + BranchIsland **island, + int allocateHigh, + void *originalFunctionAddress) __attribute__((visibility("hidden"))); + + mach_error_t +freeBranchIsland( + BranchIsland *island ) __attribute__((visibility("hidden"))); + +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ) __attribute__((visibility("hidden"))); +#endif + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) __attribute__((visibility("hidden"))); +void +atomic_mov64( + uint64_t *targetAddress, + uint64_t value ) __attribute__((visibility("hidden"))); + + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ) __attribute__((visibility("hidden"))); + + static void +fixupInstructions( + void *originalFunction, + void *escapeIsland, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ) __attribute__((visibility("hidden"))); +#endif + +/******************************************************************************* +* +* Interface +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Interface) + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t makeIslandExecutable(void *address) { + mach_error_t err = err_none; + vm_size_t pageSize; + host_page_size( mach_host_self(), &pageSize ); + uintptr_t page = (uintptr_t)address & ~(uintptr_t)(pageSize-1); + int e = err_none; + e |= mprotect((void *)page, pageSize, PROT_EXEC | PROT_READ | PROT_WRITE); + e |= msync((void *)page, pageSize, MS_INVALIDATE ); + if (e) { + err = err_cannot_override; + } + return err; +} +#endif + + mach_error_t +__asan_mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ) +{ + assert( originalFunctionAddress ); + assert( overrideFunctionAddress ); + + // this addresses overriding such functions as AudioOutputUnitStart() + // test with modified DefaultOutputUnit project +#if defined(__x86_64__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp qword near [rip+0x????????] + originalFunctionAddress=*(void**)((char*)originalFunctionAddress+6+*(int32_t *)((uint16_t*)originalFunctionAddress+1)); + else break; + } +#elif defined(__i386__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp *0x???????? + originalFunctionAddress=**(void***)((uint16_t*)originalFunctionAddress+1); + else break; + } +#endif +#ifdef DEBUG_DISASM + { + fprintf(stderr, "Replacing function at %p\n", originalFunctionAddress); + fprintf(stderr, "First 16 bytes of the function: "); + unsigned char *orig = (unsigned char *)originalFunctionAddress; + int i; + for (i = 0; i < 16; i++) { + fprintf(stderr, "%x ", (unsigned int) orig[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, + "To disassemble, save the following function as disas.c" + " and run:\n gcc -c disas.c && gobjdump -d disas.o\n" + "The first 16 bytes of the original function will start" + " after four nop instructions.\n"); + fprintf(stderr, "\nvoid foo() {\n asm volatile(\"nop;nop;nop;nop;\");\n"); + int j = 0; + for (j = 0; j < 2; j++) { + fprintf(stderr, " asm volatile(\".byte "); + for (i = 8 * j; i < 8 * (j+1) - 1; i++) { + fprintf(stderr, "0x%x, ", (unsigned int) orig[i]); + } + fprintf(stderr, "0x%x;\");\n", (unsigned int) orig[8 * (j+1) - 1]); + } + fprintf(stderr, "}\n\n"); + } +#endif + + long *originalFunctionPtr = (long*) originalFunctionAddress; + mach_error_t err = err_none; + +#if defined(__ppc__) || defined(__POWERPC__) + // Ensure first instruction isn't 'mfctr'. + #define kMFCTRMask 0xfc1fffff + #define kMFCTRInstruction 0x7c0903a6 + + long originalInstruction = *originalFunctionPtr; + if( !err && ((originalInstruction & kMFCTRMask) == kMFCTRInstruction) ) + err = err_cannot_override; +#elif defined(__i386__) || defined(__x86_64__) + int eatenCount = 0; + int originalInstructionCount = 0; + char originalInstructions[kOriginalInstructionsSize]; + uint8_t originalInstructionSizes[kOriginalInstructionsSize]; + uint64_t jumpRelativeInstruction = 0; // JMP + + Boolean overridePossible = eatKnownInstructions ((unsigned char *)originalFunctionPtr, + &jumpRelativeInstruction, &eatenCount, + originalInstructions, &originalInstructionCount, + originalInstructionSizes ); +#ifdef DEBUG_DISASM + if (!overridePossible) fprintf(stderr, "overridePossible = false @%d\n", __LINE__); +#endif + if (eatenCount > kOriginalInstructionsSize) { +#ifdef DEBUG_DISASM + fprintf(stderr, "Too many instructions eaten\n"); +#endif + overridePossible = false; + } + if (!overridePossible) err = err_cannot_override; + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); +#endif + + // Make the original function implementation writable. + if( !err ) { + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_ALL | VM_PROT_COPY) ); + if( err ) + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_DEFAULT | VM_PROT_COPY) ); + } + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + // Allocate and target the escape island to the overriding function. + BranchIsland *escapeIsland = NULL; + if( !err ) + err = allocateBranchIsland( &escapeIsland, kAllocateHigh, originalFunctionAddress ); + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + +#if defined(__ppc__) || defined(__POWERPC__) + if( !err ) + err = setBranchIslandTarget( escapeIsland, overrideFunctionAddress, 0 ); + + // Build the branch absolute instruction to the escape island. + long branchAbsoluteInstruction = 0; // Set to 0 just to silence warning. + if( !err ) { + long escapeIslandAddress = ((long) escapeIsland) & 0x3FFFFFF; + branchAbsoluteInstruction = 0x48000002 | escapeIslandAddress; + } +#elif defined(__i386__) || defined(__x86_64__) + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + if( !err ) + err = setBranchIslandTarget_i386( escapeIsland, overrideFunctionAddress, 0 ); + + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + // Build the jump relative instruction to the escape island +#endif + + +#if defined(__i386__) || defined(__x86_64__) + if (!err) { + uint32_t addressOffset = ((char*)escapeIsland - (char*)originalFunctionPtr - 5); + addressOffset = OSSwapInt32(addressOffset); + + jumpRelativeInstruction |= 0xE900000000000000LL; + jumpRelativeInstruction |= ((uint64_t)addressOffset & 0xffffffff) << 24; + jumpRelativeInstruction = OSSwapInt64(jumpRelativeInstruction); + } +#endif + + // Optionally allocate & return the reentry island. This may contain relocated + // jmp instructions and so has all the same addressing reachability requirements + // the escape island has to the original function, except the escape island is + // technically our original function. + BranchIsland *reentryIsland = NULL; + if( !err && originalFunctionReentryIsland ) { + err = allocateBranchIsland( &reentryIsland, kAllocateHigh, escapeIsland); + if( !err ) + *originalFunctionReentryIsland = reentryIsland; + } + +#if defined(__ppc__) || defined(__POWERPC__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instruction into the reentry island. + // o Target the reentry island at the 2nd instruction of the + // original function. + // o Replace the original instruction with the branch absolute. + if( !err ) { + int escapeIslandEngaged = false; + do { + if( reentryIsland ) + err = setBranchIslandTarget( reentryIsland, + (void*) (originalFunctionPtr+1), originalInstruction ); + if( !err ) { + escapeIslandEngaged = CompareAndSwap( originalInstruction, + branchAbsoluteInstruction, + (UInt32*)originalFunctionPtr ); + if( !escapeIslandEngaged ) { + // Someone replaced the instruction out from under us, + // re-read the instruction, make sure it's still not + // 'mfctr' and try again. + originalInstruction = *originalFunctionPtr; + if( (originalInstruction & kMFCTRMask) == kMFCTRInstruction) + err = err_cannot_override; + } + } + } while( !err && !escapeIslandEngaged ); + } +#elif defined(__i386__) || defined(__x86_64__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instructions into the reentry island. + // o Target the reentry island at the first non-replaced + // instruction of the original function. + // o Replace the original first instructions with the jump relative. + // + // Note that on i386, we do not support someone else changing the code under our feet + if ( !err ) { + fixupInstructions(originalFunctionPtr, reentryIsland, originalInstructions, + originalInstructionCount, originalInstructionSizes ); + + if( reentryIsland ) + err = setBranchIslandTarget_i386( reentryIsland, + (void*) ((char *)originalFunctionPtr+eatenCount), originalInstructions ); + // try making islands executable before planting the jmp +#if defined(__x86_64__) || defined(__i386__) + if( !err ) + err = makeIslandExecutable(escapeIsland); + if( !err && reentryIsland ) + err = makeIslandExecutable(reentryIsland); +#endif + if ( !err ) + atomic_mov64((uint64_t *)originalFunctionPtr, jumpRelativeInstruction); + } +#endif + + // Clean up on error. + if( err ) { + if( reentryIsland ) + freeBranchIsland( reentryIsland ); + if( escapeIsland ) + freeBranchIsland( escapeIsland ); + } + +#ifdef DEBUG_DISASM + { + fprintf(stderr, "First 16 bytes of the function after slicing: "); + unsigned char *orig = (unsigned char *)originalFunctionAddress; + int i; + for (i = 0; i < 16; i++) { + fprintf(stderr, "%x ", (unsigned int) orig[i]); + } + fprintf(stderr, "\n"); + } +#endif + return err; +} + +/******************************************************************************* +* +* Implementation +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Implementation) + +/***************************************************************************//** + Implementation: Allocates memory for a branch island. + + @param island <- The allocated island. + @param allocateHigh -> Whether to allocate the island at the end of the + address space (for use with the branch absolute + instruction). + @result <- mach_error_t + + ***************************************************************************/ + + mach_error_t +allocateBranchIsland( + BranchIsland **island, + int allocateHigh, + void *originalFunctionAddress) +{ + assert( island ); + + mach_error_t err = err_none; + + if( allocateHigh ) { + vm_size_t pageSize; + err = host_page_size( mach_host_self(), &pageSize ); + if( !err ) { + assert( sizeof( BranchIsland ) <= pageSize ); +#if defined(__ppc__) || defined(__POWERPC__) + vm_address_t first = 0xfeffffff; + vm_address_t last = 0xfe000000 + pageSize; +#elif defined(__x86_64__) + vm_address_t first = ((uint64_t)originalFunctionAddress & ~(uint64_t)(((uint64_t)1 << 31) - 1)) | ((uint64_t)1 << 31); // start in the middle of the page? + vm_address_t last = 0x0; +#else + vm_address_t first = 0xffc00000; + vm_address_t last = 0xfffe0000; +#endif + + vm_address_t page = first; + int allocated = 0; + vm_map_t task_self = mach_task_self(); + + while( !err && !allocated && page != last ) { + + err = vm_allocate( task_self, &page, pageSize, 0 ); + if( err == err_none ) + allocated = 1; + else if( err == KERN_NO_SPACE ) { +#if defined(__x86_64__) + page -= pageSize; +#else + page += pageSize; +#endif + err = err_none; + } + } + if( allocated ) + *island = (BranchIsland*) page; + else if( !allocated && !err ) + err = KERN_NO_SPACE; + } + } else { + void *block = malloc( sizeof( BranchIsland ) ); + if( block ) + *island = block; + else + err = KERN_NO_SPACE; + } + if( !err ) + (**island).allocatedHigh = allocateHigh; + + return err; +} + +/***************************************************************************//** + Implementation: Deallocates memory for a branch island. + + @param island -> The island to deallocate. + @result <- mach_error_t + + ***************************************************************************/ + + mach_error_t +freeBranchIsland( + BranchIsland *island ) +{ + assert( island ); + assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] ); + assert( island->allocatedHigh ); + + mach_error_t err = err_none; + + if( island->allocatedHigh ) { + vm_size_t pageSize; + err = host_page_size( mach_host_self(), &pageSize ); + if( !err ) { + assert( sizeof( BranchIsland ) <= pageSize ); + err = vm_deallocate( + mach_task_self(), + (vm_address_t) island, pageSize ); + } + } else { + free( island ); + } + + return err; +} + +/***************************************************************************//** + Implementation: Sets the branch island's target, with an optional + instruction. + + @param island -> The branch island to insert target into. + @param branchTo -> The address of the target. + @param instruction -> Optional instruction to execute prior to branch. Set + to zero for nop. + @result <- mach_error_t + + ***************************************************************************/ +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Fill in the address. + ((short*)island->instructions)[kAddressLo] = ((long) branchTo) & 0x0000FFFF; + ((short*)island->instructions)[kAddressHi] + = (((long) branchTo) >> 16) & 0x0000FFFF; + + // Fill in the (optional) instuction. + if( instruction != 0 ) { + ((short*)island->instructions)[kInstructionLo] + = instruction & 0x0000FFFF; + ((short*)island->instructions)[kInstructionHi] + = (instruction >> 16) & 0x0000FFFF; + } + + //MakeDataExecutable( island->instructions, sizeof( kIslandTemplate ) ); + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + +#if defined(__i386__) + mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // copy original instructions + if (instructions) { + bcopy (instructions, island->instructions + kInstructions, kOriginalInstructionsSize); + } + + // Fill in the address. + int32_t addressOffset = (char *)branchTo - (island->instructions + kJumpAddress + 4); + *((int32_t *)(island->instructions + kJumpAddress)) = addressOffset; + + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + return err_none; +} + +#elif defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Copy original instructions. + if (instructions) { + bcopy (instructions, island->instructions, kOriginalInstructionsSize); + } + + // Fill in the address. + *((uint64_t *)(island->instructions + kJumpAddress)) = (uint64_t)branchTo; + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + + +#if defined(__i386__) || defined(__x86_64__) +// simplistic instruction matching +typedef struct { + unsigned int length; // max 15 + unsigned char mask[15]; // sequence of bytes in memory order + unsigned char constraint[15]; // sequence of bytes in memory order +} AsmInstructionMatch; + +#if defined(__i386__) +static AsmInstructionMatch possibleInstructions[] = { + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x5, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x55, 0x89, 0xe5, 0xc9, 0xc3} }, // push %esp; mov %esp,%ebp; leave; ret + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xF8}, {0x50} }, // push %reg + { 0x2, {0xFF, 0xFF}, {0x89, 0xE5} }, // mov %esp,%ebp + { 0x3, {0xFF, 0xFF, 0xFF}, {0x89, 0x1C, 0x24} }, // mov %ebx,(%esp) + { 0x3, {0xFF, 0xFF, 0x00}, {0x83, 0xEC, 0x00} }, // sub 0x??, %esp + { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x81, 0xEC, 0x00, 0x00, 0x00, 0x00} }, // sub 0x??, %esp with 32bit immediate + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x3, {0xFF, 0x4F, 0x00}, {0x8B, 0x45, 0x00} }, // mov $imm(%ebp), %reg + { 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg + { 0x3, {0xFF, 0xCF, 0x00}, {0x8B, 0x4D, 0x00} }, // mov $imm(%rpb), %reg + { 0x3, {0xFF, 0x4F, 0x00}, {0x8A, 0x4D, 0x00} }, // mov $imm(%ebp), %cl + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx + { 0x4, {0xFF, 0x00, 0x00, 0x00}, {0x8B, 0x00, 0x00, 0x00} }, // mov r16,r/m16 or r32,r/m32 + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB9, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %ecx + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x66, 0x0F, 0xEF, 0x00} }, // pxor xmm2/128, xmm1 + { 0x2, {0xFF, 0xFF}, {0xDB, 0xE3} }, // fninit + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE8, 0x00, 0x00, 0x00, 0x00} }, // call $imm + { 0x0 } +}; +#elif defined(__x86_64__) +// TODO(glider): disassembling the "0x48, 0x89" sequences is trickier than it's done below. +// If it stops working, refer to http://ref.x86asm.net/geek.html#modrm_byte_32_64 to do it +// more accurately. +// Note: 0x48 is in fact the REX.W prefix, but it might be wrong to treat it as a separate +// instruction. +static AsmInstructionMatch possibleInstructions[] = { + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xF8}, {0x50} }, // push %rX + { 0x1, {0xFF}, {0x65} }, // GS prefix + { 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x89, 0xE5} }, // mov %rsp,%rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xEC, 0x00} }, // sub 0x??, %rsp + { 0x4, {0xFB, 0xFF, 0x07, 0x00}, {0x48, 0x89, 0x05, 0x00} }, // move onto rbp + { 0x3, {0xFB, 0xFF, 0x00}, {0x48, 0x89, 0x00} }, // mov %reg, %reg + { 0x3, {0xFB, 0xFF, 0x00}, {0x49, 0x89, 0x00} }, // mov %reg, %reg (REX.WB) + { 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX + { 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX + { 0x2, {0xFF, 0x00}, {0x77, 0x00} }, // ja $i8 + { 0x2, {0xFF, 0x00}, {0x74, 0x00} }, // je $i8 + { 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg + { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0x25, 0x00, 0x00, 0x00, 0x00} }, // and $imm, %eax + + { 0x8, {0xFF, 0xFF, 0xCF, 0xFF, 0x00, 0x00, 0x00, 0x00}, + {0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00}, }, // mov $imm, %{rax,rdx,rsp,rsi} + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xFA, 0x00}, }, // cmp $i8, %rdx + { 0x4, {0xFF, 0xFF, 0x00, 0x00}, {0x83, 0x7f, 0x00, 0x00}, }, // cmpl $imm, $imm(%rdi) + { 0xa, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %rax + { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, + {0x81, 0xE6, 0x00, 0x00, 0x00, 0x00} }, // and $imm, %esi + { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, + {0xFF, 0x25, 0x00, 0x00, 0x00, 0x00} }, // jmpq *(%rip) + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x66, 0x0F, 0xEF, 0x00} }, // pxor xmm2/128, xmm1 + { 0x2, {0xFF, 0x00}, {0x89, 0x00} }, // mov r/m32,r32 or r/m16,r16 + { 0x3, {0xFF, 0xFF, 0xFF}, {0x49, 0x89, 0xF8} }, // mov %rdi,%r8 + { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) + { 0x2, {0xFF, 0xFF}, {0xDB, 0xE3} }, // fninit + { 0x0 } +}; +#endif + +static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* instruction) +{ + Boolean match = true; + + size_t i; + assert(instruction); +#ifdef DEBUG_DISASM + fprintf(stderr, "Matching: "); +#endif + for (i=0; i<instruction->length; i++) { + unsigned char mask = instruction->mask[i]; + unsigned char constraint = instruction->constraint[i]; + unsigned char codeValue = code[i]; +#ifdef DEBUG_DISASM + fprintf(stderr, "%x ", (unsigned)codeValue); +#endif + match = ((codeValue & mask) == constraint); + if (!match) break; + } +#ifdef DEBUG_DISASM + if (match) { + fprintf(stderr, " OK\n"); + } else { + fprintf(stderr, " FAIL\n"); + } +#endif + return match; +} + +#if defined(__i386__) || defined(__x86_64__) + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ) +{ + Boolean allInstructionsKnown = true; + int totalEaten = 0; + unsigned char* ptr = code; + int remainsToEat = 5; // a JMP instruction takes 5 bytes + int instructionIndex = 0; + + if (howManyEaten) *howManyEaten = 0; + if (originalInstructionCount) *originalInstructionCount = 0; + while (remainsToEat > 0) { + Boolean curInstructionKnown = false; + + // See if instruction matches one we know + AsmInstructionMatch* curInstr = possibleInstructions; + do { + if ((curInstructionKnown = codeMatchesInstruction(ptr, curInstr))) break; + curInstr++; + } while (curInstr->length > 0); + + // if all instruction matches failed, we don't know current instruction then, stop here + if (!curInstructionKnown) { + allInstructionsKnown = false; + fprintf(stderr, "mach_override: some instructions unknown! Need to update mach_override.c\n"); + break; + } + + // At this point, we've matched curInstr + int eaten = curInstr->length; + ptr += eaten; + remainsToEat -= eaten; + totalEaten += eaten; + + if (originalInstructionSizes) originalInstructionSizes[instructionIndex] = eaten; + instructionIndex += 1; + if (originalInstructionCount) *originalInstructionCount = instructionIndex; + } + + + if (howManyEaten) *howManyEaten = totalEaten; + + if (originalInstructions) { + Boolean enoughSpaceForOriginalInstructions = (totalEaten < kOriginalInstructionsSize); + + if (enoughSpaceForOriginalInstructions) { + memset(originalInstructions, 0x90 /* NOP */, kOriginalInstructionsSize); // fill instructions with NOP + bcopy(code, originalInstructions, totalEaten); + } else { +#ifdef DEBUG_DISASM + fprintf(stderr, "Not enough space in island to store original instructions. Adapt the island definition and kOriginalInstructionsSize\n"); +#endif + return false; + } + } + + if (allInstructionsKnown) { + // save last 3 bytes of first 64bits of codre we'll replace + uint64_t currentFirst64BitsOfCode = *((uint64_t *)code); + currentFirst64BitsOfCode = OSSwapInt64(currentFirst64BitsOfCode); // back to memory representation + currentFirst64BitsOfCode &= 0x0000000000FFFFFFLL; + + // keep only last 3 instructions bytes, first 5 will be replaced by JMP instr + *newInstruction &= 0xFFFFFFFFFF000000LL; // clear last 3 bytes + *newInstruction |= (currentFirst64BitsOfCode & 0x0000000000FFFFFFLL); // set last 3 bytes + } + + return allInstructionsKnown; +} + + static void +fixupInstructions( + void *originalFunction, + void *escapeIsland, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ) +{ + int index; + for (index = 0;index < instructionCount;index += 1) + { + if ((*(uint8_t*)instructionsToFix == 0xE9) || // 32-bit jump relative + (*(uint8_t*)instructionsToFix == 0xE8)) // 32-bit call relative + { + uint32_t offset = (uintptr_t)originalFunction - (uintptr_t)escapeIsland; + uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1); + *jumpOffsetPtr += offset; + } + + + originalFunction = (void*)((uintptr_t)originalFunction + instructionSizes[index]); + escapeIsland = (void*)((uintptr_t)escapeIsland + instructionSizes[index]); + instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]); + } +} +#endif + +#if defined(__i386__) +__asm( + ".text;" + ".align 2, 0x90;" + "_atomic_mov64:;" + " pushl %ebp;" + " movl %esp, %ebp;" + " pushl %esi;" + " pushl %ebx;" + " pushl %ecx;" + " pushl %eax;" + " pushl %edx;" + + // atomic push of value to an address + // we use cmpxchg8b, which compares content of an address with + // edx:eax. If they are equal, it atomically puts 64bit value + // ecx:ebx in address. + // We thus put contents of address in edx:eax to force ecx:ebx + // in address + " mov 8(%ebp), %esi;" // esi contains target address + " mov 12(%ebp), %ebx;" + " mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address + " mov (%esi), %eax;" + " mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address + " lock; cmpxchg8b (%esi);" // atomic move. + + // restore registers + " popl %edx;" + " popl %eax;" + " popl %ecx;" + " popl %ebx;" + " popl %esi;" + " popl %ebp;" + " ret" +); +#elif defined(__x86_64__) +void atomic_mov64( + uint64_t *targetAddress, + uint64_t value ) +{ + *targetAddress = value; +} +#endif +#endif +#endif // __APPLE__ diff --git a/lib/asan/mach_override/mach_override.h b/lib/asan/mach_override/mach_override.h new file mode 100644 index 000000000000..dcccbcd8732d --- /dev/null +++ b/lib/asan/mach_override/mach_override.h @@ -0,0 +1,127 @@ +/******************************************************************************* + mach_override.h + Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + Some rights reserved: <http://opensource.org/licenses/mit-license.php> + + ***************************************************************************/ + +/***************************************************************************//** + @mainpage mach_override + @author Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + + This package, coded in C to the Mach API, allows you to override ("patch") + program- and system-supplied functions at runtime. You can fully replace + functions with your implementations, or merely head- or tail-patch the + original implementations. + + Use it by #include'ing mach_override.h from your .c, .m or .mm file(s). + + @todo Discontinue use of Carbon's MakeDataExecutable() and + CompareAndSwap() calls and start using the Mach equivalents, if they + exist. If they don't, write them and roll them in. That way, this + code will be pure Mach, which will make it easier to use everywhere. + Update: MakeDataExecutable() has been replaced by + msync(MS_INVALIDATE). There is an OSCompareAndSwap in libkern, but + I'm currently unsure if I can link against it. May have to roll in + my own version... + @todo Stop using an entire 4K high-allocated VM page per 28-byte escape + branch island. Done right, this will dramatically speed up escape + island allocations when they number over 250. Then again, if you're + overriding more than 250 functions, maybe speed isn't your main + concern... + @todo Add detection of: b, bl, bla, bc, bcl, bcla, bcctrl, bclrl + first-instructions. Initially, we should refuse to override + functions beginning with these instructions. Eventually, we should + dynamically rewrite them to make them position-independent. + @todo Write mach_unoverride(), which would remove an override placed on a + function. Must be multiple-override aware, which means an almost + complete rewrite under the covers, because the target address can't + be spread across two load instructions like it is now since it will + need to be atomically updatable. + @todo Add non-rentry variants of overrides to test_mach_override. + + ***************************************************************************/ + +#ifdef __APPLE__ + +#ifndef _mach_override_ +#define _mach_override_ + +#include <sys/types.h> +#include <mach/error.h> + +#ifdef __cplusplus + extern "C" { +#endif + +/** + Returned if the function to be overrided begins with a 'mfctr' instruction. +*/ +#define err_cannot_override (err_local|1) + +/************************************************************************************//** + Dynamically overrides the function implementation referenced by + originalFunctionAddress with the implentation pointed to by overrideFunctionAddress. + Optionally returns a pointer to a "reentry island" which, if jumped to, will resume + the original implementation. + + @param originalFunctionAddress -> Required address of the function to + override (with overrideFunctionAddress). + @param overrideFunctionAddress -> Required address to the overriding + function. + @param originalFunctionReentryIsland <- Optional pointer to pointer to the + reentry island. Can be NULL. + @result <- err_cannot_override if the original + function's implementation begins with + the 'mfctr' instruction. + + ************************************************************************************/ + +// We're prefixing mach_override_ptr() with "__asan_" to avoid name conflicts with other +// mach_override_ptr() implementations that may appear in the client program. + mach_error_t +__asan_mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ); + +/************************************************************************************//** + + + ************************************************************************************/ + +#ifdef __cplusplus + +#define MACH_OVERRIDE( ORIGINAL_FUNCTION_RETURN_TYPE, ORIGINAL_FUNCTION_NAME, ORIGINAL_FUNCTION_ARGS, ERR ) \ + { \ + static ORIGINAL_FUNCTION_RETURN_TYPE (*ORIGINAL_FUNCTION_NAME##_reenter)ORIGINAL_FUNCTION_ARGS; \ + static bool ORIGINAL_FUNCTION_NAME##_overriden = false; \ + class mach_override_class__##ORIGINAL_FUNCTION_NAME { \ + public: \ + static kern_return_t override(void *originalFunctionPtr) { \ + kern_return_t result = err_none; \ + if (!ORIGINAL_FUNCTION_NAME##_overriden) { \ + ORIGINAL_FUNCTION_NAME##_overriden = true; \ + result = mach_override_ptr( (void*)originalFunctionPtr, \ + (void*)mach_override_class__##ORIGINAL_FUNCTION_NAME::replacement, \ + (void**)&ORIGINAL_FUNCTION_NAME##_reenter ); \ + } \ + return result; \ + } \ + static ORIGINAL_FUNCTION_RETURN_TYPE replacement ORIGINAL_FUNCTION_ARGS { + +#define END_MACH_OVERRIDE( ORIGINAL_FUNCTION_NAME ) \ + } \ + }; \ + \ + err = mach_override_class__##ORIGINAL_FUNCTION_NAME::override((void*)ORIGINAL_FUNCTION_NAME); \ + } + +#endif + +#ifdef __cplusplus + } +#endif +#endif // _mach_override_ + +#endif // __APPLE__ diff --git a/lib/asan/scripts/asan_symbolize.py b/lib/asan/scripts/asan_symbolize.py new file mode 100755 index 000000000000..80b592725161 --- /dev/null +++ b/lib/asan/scripts/asan_symbolize.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +#===- lib/asan/scripts/asan_symbolize.py -----------------------------------===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# +import os +import re +import sys +import string +import subprocess + +pipes = {} + +def patch_address(frameno, addr_s): + ''' Subtracts 1 or 2 from the top frame's address. + Top frame is normally the return address from asan_report* + call, which is not expected to return at all. Because of that, this + address often belongs to the next source code line, or even to a different + function. ''' + if frameno == '0': + addr = int(addr_s, 16) + if os.uname()[4].startswith('arm'): + # Cancel the Thumb bit + addr = addr & (~1) + addr -= 1 + return hex(addr) + return addr_s + +# TODO(glider): need some refactoring here +def symbolize_addr2line(line): + #0 0x7f6e35cf2e45 (/blah/foo.so+0x11fe45) + match = re.match('^( *#([0-9]+) *0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)', line) + if match: + frameno = match.group(2) + binary = match.group(3) + addr = match.group(4) + addr = patch_address(frameno, addr) + if not pipes.has_key(binary): + pipes[binary] = subprocess.Popen(["addr2line", "-f", "-e", binary], + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + p = pipes[binary] + try: + print >>p.stdin, addr + function_name = p.stdout.readline().rstrip() + file_name = p.stdout.readline().rstrip() + except: + function_name = "" + file_name = "" + for path_to_cut in sys.argv[1:]: + file_name = re.sub(".*" + path_to_cut, "", file_name) + file_name = re.sub(".*asan_[a-z_]*.cc:[0-9]*", "_asan_rtl_", file_name) + file_name = re.sub(".*crtstuff.c:0", "???:0", file_name) + + print match.group(1), "in", function_name, file_name + else: + print line.rstrip() + +def symbolize_atos(line): + #0 0x7f6e35cf2e45 (/blah/foo.so+0x11fe45) + match = re.match('^( *#([0-9]+) *)(0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)', line) + if match: + #print line + prefix = match.group(1) + frameno = match.group(2) + addr = match.group(3) + binary = match.group(4) + offset = match.group(5) + addr = patch_address(frameno, addr) + load_addr = int(addr, 16) - int(offset, 16) + if not pipes.has_key(binary): + #print "atos -o %s -l %s" % (binary, hex(load_addr)) + pipes[binary] = subprocess.Popen(["atos", "-o", binary], + stdin=subprocess.PIPE, stdout=subprocess.PIPE,) + p = pipes[binary] + # TODO(glider): how to tell if the address is absolute? + if ".app/" in binary and not ".framework" in binary: + print >>p.stdin, "%s" % addr + else: + print >>p.stdin, "%s" % offset + # TODO(glider): it's more efficient to make a batch atos run for each binary. + p.stdin.close() + atos_line = p.stdout.readline().rstrip() + del pipes[binary] + + print "%s%s in %s" % (prefix, addr, atos_line) + else: + print line.rstrip() + +system = os.uname()[0] +if system in ['Linux', 'Darwin']: + for line in sys.stdin: + if system == 'Linux': + symbolize_addr2line(line) + elif system == 'Darwin': + symbolize_atos(line) +else: + print 'Unknown system: ', system diff --git a/lib/asan/sysinfo/LICENSE.TXT b/lib/asan/sysinfo/LICENSE.TXT new file mode 100644 index 000000000000..b519af5aedd3 --- /dev/null +++ b/lib/asan/sysinfo/LICENSE.TXT @@ -0,0 +1,29 @@ +Copyright (c) 2005, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/lib/asan/sysinfo/Makefile.mk b/lib/asan/sysinfo/Makefile.mk new file mode 100644 index 000000000000..bc4a2ffdc982 --- /dev/null +++ b/lib/asan/sysinfo/Makefile.mk @@ -0,0 +1,22 @@ +#===- lib/asan/sysinfo/Makefile.mk -------------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +ModuleName := asan +SubDirs := + +Sources := $(foreach file,$(wildcard $(Dir)/*.cc),$(notdir $(file))) +ObjNames := $(Sources:%.c=%.o) + +Implementation := Generic + +# FIXME: use automatic dependencies? +Dependencies := $(wildcard $(Dir)/*.h) + +# Define a convenience variable for all the asan functions. +AsanFunctions += $(Sources:%.cc=%) diff --git a/lib/asan/sysinfo/basictypes.h b/lib/asan/sysinfo/basictypes.h new file mode 100644 index 000000000000..ac21f8c29833 --- /dev/null +++ b/lib/asan/sysinfo/basictypes.h @@ -0,0 +1,321 @@ +// Copyright (c) 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef _BASICTYPES_H_ +#define _BASICTYPES_H_ + +#include <inttypes.h> // uint16_t might be here; PRId64 too. +#include <stdint.h> // to get uint16_t (ISO naming madness) +#include <sys/types.h> // our last best hope for uint16_t + +// Standard typedefs +// All Google code is compiled with -funsigned-char to make "char" +// unsigned. Google code therefore doesn't need a "uchar" type. +// TODO(csilvers): how do we make sure unsigned-char works on non-gcc systems? +typedef signed char schar; +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef int64_t int64; + +// NOTE: unsigned types are DANGEROUS in loops and other arithmetical +// places. Use the signed types unless your variable represents a bit +// pattern (eg a hash value) or you really need the extra bit. Do NOT +// use 'unsigned' to express "this value should always be positive"; +// use assertions for this. + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; + +const uint16 kuint16max = ( (uint16) 0xFFFF); +const uint32 kuint32max = ( (uint32) 0xFFFFFFFF); +const uint64 kuint64max = ( (((uint64) kuint32max) << 32) | kuint32max ); + +const int8 kint8max = ( ( int8) 0x7F); +const int16 kint16max = ( ( int16) 0x7FFF); +const int32 kint32max = ( ( int32) 0x7FFFFFFF); +const int64 kint64max = ( ((( int64) kint32max) << 32) | kuint32max ); + +const int8 kint8min = ( ( int8) 0x80); +const int16 kint16min = ( ( int16) 0x8000); +const int32 kint32min = ( ( int32) 0x80000000); +const int64 kint64min = ( ((( int64) kint32min) << 32) | 0 ); + +// Define the "portable" printf and scanf macros, if they're not +// already there (via the inttypes.h we #included above, hopefully). +// Mostly it's old systems that don't support inttypes.h, so we assume +// they're 32 bit. +#ifndef PRIx64 +#define PRIx64 "llx" +#endif +#ifndef SCNx64 +#define SCNx64 "llx" +#endif +#ifndef PRId64 +#define PRId64 "lld" +#endif +#ifndef SCNd64 +#define SCNd64 "lld" +#endif +#ifndef PRIu64 +#define PRIu64 "llu" +#endif +#ifndef PRIxPTR +#define PRIxPTR "lx" +#endif + +// Also allow for printing of a pthread_t. +#define GPRIuPTHREAD "lu" +#define GPRIxPTHREAD "lx" +#if defined(__CYGWIN__) || defined(__CYGWIN32__) || defined(__APPLE__) || defined(__FreeBSD__) +#define PRINTABLE_PTHREAD(pthreadt) reinterpret_cast<uintptr_t>(pthreadt) +#else +#define PRINTABLE_PTHREAD(pthreadt) pthreadt +#endif + +// A macro to disallow the evil copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// An alternate name that leaves out the moral judgment... :-) +#define DISALLOW_COPY_AND_ASSIGN(TypeName) DISALLOW_EVIL_CONSTRUCTORS(TypeName) + +// The COMPILE_ASSERT macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// COMPILE_ASSERT(sizeof(num_content_type_names) == sizeof(int), +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. +// +// Implementation details of COMPILE_ASSERT: +// +// - COMPILE_ASSERT works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outter parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert<bool(expr)> +// +// instead, these compilers will refuse to compile +// +// COMPILE_ASSERT(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + +template <bool> +struct CompileAssert { +}; + +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] + +#define arraysize(a) (sizeof(a) / sizeof(*(a))) + +#define OFFSETOF_MEMBER(strct, field) \ + (reinterpret_cast<char*>(&reinterpret_cast<strct*>(16)->field) - \ + reinterpret_cast<char*>(16)) + +#ifdef HAVE___ATTRIBUTE__ +# define ATTRIBUTE_WEAK __attribute__((weak)) +# define ATTRIBUTE_NOINLINE __attribute__((noinline)) +#else +# define ATTRIBUTE_WEAK +# define ATTRIBUTE_NOINLINE +#endif + +// Section attributes are supported for both ELF and Mach-O, but in +// very different ways. Here's the API we provide: +// 1) ATTRIBUTE_SECTION: put this with the declaration of all functions +// you want to be in the same linker section +// 2) DEFINE_ATTRIBUTE_SECTION_VARS: must be called once per unique +// name. You want to make sure this is executed before any +// DECLARE_ATTRIBUTE_SECTION_VARS; the easiest way is to put them +// in the same .cc file. Put this call at the global level. +// 3) INIT_ATTRIBUTE_SECTION_VARS: you can scatter calls to this in +// multiple places to help ensure execution before any +// DECLARE_ATTRIBUTE_SECTION_VARS. You must have at least one +// DEFINE, but you can have many INITs. Put each in its own scope. +// 4) DECLARE_ATTRIBUTE_SECTION_VARS: must be called before using +// ATTRIBUTE_SECTION_START or ATTRIBUTE_SECTION_STOP on a name. +// Put this call at the global level. +// 5) ATTRIBUTE_SECTION_START/ATTRIBUTE_SECTION_STOP: call this to say +// where in memory a given section is. All functions declared with +// ATTRIBUTE_SECTION are guaranteed to be between START and STOP. + +#if defined(HAVE___ATTRIBUTE__) && defined(__ELF__) +# define ATTRIBUTE_SECTION(name) __attribute__ ((section (#name))) + + // Weak section declaration to be used as a global declaration + // for ATTRIBUTE_SECTION_START|STOP(name) to compile and link + // even without functions with ATTRIBUTE_SECTION(name). +# define DECLARE_ATTRIBUTE_SECTION_VARS(name) \ + extern char __start_##name[] ATTRIBUTE_WEAK; \ + extern char __stop_##name[] ATTRIBUTE_WEAK +# define INIT_ATTRIBUTE_SECTION_VARS(name) // no-op for ELF +# define DEFINE_ATTRIBUTE_SECTION_VARS(name) // no-op for ELF + + // Return void* pointers to start/end of a section of code with functions + // having ATTRIBUTE_SECTION(name), or 0 if no such function exists. + // One must DECLARE_ATTRIBUTE_SECTION(name) for this to compile and link. +# define ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void*>(__start_##name)) +# define ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void*>(__stop_##name)) +# define HAVE_ATTRIBUTE_SECTION_START 1 + +#elif defined(HAVE___ATTRIBUTE__) && defined(__MACH__) +# define ATTRIBUTE_SECTION(name) __attribute__ ((section ("__TEXT, " #name))) + +#include <mach-o/getsect.h> +#include <mach-o/dyld.h> +class AssignAttributeStartEnd { + public: + AssignAttributeStartEnd(const char* name, char** pstart, char** pend) { + // Find out what dynamic library name is defined in + if (_dyld_present()) { + for (int i = _dyld_image_count() - 1; i >= 0; --i) { + const mach_header* hdr = _dyld_get_image_header(i); +#ifdef MH_MAGIC_64 + if (hdr->magic == MH_MAGIC_64) { + uint64_t len; + *pstart = getsectdatafromheader_64((mach_header_64*)hdr, + "__TEXT", name, &len); + if (*pstart) { // NULL if not defined in this dynamic library + *pstart += _dyld_get_image_vmaddr_slide(i); // correct for reloc + *pend = *pstart + len; + return; + } + } +#endif + if (hdr->magic == MH_MAGIC) { + uint32_t len; + *pstart = getsectdatafromheader(hdr, "__TEXT", name, &len); + if (*pstart) { // NULL if not defined in this dynamic library + *pstart += _dyld_get_image_vmaddr_slide(i); // correct for reloc + *pend = *pstart + len; + return; + } + } + } + } + // If we get here, not defined in a dll at all. See if defined statically. + unsigned long len; // don't ask me why this type isn't uint32_t too... + *pstart = getsectdata("__TEXT", name, &len); + *pend = *pstart + len; + } +}; + +#define DECLARE_ATTRIBUTE_SECTION_VARS(name) \ + extern char* __start_##name; \ + extern char* __stop_##name + +#define INIT_ATTRIBUTE_SECTION_VARS(name) \ + DECLARE_ATTRIBUTE_SECTION_VARS(name); \ + static const AssignAttributeStartEnd __assign_##name( \ + #name, &__start_##name, &__stop_##name) + +#define DEFINE_ATTRIBUTE_SECTION_VARS(name) \ + char* __start_##name, *__stop_##name; \ + INIT_ATTRIBUTE_SECTION_VARS(name) + +# define ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void*>(__start_##name)) +# define ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void*>(__stop_##name)) +# define HAVE_ATTRIBUTE_SECTION_START 1 + +#else // not HAVE___ATTRIBUTE__ && __ELF__, nor HAVE___ATTRIBUTE__ && __MACH__ +# define ATTRIBUTE_SECTION(name) +# define DECLARE_ATTRIBUTE_SECTION_VARS(name) +# define INIT_ATTRIBUTE_SECTION_VARS(name) +# define DEFINE_ATTRIBUTE_SECTION_VARS(name) +# define ATTRIBUTE_SECTION_START(name) (reinterpret_cast<void*>(0)) +# define ATTRIBUTE_SECTION_STOP(name) (reinterpret_cast<void*>(0)) + +#endif // HAVE___ATTRIBUTE__ and __ELF__ or __MACH__ + +#if defined(HAVE___ATTRIBUTE__) && (defined(__i386__) || defined(__x86_64__)) +# define CACHELINE_SIZE 64 +# define CACHELINE_ALIGNED __attribute__((aligned(CACHELINE_SIZE))) +#else +# define CACHELINE_ALIGNED +#endif // defined(HAVE___ATTRIBUTE__) && (__i386__ || __x86_64__) + + +// The following enum should be used only as a constructor argument to indicate +// that the variable has static storage class, and that the constructor should +// do nothing to its state. It indicates to the reader that it is legal to +// declare a static nistance of the class, provided the constructor is given +// the base::LINKER_INITIALIZED argument. Normally, it is unsafe to declare a +// static variable that has a constructor or a destructor because invocation +// order is undefined. However, IF the type can be initialized by filling with +// zeroes (which the loader does for static variables), AND the destructor also +// does nothing to the storage, then a constructor declared as +// explicit MyClass(base::LinkerInitialized x) {} +// and invoked as +// static MyClass my_variable_name(base::LINKER_INITIALIZED); +namespace base { +enum LinkerInitialized { LINKER_INITIALIZED }; +} + +#endif // _BASICTYPES_H_ diff --git a/lib/asan/sysinfo/sysinfo.cc b/lib/asan/sysinfo/sysinfo.cc new file mode 100644 index 000000000000..ee0673526402 --- /dev/null +++ b/lib/asan/sysinfo/sysinfo.cc @@ -0,0 +1,617 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <assert.h> +#include <stdlib.h> // for getenv() +#include <stdio.h> // for snprintf(), sscanf() +#include <string.h> // for memmove(), memchr(), etc. +#include <fcntl.h> // for open() +#include <errno.h> // for errno +#include <unistd.h> // for read() +#if defined __MACH__ // Mac OS X, almost certainly +#include <mach-o/dyld.h> // for iterating over dll's in ProcMapsIter +#include <mach-o/loader.h> // for iterating over dll's in ProcMapsIter +#include <sys/types.h> +#include <sys/sysctl.h> // how we figure out numcpu's on OS X +#elif defined __FreeBSD__ +#include <sys/sysctl.h> +#elif defined __sun__ // Solaris +#include <procfs.h> // for, e.g., prmap_t +#elif defined(PLATFORM_WINDOWS) +#include <process.h> // for getpid() (actually, _getpid()) +#include <shlwapi.h> // for SHGetValueA() +#include <tlhelp32.h> // for Module32First() +#endif +#include "sysinfo.h" + +#ifdef PLATFORM_WINDOWS +#ifdef MODULEENTRY32 +// In a change from the usual W-A pattern, there is no A variant of +// MODULEENTRY32. Tlhelp32.h #defines the W variant, but not the A. +// In unicode mode, tlhelp32.h #defines MODULEENTRY32 to be +// MODULEENTRY32W. These #undefs are the only way I see to get back +// access to the original, ascii struct (and related functions). +#undef MODULEENTRY32 +#undef Module32First +#undef Module32Next +#undef PMODULEENTRY32 +#undef LPMODULEENTRY32 +#endif /* MODULEENTRY32 */ +// MinGW doesn't seem to define this, perhaps some windowsen don't either. +#ifndef TH32CS_SNAPMODULE32 +#define TH32CS_SNAPMODULE32 0 +#endif /* TH32CS_SNAPMODULE32 */ +#endif /* PLATFORM_WINDOWS */ + +// Re-run fn until it doesn't cause EINTR. +#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR) + +// open/read/close can set errno, which may be illegal at this +// time, so prefer making the syscalls directly if we can. +#ifdef HAVE_SYS_SYSCALL_H +# include <sys/syscall.h> +# define safeopen(filename, mode) syscall(SYS_open, filename, mode) +# define saferead(fd, buffer, size) syscall(SYS_read, fd, buffer, size) +# define safeclose(fd) syscall(SYS_close, fd) +#else +# define safeopen(filename, mode) open(filename, mode) +# define saferead(fd, buffer, size) read(fd, buffer, size) +# define safeclose(fd) close(fd) +#endif + + +// ---------------------------------------------------------------------- +// HasPosixThreads() +// Return true if we're running POSIX (e.g., NPTL on Linux) +// threads, as opposed to a non-POSIX thread libary. The thing +// that we care about is whether a thread's pid is the same as +// the thread that spawned it. If so, this function returns +// true. +// ---------------------------------------------------------------------- +bool HasPosixThreads() { +#if defined(__linux__) and !defined(ANDROID) +#ifndef _CS_GNU_LIBPTHREAD_VERSION +#define _CS_GNU_LIBPTHREAD_VERSION 3 +#endif + char buf[32]; + // We assume that, if confstr() doesn't know about this name, then + // the same glibc is providing LinuxThreads. + if (confstr(_CS_GNU_LIBPTHREAD_VERSION, buf, sizeof(buf)) == 0) + return false; + return strncmp(buf, "NPTL", 4) == 0; +#elif defined(PLATFORM_WINDOWS) || defined(__CYGWIN__) || defined(__CYGWIN32__) + return false; +#else // other OS + return true; // Assume that everything else has Posix +#endif // else OS_LINUX +} + +// ---------------------------------------------------------------------- + +#define CHECK_LT(x, y) do { assert((x) < (y)); } while (0) + +#if defined __linux__ || defined __FreeBSD__ || defined __sun__ || defined __CYGWIN__ || defined __CYGWIN32__ +static void ConstructFilename(const char* spec, pid_t pid, + char* buf, int buf_size) { + CHECK_LT(snprintf(buf, buf_size, + spec, + static_cast<int>(pid ? pid : getpid())), buf_size); +} +#endif + +// A templatized helper function instantiated for Mach (OS X) only. +// It can handle finding info for both 32 bits and 64 bits. +// Returns true if it successfully handled the hdr, false else. +#ifdef __MACH__ // Mac OS X, almost certainly +template<uint32_t kMagic, uint32_t kLCSegment, + typename MachHeader, typename SegmentCommand> +static bool NextExtMachHelper(const mach_header* hdr, + int current_image, int current_load_cmd, + uint64 *start, uint64 *end, char **flags, + uint64 *offset, int64 *inode, char **filename, + uint64 *file_mapping, uint64 *file_pages, + uint64 *anon_mapping, uint64 *anon_pages, + dev_t *dev) { + static char kDefaultPerms[5] = "r-xp"; + if (hdr->magic != kMagic) + return false; + const char* lc = (const char *)hdr + sizeof(MachHeader); + // TODO(csilvers): make this not-quadradic (increment and hold state) + for (int j = 0; j < current_load_cmd; j++) // advance to *our* load_cmd + lc += ((const load_command *)lc)->cmdsize; + if (((const load_command *)lc)->cmd == kLCSegment) { + const intptr_t dlloff = _dyld_get_image_vmaddr_slide(current_image); + const SegmentCommand* sc = (const SegmentCommand *)lc; + if (start) *start = sc->vmaddr + dlloff; + if (end) *end = sc->vmaddr + sc->vmsize + dlloff; + if (flags) *flags = kDefaultPerms; // can we do better? + if (offset) *offset = sc->fileoff; + if (inode) *inode = 0; + if (filename) + *filename = const_cast<char*>(_dyld_get_image_name(current_image)); + if (file_mapping) *file_mapping = 0; + if (file_pages) *file_pages = 0; // could we use sc->filesize? + if (anon_mapping) *anon_mapping = 0; + if (anon_pages) *anon_pages = 0; + if (dev) *dev = 0; + return true; + } + + return false; +} +#endif + +ProcMapsIterator::ProcMapsIterator(pid_t pid) { + Init(pid, NULL, false); +} + +ProcMapsIterator::ProcMapsIterator(pid_t pid, Buffer *buffer) { + Init(pid, buffer, false); +} + +ProcMapsIterator::ProcMapsIterator(pid_t pid, Buffer *buffer, + bool use_maps_backing) { + Init(pid, buffer, use_maps_backing); +} + +void ProcMapsIterator::Init(pid_t pid, Buffer *buffer, + bool use_maps_backing) { + pid_ = pid; + using_maps_backing_ = use_maps_backing; + dynamic_buffer_ = NULL; + if (!buffer) { + // If the user didn't pass in any buffer storage, allocate it + // now. This is the normal case; the signal handler passes in a + // static buffer. + buffer = dynamic_buffer_ = new Buffer; + } else { + dynamic_buffer_ = NULL; + } + + ibuf_ = buffer->buf_; + + stext_ = etext_ = nextline_ = ibuf_; + ebuf_ = ibuf_ + Buffer::kBufSize - 1; + nextline_ = ibuf_; + +#if defined(__linux__) || defined(__CYGWIN__) || defined(__CYGWIN32__) + if (use_maps_backing) { // don't bother with clever "self" stuff in this case + ConstructFilename("/proc/%d/maps_backing", pid, ibuf_, Buffer::kBufSize); + } else if (pid == 0) { + // We have to kludge a bit to deal with the args ConstructFilename + // expects. The 1 is never used -- it's only impt. that it's not 0. + ConstructFilename("/proc/self/maps", 1, ibuf_, Buffer::kBufSize); + } else { + ConstructFilename("/proc/%d/maps", pid, ibuf_, Buffer::kBufSize); + } + // No error logging since this can be called from the crash dump + // handler at awkward moments. Users should call Valid() before + // using. + NO_INTR(fd_ = open(ibuf_, O_RDONLY)); +#elif defined(__FreeBSD__) + // We don't support maps_backing on freebsd + if (pid == 0) { + ConstructFilename("/proc/curproc/map", 1, ibuf_, Buffer::kBufSize); + } else { + ConstructFilename("/proc/%d/map", pid, ibuf_, Buffer::kBufSize); + } + NO_INTR(fd_ = open(ibuf_, O_RDONLY)); +#elif defined(__sun__) + if (pid == 0) { + ConstructFilename("/proc/self/map", 1, ibuf_, Buffer::kBufSize); + } else { + ConstructFilename("/proc/%d/map", pid, ibuf_, Buffer::kBufSize); + } + NO_INTR(fd_ = open(ibuf_, O_RDONLY)); +#elif defined(__MACH__) + current_image_ = _dyld_image_count(); // count down from the top + current_load_cmd_ = -1; +#elif defined(PLATFORM_WINDOWS) + snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | + TH32CS_SNAPMODULE32, + GetCurrentProcessId()); + memset(&module_, 0, sizeof(module_)); +#else + fd_ = -1; // so Valid() is always false +#endif + +} + +ProcMapsIterator::~ProcMapsIterator() { +#if defined(PLATFORM_WINDOWS) + if (snapshot_ != INVALID_HANDLE_VALUE) CloseHandle(snapshot_); +#elif defined(__MACH__) + // no cleanup necessary! +#else + if (fd_ >= 0) NO_INTR(close(fd_)); +#endif + delete dynamic_buffer_; +} + +bool ProcMapsIterator::Valid() const { +#if defined(PLATFORM_WINDOWS) + return snapshot_ != INVALID_HANDLE_VALUE; +#elif defined(__MACH__) + return 1; +#else + return fd_ != -1; +#endif +} + +bool ProcMapsIterator::Next(uint64 *start, uint64 *end, char **flags, + uint64 *offset, int64 *inode, char **filename) { + return NextExt(start, end, flags, offset, inode, filename, NULL, NULL, + NULL, NULL, NULL); +} + +// This has too many arguments. It should really be building +// a map object and returning it. The problem is that this is called +// when the memory allocator state is undefined, hence the arguments. +bool ProcMapsIterator::NextExt(uint64 *start, uint64 *end, char **flags, + uint64 *offset, int64 *inode, char **filename, + uint64 *file_mapping, uint64 *file_pages, + uint64 *anon_mapping, uint64 *anon_pages, + dev_t *dev) { + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__CYGWIN__) || defined(__CYGWIN32__) + do { + // Advance to the start of the next line + stext_ = nextline_; + + // See if we have a complete line in the buffer already + nextline_ = static_cast<char *>(memchr (stext_, '\n', etext_ - stext_)); + if (!nextline_) { + // Shift/fill the buffer so we do have a line + int count = etext_ - stext_; + + // Move the current text to the start of the buffer + memmove(ibuf_, stext_, count); + stext_ = ibuf_; + etext_ = ibuf_ + count; + + int nread = 0; // fill up buffer with text + while (etext_ < ebuf_) { + NO_INTR(nread = read(fd_, etext_, ebuf_ - etext_)); + if (nread > 0) + etext_ += nread; + else + break; + } + + // Zero out remaining characters in buffer at EOF to avoid returning + // garbage from subsequent calls. + if (etext_ != ebuf_ && nread == 0) { + memset(etext_, 0, ebuf_ - etext_); + } + *etext_ = '\n'; // sentinel; safe because ibuf extends 1 char beyond ebuf + nextline_ = static_cast<char *>(memchr (stext_, '\n', etext_ + 1 - stext_)); + } + *nextline_ = 0; // turn newline into nul + nextline_ += ((nextline_ < etext_)? 1 : 0); // skip nul if not end of text + // stext_ now points at a nul-terminated line + uint64 tmpstart, tmpend, tmpoffset; + int64 tmpinode; + int major, minor; + unsigned filename_offset = 0; +#if defined(__linux__) + // for now, assume all linuxes have the same format + if (sscanf(stext_, "%"SCNx64"-%"SCNx64" %4s %"SCNx64" %x:%x %"SCNd64" %n", + (unsigned long long *)(start ? start : &tmpstart), + (unsigned long long *)(end ? end : &tmpend), + flags_, + (unsigned long long *)(offset ? offset : &tmpoffset), + &major, &minor, + (unsigned long long *)(inode ? inode : &tmpinode), + &filename_offset) != 7) continue; +#elif defined(__CYGWIN__) || defined(__CYGWIN32__) + // cygwin is like linux, except the third field is the "entry point" + // rather than the offset (see format_process_maps at + // http://cygwin.com/cgi-bin/cvsweb.cgi/src/winsup/cygwin/fhandler_process.cc?rev=1.89&content-type=text/x-cvsweb-markup&cvsroot=src + // Offset is always be 0 on cygwin: cygwin implements an mmap + // by loading the whole file and then calling NtMapViewOfSection. + // Cygwin also seems to set its flags kinda randomly; use windows default. + char tmpflags[5]; + if (offset) + *offset = 0; + strcpy(flags_, "r-xp"); + if (sscanf(stext_, "%llx-%llx %4s %llx %x:%x %lld %n", + start ? start : &tmpstart, + end ? end : &tmpend, + tmpflags, + &tmpoffset, + &major, &minor, + inode ? inode : &tmpinode, &filename_offset) != 7) continue; +#elif defined(__FreeBSD__) + // For the format, see http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/fs/procfs/procfs_map.c?rev=1.31&content-type=text/x-cvsweb-markup + tmpstart = tmpend = tmpoffset = 0; + tmpinode = 0; + major = minor = 0; // can't get this info in freebsd + if (inode) + *inode = 0; // nor this + if (offset) + *offset = 0; // seems like this should be in there, but maybe not + // start end resident privateresident obj(?) prot refcnt shadowcnt + // flags copy_on_write needs_copy type filename: + // 0x8048000 0x804a000 2 0 0xc104ce70 r-x 1 0 0x0 COW NC vnode /bin/cat + if (sscanf(stext_, "0x%"SCNx64" 0x%"SCNx64" %*d %*d %*p %3s %*d %*d 0x%*x %*s %*s %*s %n", + start ? start : &tmpstart, + end ? end : &tmpend, + flags_, + &filename_offset) != 3) continue; +#endif + + // Depending on the Linux kernel being used, there may or may not be a space + // after the inode if there is no filename. sscanf will in such situations + // nondeterministically either fill in filename_offset or not (the results + // differ on multiple calls in the same run even with identical arguments). + // We don't want to wander off somewhere beyond the end of the string. + size_t stext_length = strlen(stext_); + if (filename_offset == 0 || filename_offset > stext_length) + filename_offset = stext_length; + + // We found an entry + if (flags) *flags = flags_; + if (filename) *filename = stext_ + filename_offset; + if (dev) *dev = minor | (major << 8); + + if (using_maps_backing_) { + // Extract and parse physical page backing info. + char *backing_ptr = stext_ + filename_offset + + strlen(stext_+filename_offset); + + // find the second '(' + int paren_count = 0; + while (--backing_ptr > stext_) { + if (*backing_ptr == '(') { + ++paren_count; + if (paren_count >= 2) { + uint64 tmp_file_mapping; + uint64 tmp_file_pages; + uint64 tmp_anon_mapping; + uint64 tmp_anon_pages; + + sscanf(backing_ptr+1, "F %"SCNx64" %"SCNd64") (A %"SCNx64" %"SCNd64")", + (unsigned long long *)(file_mapping ? + file_mapping : &tmp_file_mapping), + (unsigned long long *)(file_pages ? + file_pages : &tmp_file_pages), + (unsigned long long *)(anon_mapping + ? anon_mapping : &tmp_anon_mapping), + (unsigned long long *)(anon_pages + ? anon_pages : &tmp_anon_pages)); + // null terminate the file name (there is a space + // before the first (. + backing_ptr[-1] = 0; + break; + } + } + } + } + + return true; + } while (etext_ > ibuf_); +#elif defined(__sun__) + // This is based on MA_READ == 4, MA_WRITE == 2, MA_EXEC == 1 + static char kPerms[8][4] = { "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" }; + COMPILE_ASSERT(MA_READ == 4, solaris_ma_read_must_equal_4); + COMPILE_ASSERT(MA_WRITE == 2, solaris_ma_write_must_equal_2); + COMPILE_ASSERT(MA_EXEC == 1, solaris_ma_exec_must_equal_1); + Buffer object_path; + int nread = 0; // fill up buffer with text + NO_INTR(nread = read(fd_, ibuf_, sizeof(prmap_t))); + if (nread == sizeof(prmap_t)) { + long inode_from_mapname = 0; + prmap_t* mapinfo = reinterpret_cast<prmap_t*>(ibuf_); + // Best-effort attempt to get the inode from the filename. I think the + // two middle ints are major and minor device numbers, but I'm not sure. + sscanf(mapinfo->pr_mapname, "ufs.%*d.%*d.%ld", &inode_from_mapname); + + if (pid_ == 0) { + CHECK_LT(snprintf(object_path.buf_, Buffer::kBufSize, + "/proc/self/path/%s", mapinfo->pr_mapname), + Buffer::kBufSize); + } else { + CHECK_LT(snprintf(object_path.buf_, Buffer::kBufSize, + "/proc/%d/path/%s", + static_cast<int>(pid_), mapinfo->pr_mapname), + Buffer::kBufSize); + } + ssize_t len = readlink(object_path.buf_, current_filename_, PATH_MAX); + CHECK_LT(len, PATH_MAX); + if (len < 0) + len = 0; + current_filename_[len] = '\0'; + + if (start) *start = mapinfo->pr_vaddr; + if (end) *end = mapinfo->pr_vaddr + mapinfo->pr_size; + if (flags) *flags = kPerms[mapinfo->pr_mflags & 7]; + if (offset) *offset = mapinfo->pr_offset; + if (inode) *inode = inode_from_mapname; + if (filename) *filename = current_filename_; + if (file_mapping) *file_mapping = 0; + if (file_pages) *file_pages = 0; + if (anon_mapping) *anon_mapping = 0; + if (anon_pages) *anon_pages = 0; + if (dev) *dev = 0; + return true; + } +#elif defined(__MACH__) + // We return a separate entry for each segment in the DLL. (TODO(csilvers): + // can we do better?) A DLL ("image") has load-commands, some of which + // talk about segment boundaries. + // cf image_for_address from http://svn.digium.com/view/asterisk/team/oej/minivoicemail/dlfcn.c?revision=53912 + for (; current_image_ >= 0; current_image_--) { + const mach_header* hdr = _dyld_get_image_header(current_image_); + if (!hdr) continue; + if (current_load_cmd_ < 0) // set up for this image + current_load_cmd_ = hdr->ncmds; // again, go from the top down + + // We start with the next load command (we've already looked at this one). + for (current_load_cmd_--; current_load_cmd_ >= 0; current_load_cmd_--) { +#ifdef MH_MAGIC_64 + if (NextExtMachHelper<MH_MAGIC_64, LC_SEGMENT_64, + struct mach_header_64, struct segment_command_64>( + hdr, current_image_, current_load_cmd_, + start, end, flags, offset, inode, filename, + file_mapping, file_pages, anon_mapping, + anon_pages, dev)) { + return true; + } +#endif + if (NextExtMachHelper<MH_MAGIC, LC_SEGMENT, + struct mach_header, struct segment_command>( + hdr, current_image_, current_load_cmd_, + start, end, flags, offset, inode, filename, + file_mapping, file_pages, anon_mapping, + anon_pages, dev)) { + return true; + } + } + // If we get here, no more load_cmd's in this image talk about + // segments. Go on to the next image. + } +#elif defined(PLATFORM_WINDOWS) + static char kDefaultPerms[5] = "r-xp"; + BOOL ok; + if (module_.dwSize == 0) { // only possible before first call + module_.dwSize = sizeof(module_); + ok = Module32First(snapshot_, &module_); + } else { + ok = Module32Next(snapshot_, &module_); + } + if (ok) { + uint64 base_addr = reinterpret_cast<DWORD_PTR>(module_.modBaseAddr); + if (start) *start = base_addr; + if (end) *end = base_addr + module_.modBaseSize; + if (flags) *flags = kDefaultPerms; + if (offset) *offset = 0; + if (inode) *inode = 0; + if (filename) *filename = module_.szExePath; + if (file_mapping) *file_mapping = 0; + if (file_pages) *file_pages = 0; + if (anon_mapping) *anon_mapping = 0; + if (anon_pages) *anon_pages = 0; + if (dev) *dev = 0; + return true; + } +#endif + + // We didn't find anything + return false; +} + +int ProcMapsIterator::FormatLine(char* buffer, int bufsize, + uint64 start, uint64 end, const char *flags, + uint64 offset, int64 inode, + const char *filename, dev_t dev) { + // We assume 'flags' looks like 'rwxp' or 'rwx'. + char r = (flags && flags[0] == 'r') ? 'r' : '-'; + char w = (flags && flags[0] && flags[1] == 'w') ? 'w' : '-'; + char x = (flags && flags[0] && flags[1] && flags[2] == 'x') ? 'x' : '-'; + // p always seems set on linux, so we set the default to 'p', not '-' + char p = (flags && flags[0] && flags[1] && flags[2] && flags[3] != 'p') + ? '-' : 'p'; + + const int rc = snprintf(buffer, bufsize, + "%08"PRIx64"-%08"PRIx64" %c%c%c%c %08"PRIx64" %02x:%02x %-11"PRId64" %s\n", + (unsigned long long)start, (unsigned long long)end, r,w,x,p, + (unsigned long long)offset, + static_cast<int>(dev/256), static_cast<int>(dev%256), + (unsigned long long)inode, filename); + return (rc < 0 || rc >= bufsize) ? 0 : rc; +} + +// Helper to add the list of mapped shared libraries to a profile. +// Fill formatted "/proc/self/maps" contents into buffer 'buf' of size 'size' +// and return the actual size occupied in 'buf'. We fill wrote_all to true +// if we successfully wrote all proc lines to buf, false else. +// We do not provision for 0-terminating 'buf'. +int FillProcSelfMaps(char buf[], int size, bool* wrote_all) { + ProcMapsIterator::Buffer iterbuf; + ProcMapsIterator it(0, &iterbuf); // 0 means "current pid" + + uint64 start, end, offset; + int64 inode; + char *flags, *filename; + int bytes_written = 0; + *wrote_all = true; + while (it.Next(&start, &end, &flags, &offset, &inode, &filename)) { + const int line_length = it.FormatLine(buf + bytes_written, + size - bytes_written, + start, end, flags, offset, + inode, filename, 0); + if (line_length == 0) + *wrote_all = false; // failed to write this line out + else + bytes_written += line_length; + + } + return bytes_written; +} + +// Dump the same data as FillProcSelfMaps reads to fd. +// It seems easier to repeat parts of FillProcSelfMaps here than to +// reuse it via a call. +void DumpProcSelfMaps(RawFD fd) { + ProcMapsIterator::Buffer iterbuf; + ProcMapsIterator it(0, &iterbuf); // 0 means "current pid" + + uint64 start, end, offset; + int64 inode; + char *flags, *filename; + ProcMapsIterator::Buffer linebuf; + while (it.Next(&start, &end, &flags, &offset, &inode, &filename)) { + int written = it.FormatLine(linebuf.buf_, sizeof(linebuf.buf_), + start, end, flags, offset, inode, filename, + 0); + RawWrite(fd, linebuf.buf_, written); + } +} + +// Re-run fn until it doesn't cause EINTR. +#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR) + +RawFD RawOpenForWriting(const char* filename) { + return open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0664); +} + +void RawWrite(RawFD fd, const char* buf, size_t len) { + while (len > 0) { + ssize_t r; + NO_INTR(r = write(fd, buf, len)); + if (r <= 0) break; + buf += r; + len -= r; + } +} + +void RawClose(RawFD fd) { + NO_INTR(close(fd)); +} diff --git a/lib/asan/sysinfo/sysinfo.h b/lib/asan/sysinfo/sysinfo.h new file mode 100644 index 000000000000..707687e037ac --- /dev/null +++ b/lib/asan/sysinfo/sysinfo.h @@ -0,0 +1,234 @@ +// Copyright (c) 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// All functions here are thread-hostile due to file caching unless +// commented otherwise. + +#ifndef _SYSINFO_H_ +#define _SYSINFO_H_ + +#include <time.h> +#if (defined(_WIN32) || defined(__MINGW32__)) && (!defined(__CYGWIN__) && !defined(__CYGWIN32__)) +#include <windows.h> // for DWORD +#include <TlHelp32.h> // for CreateToolhelp32Snapshot +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> // for pid_t +#endif +#include <stddef.h> // for size_t +#include <limits.h> // for PATH_MAX +#include "basictypes.h" + +// This getenv function is safe to call before the C runtime is initialized. +// On Windows, it utilizes GetEnvironmentVariable() and on unix it uses +// /proc/self/environ instead calling getenv(). It's intended to be used in +// routines that run before main(), when the state required for getenv() may +// not be set up yet. In particular, errno isn't set up until relatively late +// (after the pthreads library has a chance to make it threadsafe), and +// getenv() doesn't work until then. +// On some platforms, this call will utilize the same, static buffer for +// repeated GetenvBeforeMain() calls. Callers should not expect pointers from +// this routine to be long lived. +// Note that on unix, /proc only has the environment at the time the +// application was started, so this routine ignores setenv() calls/etc. Also +// note it only reads the first 16K of the environment. +extern const char* GetenvBeforeMain(const char* name); + +// This takes as an argument an environment-variable name (like +// CPUPROFILE) whose value is supposed to be a file-path, and sets +// path to that path, and returns true. Non-trivial for surprising +// reasons, as documented in sysinfo.cc. path must have space PATH_MAX. +extern bool GetUniquePathFromEnv(const char* env_name, char* path); + +extern int NumCPUs(); + +// processor cycles per second of each processor. Thread-safe. +extern double CyclesPerSecond(void); + + +// Return true if we're running POSIX (e.g., NPTL on Linux) threads, +// as opposed to a non-POSIX thread libary. The thing that we care +// about is whether a thread's pid is the same as the thread that +// spawned it. If so, this function returns true. +// Thread-safe. +// Note: We consider false negatives to be OK. +bool HasPosixThreads(); + +#ifndef SWIG // SWIG doesn't like struct Buffer and variable arguments. + +// A ProcMapsIterator abstracts access to /proc/maps for a given +// process. Needs to be stack-allocatable and avoid using stdio/malloc +// so it can be used in the google stack dumper, heap-profiler, etc. +// +// On Windows and Mac OS X, this iterator iterates *only* over DLLs +// mapped into this process space. For Linux, FreeBSD, and Solaris, +// it iterates over *all* mapped memory regions, including anonymous +// mmaps. For other O/Ss, it is unlikely to work at all, and Valid() +// will always return false. Also note: this routine only works on +// FreeBSD if procfs is mounted: make sure this is in your /etc/fstab: +// proc /proc procfs rw 0 0 +class ProcMapsIterator { + public: + struct Buffer { +#ifdef __FreeBSD__ + // FreeBSD requires us to read all of the maps file at once, so + // we have to make a buffer that's "always" big enough + static const size_t kBufSize = 102400; +#else // a one-line buffer is good enough + static const size_t kBufSize = PATH_MAX + 1024; +#endif + char buf_[kBufSize]; + }; + + + // Create a new iterator for the specified pid. pid can be 0 for "self". + explicit ProcMapsIterator(pid_t pid); + + // Create an iterator with specified storage (for use in signal + // handler). "buffer" should point to a ProcMapsIterator::Buffer + // buffer can be NULL in which case a bufer will be allocated. + ProcMapsIterator(pid_t pid, Buffer *buffer); + + // Iterate through maps_backing instead of maps if use_maps_backing + // is true. Otherwise the same as above. buffer can be NULL and + // it will allocate a buffer itself. + ProcMapsIterator(pid_t pid, Buffer *buffer, + bool use_maps_backing); + + // Returns true if the iterator successfully initialized; + bool Valid() const; + + // Returns a pointer to the most recently parsed line. Only valid + // after Next() returns true, and until the iterator is destroyed or + // Next() is called again. This may give strange results on non-Linux + // systems. Prefer FormatLine() if that may be a concern. + const char *CurrentLine() const { return stext_; } + + // Writes the "canonical" form of the /proc/xxx/maps info for a single + // line to the passed-in buffer. Returns the number of bytes written, + // or 0 if it was not able to write the complete line. (To guarantee + // success, buffer should have size at least Buffer::kBufSize.) + // Takes as arguments values set via a call to Next(). The + // "canonical" form of the line (taken from linux's /proc/xxx/maps): + // <start_addr(hex)>-<end_addr(hex)> <perms(rwxp)> <offset(hex)> + + // <major_dev(hex)>:<minor_dev(hex)> <inode> <filename> Note: the + // eg + // 08048000-0804c000 r-xp 00000000 03:01 3793678 /bin/cat + // If you don't have the dev_t (dev), feel free to pass in 0. + // (Next() doesn't return a dev_t, though NextExt does.) + // + // Note: if filename and flags were obtained via a call to Next(), + // then the output of this function is only valid if Next() returned + // true, and only until the iterator is destroyed or Next() is + // called again. (Since filename, at least, points into CurrentLine.) + static int FormatLine(char* buffer, int bufsize, + uint64 start, uint64 end, const char *flags, + uint64 offset, int64 inode, const char *filename, + dev_t dev); + + // Find the next entry in /proc/maps; return true if found or false + // if at the end of the file. + // + // Any of the result pointers can be NULL if you're not interested + // in those values. + // + // If "flags" and "filename" are passed, they end up pointing to + // storage within the ProcMapsIterator that is valid only until the + // iterator is destroyed or Next() is called again. The caller may + // modify the contents of these strings (up as far as the first NUL, + // and only until the subsequent call to Next()) if desired. + + // The offsets are all uint64 in order to handle the case of a + // 32-bit process running on a 64-bit kernel + // + // IMPORTANT NOTE: see top-of-class notes for details about what + // mapped regions Next() iterates over, depending on O/S. + // TODO(csilvers): make flags and filename const. + bool Next(uint64 *start, uint64 *end, char **flags, + uint64 *offset, int64 *inode, char **filename); + + bool NextExt(uint64 *start, uint64 *end, char **flags, + uint64 *offset, int64 *inode, char **filename, + uint64 *file_mapping, uint64 *file_pages, + uint64 *anon_mapping, uint64 *anon_pages, + dev_t *dev); + + ~ProcMapsIterator(); + + private: + void Init(pid_t pid, Buffer *buffer, bool use_maps_backing); + + char *ibuf_; // input buffer + char *stext_; // start of text + char *etext_; // end of text + char *nextline_; // start of next line + char *ebuf_; // end of buffer (1 char for a nul) +#if (defined(_WIN32) || defined(__MINGW32__)) && (!defined(__CYGWIN__) && !defined(__CYGWIN32__)) + HANDLE snapshot_; // filehandle on dll info + // In a change from the usual W-A pattern, there is no A variant of + // MODULEENTRY32. Tlhelp32.h #defines the W variant, but not the A. + // We want the original A variants, and this #undef is the only + // way I see to get them. Redefining it when we're done prevents us + // from affecting other .cc files. +# ifdef MODULEENTRY32 // Alias of W +# undef MODULEENTRY32 + MODULEENTRY32 module_; // info about current dll (and dll iterator) +# define MODULEENTRY32 MODULEENTRY32W +# else // It's the ascii, the one we want. + MODULEENTRY32 module_; // info about current dll (and dll iterator) +# endif +#elif defined(__MACH__) + int current_image_; // dll's are called "images" in macos parlance + int current_load_cmd_; // the segment of this dll we're examining +#elif defined(__sun__) // Solaris + int fd_; + char current_filename_[PATH_MAX]; +#else + int fd_; // filehandle on /proc/*/maps +#endif + pid_t pid_; + char flags_[10]; + Buffer* dynamic_buffer_; // dynamically-allocated Buffer + bool using_maps_backing_; // true if we are looking at maps_backing instead of maps. +}; + +#endif /* #ifndef SWIG */ + +// Helper routines +typedef int RawFD; +const RawFD kIllegalRawFD = -1; // what open returns if it fails + +RawFD RawOpenForWriting(const char* filename); // uses default permissions +void RawWrite(RawFD fd, const char* buf, size_t len); +void RawClose(RawFD fd); + +int FillProcSelfMaps(char buf[], int size, bool* wrote_all); +void DumpProcSelfMaps(RawFD fd); + +#endif /* #ifndef _SYSINFO_H_ */ diff --git a/lib/asan/tests/asan_benchmarks_test.cc b/lib/asan/tests/asan_benchmarks_test.cc new file mode 100644 index 000000000000..b72cc3fbe14b --- /dev/null +++ b/lib/asan/tests/asan_benchmarks_test.cc @@ -0,0 +1,86 @@ +//===-- asan_benchmarks_test.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Some benchmarks for the instrumented code. +//===----------------------------------------------------------------------===// + +#include "asan_test_config.h" +#include "asan_test_utils.h" + +template<class T> +__attribute__((noinline)) +static void ManyAccessFunc(T *x, size_t n_elements, size_t n_iter) { + for (size_t iter = 0; iter < n_iter; iter++) { + break_optimization(0); + // hand unroll the loop to stress the reg alloc. + for (size_t i = 0; i <= n_elements - 16; i += 16) { + x[i + 0] = i; + x[i + 1] = i; + x[i + 2] = i; + x[i + 3] = i; + x[i + 4] = i; + x[i + 5] = i; + x[i + 6] = i; + x[i + 7] = i; + x[i + 8] = i; + x[i + 9] = i; + x[i + 10] = i; + x[i + 11] = i; + x[i + 12] = i; + x[i + 13] = i; + x[i + 14] = i; + x[i + 15] = i; + } + } +} + +TEST(AddressSanitizer, ManyAccessBenchmark) { + size_t kLen = 1024; + int *int_array = new int[kLen]; + ManyAccessFunc(int_array, kLen, 1 << 24); + delete [] int_array; +} + +// access 7 char elements in a 7 byte array (i.e. on the border). +__attribute__((noinline)) +static void BorderAccessFunc(char *x, size_t n_iter) { + for (size_t iter = 0; iter < n_iter; iter++) { + break_optimization(x); + x[0] = 0; + x[1] = 0; + x[2] = 0; + x[3] = 0; + x[4] = 0; + x[5] = 0; + x[6] = 0; + } +} + +TEST(AddressSanitizer, BorderAccessBenchmark) { + char *char_7_array = new char[7]; + BorderAccessFunc(char_7_array, 1 << 30); + delete [] char_7_array; +} + +static void FunctionWithLargeStack() { + int stack[1000]; + Ident(stack); +} + +TEST(AddressSanitizer, FakeStackBenchmark) { + for (int i = 0; i < 10000000; i++) + Ident(&FunctionWithLargeStack)(); +} + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/lib/asan/tests/asan_break_optimization.cc b/lib/asan/tests/asan_break_optimization.cc new file mode 100644 index 000000000000..acd042701e1e --- /dev/null +++ b/lib/asan/tests/asan_break_optimization.cc @@ -0,0 +1,18 @@ +//===-- asan_break_optimization.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +//===----------------------------------------------------------------------===// + +#include "asan_test_utils.h" +// Have this function in a separate file to avoid inlining. +// (Yes, we know about cross-file inlining, but let's assume we don't use it). +extern "C" void break_optimization(void *x) { +} diff --git a/lib/asan/tests/asan_exceptions_test.cc b/lib/asan/tests/asan_exceptions_test.cc new file mode 100644 index 000000000000..ecd406de7561 --- /dev/null +++ b/lib/asan/tests/asan_exceptions_test.cc @@ -0,0 +1,27 @@ +// See http://llvm.org/bugs/show_bug.cgi?id=11468 +#include <stdio.h> +#include <string> + +class Action { + public: + Action() {} + void PrintString(const std::string& msg) const { + fprintf(stderr, "%s\n", msg.c_str()); + } + void Throw(const char& arg) const { + PrintString("PrintString called!"); // this line is important + throw arg; + } +}; + +int main() { + const Action a; + fprintf(stderr, "&a before = %p\n", &a); + try { + a.Throw('c'); + } catch(const char&) { + fprintf(stderr, "&a in catch = %p\n", &a); + } + fprintf(stderr, "&a final = %p\n", &a); + return 0; +} diff --git a/lib/asan/tests/asan_globals_test.cc b/lib/asan/tests/asan_globals_test.cc new file mode 100644 index 000000000000..2303f8bdc43b --- /dev/null +++ b/lib/asan/tests/asan_globals_test.cc @@ -0,0 +1,24 @@ +//===-- asan_globals_test.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Some globals in a separate file. +//===----------------------------------------------------------------------===// + +extern char glob5[5]; +static char static10[10]; + +int GlobalsTest(int zero) { + static char func_static15[15]; + glob5[zero] = 0; + static10[zero] = 0; + func_static15[zero] = 0; + return glob5[1] + func_static15[2]; +} diff --git a/lib/asan/tests/asan_interface_test.cc b/lib/asan/tests/asan_interface_test.cc new file mode 100644 index 000000000000..c26ed92468b3 --- /dev/null +++ b/lib/asan/tests/asan_interface_test.cc @@ -0,0 +1,334 @@ +//===-- asan_interface_test.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +//===----------------------------------------------------------------------===// +#include <pthread.h> +#include <stdio.h> +#include <string.h> + +#include "asan_test_config.h" +#include "asan_test_utils.h" +#include "asan_interface.h" + +TEST(AddressSanitizerInterface, GetEstimatedAllocatedSize) { + EXPECT_EQ(1, __asan_get_estimated_allocated_size(0)); + const size_t sizes[] = { 1, 30, 1<<30 }; + for (size_t i = 0; i < 3; i++) { + EXPECT_EQ(sizes[i], __asan_get_estimated_allocated_size(sizes[i])); + } +} + +static const char* kGetAllocatedSizeErrorMsg = + "__asan_get_allocated_size failed"; + +TEST(AddressSanitizerInterface, GetAllocatedSizeAndOwnershipTest) { + const size_t kArraySize = 100; + char *array = Ident((char*)malloc(kArraySize)); + int *int_ptr = Ident(new int); + + // Allocated memory is owned by allocator. Allocated size should be + // equal to requested size. + EXPECT_EQ(true, __asan_get_ownership(array)); + EXPECT_EQ(kArraySize, __asan_get_allocated_size(array)); + EXPECT_EQ(true, __asan_get_ownership(int_ptr)); + EXPECT_EQ(sizeof(int), __asan_get_allocated_size(int_ptr)); + + // We cannot call GetAllocatedSize from the memory we didn't map, + // and from the interior pointers (not returned by previous malloc). + void *wild_addr = (void*)0x1; + EXPECT_EQ(false, __asan_get_ownership(wild_addr)); + EXPECT_DEATH(__asan_get_allocated_size(wild_addr), kGetAllocatedSizeErrorMsg); + EXPECT_EQ(false, __asan_get_ownership(array + kArraySize / 2)); + EXPECT_DEATH(__asan_get_allocated_size(array + kArraySize / 2), + kGetAllocatedSizeErrorMsg); + + // NULL is a valid argument and is owned. + EXPECT_EQ(true, __asan_get_ownership(NULL)); + EXPECT_EQ(0, __asan_get_allocated_size(NULL)); + + // When memory is freed, it's not owned, and call to GetAllocatedSize + // is forbidden. + free(array); + EXPECT_EQ(false, __asan_get_ownership(array)); + EXPECT_DEATH(__asan_get_allocated_size(array), kGetAllocatedSizeErrorMsg); + + delete int_ptr; +} + +TEST(AddressSanitizerInterface, GetCurrentAllocatedBytesTest) { + size_t before_malloc, after_malloc, after_free; + char *array; + const size_t kMallocSize = 100; + before_malloc = __asan_get_current_allocated_bytes(); + + array = Ident((char*)malloc(kMallocSize)); + after_malloc = __asan_get_current_allocated_bytes(); + EXPECT_EQ(before_malloc + kMallocSize, after_malloc); + + free(array); + after_free = __asan_get_current_allocated_bytes(); + EXPECT_EQ(before_malloc, after_free); +} + +static void DoDoubleFree() { + int *x = Ident(new int); + delete Ident(x); + delete Ident(x); +} + +// This test is run in a separate process, so that large malloced +// chunk won't remain in the free lists after the test. +// Note: use ASSERT_* instead of EXPECT_* here. +static void RunGetHeapSizeTestAndDie() { + size_t old_heap_size, new_heap_size, heap_growth; + // We unlikely have have chunk of this size in free list. + static const size_t kLargeMallocSize = 1 << 29; // 512M + old_heap_size = __asan_get_heap_size(); + fprintf(stderr, "allocating %zu bytes:\n", kLargeMallocSize); + free(Ident(malloc(kLargeMallocSize))); + new_heap_size = __asan_get_heap_size(); + heap_growth = new_heap_size - old_heap_size; + fprintf(stderr, "heap growth after first malloc: %zu\n", heap_growth); + ASSERT_GE(heap_growth, kLargeMallocSize); + ASSERT_LE(heap_growth, 2 * kLargeMallocSize); + + // Now large chunk should fall into free list, and can be + // allocated without increasing heap size. + old_heap_size = new_heap_size; + free(Ident(malloc(kLargeMallocSize))); + heap_growth = __asan_get_heap_size() - old_heap_size; + fprintf(stderr, "heap growth after second malloc: %zu\n", heap_growth); + ASSERT_LT(heap_growth, kLargeMallocSize); + + // Test passed. Now die with expected double-free. + DoDoubleFree(); +} + +TEST(AddressSanitizerInterface, GetHeapSizeTest) { + EXPECT_DEATH(RunGetHeapSizeTestAndDie(), "double-free"); +} + +// Note: use ASSERT_* instead of EXPECT_* here. +static void DoLargeMallocForGetFreeBytesTestAndDie() { + size_t old_free_bytes, new_free_bytes; + static const size_t kLargeMallocSize = 1 << 29; // 512M + // If we malloc and free a large memory chunk, it will not fall + // into quarantine and will be available for future requests. + old_free_bytes = __asan_get_free_bytes(); + fprintf(stderr, "allocating %zu bytes:\n", kLargeMallocSize); + fprintf(stderr, "free bytes before malloc: %zu\n", old_free_bytes); + free(Ident(malloc(kLargeMallocSize))); + new_free_bytes = __asan_get_free_bytes(); + fprintf(stderr, "free bytes after malloc and free: %zu\n", new_free_bytes); + ASSERT_GE(new_free_bytes, old_free_bytes + kLargeMallocSize); + // Test passed. + DoDoubleFree(); +} + +TEST(AddressSanitizerInterface, GetFreeBytesTest) { + static const size_t kNumOfChunks = 100; + static const size_t kChunkSize = 100; + char *chunks[kNumOfChunks]; + size_t i; + size_t old_free_bytes, new_free_bytes; + // Allocate a small chunk. Now allocator probably has a lot of these + // chunks to fulfill future requests. So, future requests will decrease + // the number of free bytes. + chunks[0] = Ident((char*)malloc(kChunkSize)); + old_free_bytes = __asan_get_free_bytes(); + for (i = 1; i < kNumOfChunks; i++) { + chunks[i] = Ident((char*)malloc(kChunkSize)); + new_free_bytes = __asan_get_free_bytes(); + EXPECT_LT(new_free_bytes, old_free_bytes); + old_free_bytes = new_free_bytes; + } + // Deleting these chunks will move them to quarantine, number of free + // bytes won't increase. + for (i = 0; i < kNumOfChunks; i++) { + free(chunks[i]); + EXPECT_EQ(old_free_bytes, __asan_get_free_bytes()); + } + EXPECT_DEATH(DoLargeMallocForGetFreeBytesTestAndDie(), "double-free"); +} + +static const size_t kManyThreadsMallocSizes[] = {5, 1UL<<10, 1UL<<20, 357}; +static const size_t kManyThreadsIterations = 250; +static const size_t kManyThreadsNumThreads = 200; + +void *ManyThreadsWithStatsWorker(void *arg) { + for (size_t iter = 0; iter < kManyThreadsIterations; iter++) { + for (size_t size_index = 0; size_index < 4; size_index++) { + free(Ident(malloc(kManyThreadsMallocSizes[size_index]))); + } + } + return 0; +} + +TEST(AddressSanitizerInterface, ManyThreadsWithStatsStressTest) { + size_t before_test, after_test, i; + pthread_t threads[kManyThreadsNumThreads]; + before_test = __asan_get_current_allocated_bytes(); + for (i = 0; i < kManyThreadsNumThreads; i++) { + pthread_create(&threads[i], 0, + (void* (*)(void *x))ManyThreadsWithStatsWorker, (void*)i); + } + for (i = 0; i < kManyThreadsNumThreads; i++) { + pthread_join(threads[i], 0); + } + after_test = __asan_get_current_allocated_bytes(); + // ASan stats also reflect memory usage of internal ASan RTL structs, + // so we can't check for equality here. + EXPECT_LT(after_test, before_test + (1UL<<20)); +} + +TEST(AddressSanitizerInterface, ExitCode) { + int original_exit_code = __asan_set_error_exit_code(7); + EXPECT_EXIT(DoDoubleFree(), ::testing::ExitedWithCode(7), ""); + EXPECT_EQ(7, __asan_set_error_exit_code(8)); + EXPECT_EXIT(DoDoubleFree(), ::testing::ExitedWithCode(8), ""); + EXPECT_EQ(8, __asan_set_error_exit_code(original_exit_code)); + EXPECT_EXIT(DoDoubleFree(), + ::testing::ExitedWithCode(original_exit_code), ""); +} + +static const char* kUseAfterPoisonErrorMessage = "use-after-poison"; + +#define ACCESS(ptr, offset) Ident(*(ptr + offset)) + +#define DIE_ON_ACCESS(ptr, offset) \ + EXPECT_DEATH(Ident(*(ptr + offset)), kUseAfterPoisonErrorMessage) + +TEST(AddressSanitizerInterface, SimplePoisonMemoryRegionTest) { + char *array = Ident((char*)malloc(120)); + // poison array[40..80) + ASAN_POISON_MEMORY_REGION(array + 40, 40); + ACCESS(array, 39); + ACCESS(array, 80); + DIE_ON_ACCESS(array, 40); + DIE_ON_ACCESS(array, 60); + DIE_ON_ACCESS(array, 79); + ASAN_UNPOISON_MEMORY_REGION(array + 40, 40); + // access previously poisoned memory. + ACCESS(array, 40); + ACCESS(array, 79); + free(array); +} + +TEST(AddressSanitizerInterface, OverlappingPoisonMemoryRegionTest) { + char *array = Ident((char*)malloc(120)); + // Poison [0..40) and [80..120) + ASAN_POISON_MEMORY_REGION(array, 40); + ASAN_POISON_MEMORY_REGION(array + 80, 40); + DIE_ON_ACCESS(array, 20); + ACCESS(array, 60); + DIE_ON_ACCESS(array, 100); + // Poison whole array - [0..120) + ASAN_POISON_MEMORY_REGION(array, 120); + DIE_ON_ACCESS(array, 60); + // Unpoison [24..96) + ASAN_UNPOISON_MEMORY_REGION(array + 24, 72); + DIE_ON_ACCESS(array, 23); + ACCESS(array, 24); + ACCESS(array, 60); + ACCESS(array, 95); + DIE_ON_ACCESS(array, 96); + free(array); +} + +TEST(AddressSanitizerInterface, PushAndPopWithPoisoningTest) { + // Vector of capacity 20 + char *vec = Ident((char*)malloc(20)); + ASAN_POISON_MEMORY_REGION(vec, 20); + for (size_t i = 0; i < 7; i++) { + // Simulate push_back. + ASAN_UNPOISON_MEMORY_REGION(vec + i, 1); + ACCESS(vec, i); + DIE_ON_ACCESS(vec, i + 1); + } + for (size_t i = 7; i > 0; i--) { + // Simulate pop_back. + ASAN_POISON_MEMORY_REGION(vec + i - 1, 1); + DIE_ON_ACCESS(vec, i - 1); + if (i > 1) ACCESS(vec, i - 2); + } + free(vec); +} + +// Make sure that each aligned block of size "2^granularity" doesn't have +// "true" value before "false" value. +static void MakeShadowValid(bool *shadow, int length, int granularity) { + bool can_be_poisoned = true; + for (int i = length - 1; i >= 0; i--) { + can_be_poisoned &= shadow[i]; + shadow[i] &= can_be_poisoned; + if (i % (1 << granularity) == 0) { + can_be_poisoned = true; + } + } +} + +TEST(AddressSanitizerInterface, PoisoningStressTest) { + const size_t kSize = 24; + bool expected[kSize]; + char *arr = Ident((char*)malloc(kSize)); + for (size_t l1 = 0; l1 < kSize; l1++) { + for (size_t s1 = 1; l1 + s1 <= kSize; s1++) { + for (size_t l2 = 0; l2 < kSize; l2++) { + for (size_t s2 = 1; l2 + s2 <= kSize; s2++) { + // Poison [l1, l1+s1), [l2, l2+s2) and check result. + ASAN_UNPOISON_MEMORY_REGION(arr, kSize); + ASAN_POISON_MEMORY_REGION(arr + l1, s1); + ASAN_POISON_MEMORY_REGION(arr + l2, s2); + memset(expected, false, kSize); + memset(expected + l1, true, s1); + MakeShadowValid(expected, 24, /*granularity*/ 3); + memset(expected + l2, true, s2); + MakeShadowValid(expected, 24, /*granularity*/ 3); + for (size_t i = 0; i < kSize; i++) { + ASSERT_EQ(expected[i], __asan_address_is_poisoned(arr + i)); + } + // Unpoison [l1, l1+s1) and [l2, l2+s2) and check result. + ASAN_POISON_MEMORY_REGION(arr, kSize); + ASAN_UNPOISON_MEMORY_REGION(arr + l1, s1); + ASAN_UNPOISON_MEMORY_REGION(arr + l2, s2); + memset(expected, true, kSize); + memset(expected + l1, false, s1); + MakeShadowValid(expected, 24, /*granularity*/ 3); + memset(expected + l2, false, s2); + MakeShadowValid(expected, 24, /*granularity*/ 3); + for (size_t i = 0; i < kSize; i++) { + ASSERT_EQ(expected[i], __asan_address_is_poisoned(arr + i)); + } + } + } + } + } +} + +static const char *kInvalidPoisonMessage = "invalid-poison-memory-range"; +static const char *kInvalidUnpoisonMessage = "invalid-unpoison-memory-range"; + +TEST(AddressSanitizerInterface, DISABLED_InvalidPoisonAndUnpoisonCallsTest) { + char *array = Ident((char*)malloc(120)); + ASAN_UNPOISON_MEMORY_REGION(array, 120); + // Try to unpoison not owned memory + EXPECT_DEATH(ASAN_UNPOISON_MEMORY_REGION(array, 121), + kInvalidUnpoisonMessage); + EXPECT_DEATH(ASAN_UNPOISON_MEMORY_REGION(array - 1, 120), + kInvalidUnpoisonMessage); + + ASAN_POISON_MEMORY_REGION(array, 120); + // Try to poison not owned memory. + EXPECT_DEATH(ASAN_POISON_MEMORY_REGION(array, 121), kInvalidPoisonMessage); + EXPECT_DEATH(ASAN_POISON_MEMORY_REGION(array - 1, 120), + kInvalidPoisonMessage); + free(array); +} diff --git a/lib/asan/tests/asan_mac_test.h b/lib/asan/tests/asan_mac_test.h new file mode 100644 index 000000000000..e3ad8273ae14 --- /dev/null +++ b/lib/asan/tests/asan_mac_test.h @@ -0,0 +1,16 @@ +extern "C" { + void CFAllocatorDefaultDoubleFree(); + void CFAllocatorSystemDefaultDoubleFree(); + void CFAllocatorMallocDoubleFree(); + void CFAllocatorMallocZoneDoubleFree(); + void CallFreeOnWorkqueue(void *mem); + void TestGCDDispatchAsync(); + void TestGCDDispatchSync(); + void TestGCDReuseWqthreadsAsync(); + void TestGCDReuseWqthreadsSync(); + void TestGCDDispatchAfter(); + void TestGCDInTSDDestructor(); + void TestGCDSourceEvent(); + void TestGCDSourceCancel(); + void TestGCDGroupAsync(); +} diff --git a/lib/asan/tests/asan_mac_test.mm b/lib/asan/tests/asan_mac_test.mm new file mode 100644 index 000000000000..b5dbbde4f315 --- /dev/null +++ b/lib/asan/tests/asan_mac_test.mm @@ -0,0 +1,203 @@ +// Mac OS X 10.6 or higher only. +#include <dispatch/dispatch.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#import <CoreFoundation/CFBase.h> +#import <Foundation/NSObject.h> + +void CFAllocatorDefaultDoubleFree() { + void *mem = CFAllocatorAllocate(kCFAllocatorDefault, 5, 0); + CFAllocatorDeallocate(kCFAllocatorDefault, mem); + CFAllocatorDeallocate(kCFAllocatorDefault, mem); +} + +void CFAllocatorSystemDefaultDoubleFree() { + void *mem = CFAllocatorAllocate(kCFAllocatorSystemDefault, 5, 0); + CFAllocatorDeallocate(kCFAllocatorSystemDefault, mem); + CFAllocatorDeallocate(kCFAllocatorSystemDefault, mem); +} + +void CFAllocatorMallocDoubleFree() { + void *mem = CFAllocatorAllocate(kCFAllocatorMalloc, 5, 0); + CFAllocatorDeallocate(kCFAllocatorMalloc, mem); + CFAllocatorDeallocate(kCFAllocatorMalloc, mem); +} + +void CFAllocatorMallocZoneDoubleFree() { + void *mem = CFAllocatorAllocate(kCFAllocatorMallocZone, 5, 0); + CFAllocatorDeallocate(kCFAllocatorMallocZone, mem); + CFAllocatorDeallocate(kCFAllocatorMallocZone, mem); +} + + +// Test the +load instrumentation. +// Because the +load methods are invoked before anything else is initialized, +// it makes little sense to wrap the code below into a gTest test case. +// If AddressSanitizer doesn't instrument the +load method below correctly, +// everything will just crash. + +char kStartupStr[] = + "If your test didn't crash, AddressSanitizer is instrumenting " + "the +load methods correctly."; + +@interface LoadSomething : NSObject { +} +@end + +@implementation LoadSomething + ++(void) load { + for (int i = 0; i < strlen(kStartupStr); i++) { + volatile char ch = kStartupStr[i]; // make sure no optimizations occur. + } + // Don't print anything here not to interfere with the death tests. +} + +@end + +void worker_do_alloc(int size) { + char * volatile mem = malloc(size); + mem[0] = 0; // Ok + free(mem); +} + +void worker_do_crash(int size) { + char * volatile mem = malloc(size); + mem[size] = 0; // BOOM + free(mem); +} + +// Tests for the Grand Central Dispatch. See +// http://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html +// for the reference. + +void TestGCDDispatchAsync() { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_block_t block = ^{ worker_do_crash(1024); }; + // dispatch_async() runs the task on a worker thread that does not go through + // pthread_create(). We need to verify that AddressSanitizer notices that the + // thread has started. + dispatch_async(queue, block); + // TODO(glider): this is hacky. Need to wait for the worker instead. + sleep(1); +} + +void TestGCDDispatchSync() { + dispatch_queue_t queue = dispatch_get_global_queue(2, 0); + dispatch_block_t block = ^{ worker_do_crash(1024); }; + // dispatch_sync() runs the task on a worker thread that does not go through + // pthread_create(). We need to verify that AddressSanitizer notices that the + // thread has started. + dispatch_sync(queue, block); + // TODO(glider): this is hacky. Need to wait for the worker instead. + sleep(1); +} + +// libdispatch spawns a rather small number of threads and reuses them. We need +// to make sure AddressSanitizer handles the reusing correctly. +void TestGCDReuseWqthreadsAsync() { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_block_t block_alloc = ^{ worker_do_alloc(1024); }; + dispatch_block_t block_crash = ^{ worker_do_crash(1024); }; + for (int i = 0; i < 100; i++) { + dispatch_async(queue, block_alloc); + } + dispatch_async(queue, block_crash); + // TODO(glider): this is hacky. Need to wait for the workers instead. + sleep(1); +} + +// Try to trigger abnormal behaviour of dispatch_sync() being unhandled by us. +void TestGCDReuseWqthreadsSync() { + dispatch_queue_t queue[4]; + queue[0] = dispatch_get_global_queue(2, 0); + queue[1] = dispatch_get_global_queue(0, 0); + queue[2] = dispatch_get_global_queue(-2, 0); + queue[3] = dispatch_queue_create("my_queue", NULL); + dispatch_block_t block_alloc = ^{ worker_do_alloc(1024); }; + dispatch_block_t block_crash = ^{ worker_do_crash(1024); }; + for (int i = 0; i < 1000; i++) { + dispatch_sync(queue[i % 4], block_alloc); + } + dispatch_sync(queue[3], block_crash); + // TODO(glider): this is hacky. Need to wait for the workers instead. + sleep(1); +} + +void TestGCDDispatchAfter() { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_block_t block_crash = ^{ worker_do_crash(1024); }; + // Schedule the event one second from the current time. + dispatch_time_t milestone = + dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); + dispatch_after(milestone, queue, block_crash); + // Let's wait for a bit longer now. + // TODO(glider): this is still hacky. + sleep(2); +} + +void worker_do_deallocate(void *ptr) { + free(ptr); +} + +void CallFreeOnWorkqueue(void *tsd) { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_block_t block_dealloc = ^{ worker_do_deallocate(tsd); }; + dispatch_async(queue, block_dealloc); + // Do not wait for the worker to free the memory -- nobody is going to touch + // it. +} + +void TestGCDSourceEvent() { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_source_t timer = + dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + // Schedule the timer one second from the current time. + dispatch_time_t milestone = + dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); + + dispatch_source_set_timer(timer, milestone, DISPATCH_TIME_FOREVER, 0); + char * volatile mem = malloc(10); + dispatch_source_set_event_handler(timer, ^{ + mem[10] = 1; + }); + dispatch_resume(timer); + sleep(2); +} + +void TestGCDSourceCancel() { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_source_t timer = + dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + // Schedule the timer one second from the current time. + dispatch_time_t milestone = + dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); + + dispatch_source_set_timer(timer, milestone, DISPATCH_TIME_FOREVER, 0); + char * volatile mem = malloc(10); + // Both dispatch_source_set_cancel_handler() and + // dispatch_source_set_event_handler() use dispatch_barrier_async_f(). + // It's tricky to test dispatch_source_set_cancel_handler() separately, + // so we test both here. + dispatch_source_set_event_handler(timer, ^{ + dispatch_source_cancel(timer); + }); + dispatch_source_set_cancel_handler(timer, ^{ + mem[10] = 1; + }); + dispatch_resume(timer); + sleep(2); +} + +void TestGCDGroupAsync() { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_group_t group = dispatch_group_create(); + char * volatile mem = malloc(10); + dispatch_group_async(group, queue, ^{ + mem[10] = 1; + }); + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); +} diff --git a/lib/asan/tests/asan_noinst_test.cc b/lib/asan/tests/asan_noinst_test.cc new file mode 100644 index 000000000000..204c0dacc342 --- /dev/null +++ b/lib/asan/tests/asan_noinst_test.cc @@ -0,0 +1,329 @@ +//===-- asan_noinst_test.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// This test file should be compiled w/o asan instrumentation. +//===----------------------------------------------------------------------===// +#include "asan_allocator.h" +#include "asan_interface.h" +#include "asan_internal.h" +#include "asan_mapping.h" +#include "asan_stack.h" +#include "asan_test_utils.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <vector> +#include <algorithm> +#include "gtest/gtest.h" + +// Simple stand-alone pseudorandom number generator. +// Current algorithm is ANSI C linear congruential PRNG. +static inline uint32_t my_rand(uint32_t* state) { + return (*state = *state * 1103515245 + 12345) >> 16; +} + +static uint32_t global_seed = 0; + + +TEST(AddressSanitizer, InternalSimpleDeathTest) { + EXPECT_DEATH(exit(1), ""); +} + +static void MallocStress(size_t n) { + uint32_t seed = my_rand(&global_seed); + __asan::AsanStackTrace stack1; + stack1.trace[0] = 0xa123; + stack1.trace[1] = 0xa456; + stack1.size = 2; + + __asan::AsanStackTrace stack2; + stack2.trace[0] = 0xb123; + stack2.trace[1] = 0xb456; + stack2.size = 2; + + __asan::AsanStackTrace stack3; + stack3.trace[0] = 0xc123; + stack3.trace[1] = 0xc456; + stack3.size = 2; + + std::vector<void *> vec; + for (size_t i = 0; i < n; i++) { + if ((i % 3) == 0) { + if (vec.empty()) continue; + size_t idx = my_rand(&seed) % vec.size(); + void *ptr = vec[idx]; + vec[idx] = vec.back(); + vec.pop_back(); + __asan::asan_free(ptr, &stack1); + } else { + size_t size = my_rand(&seed) % 1000 + 1; + switch ((my_rand(&seed) % 128)) { + case 0: size += 1024; break; + case 1: size += 2048; break; + case 2: size += 4096; break; + } + size_t alignment = 1 << (my_rand(&seed) % 10 + 1); + char *ptr = (char*)__asan::asan_memalign(alignment, size, &stack2); + vec.push_back(ptr); + ptr[0] = 0; + ptr[size-1] = 0; + ptr[size/2] = 0; + } + } + for (size_t i = 0; i < vec.size(); i++) + __asan::asan_free(vec[i], &stack3); +} + + +TEST(AddressSanitizer, NoInstMallocTest) { +#ifdef __arm__ + MallocStress(300000); +#else + MallocStress(1000000); +#endif +} + +static void PrintShadow(const char *tag, uintptr_t ptr, size_t size) { + fprintf(stderr, "%s shadow: %lx size % 3ld: ", tag, (long)ptr, (long)size); + uintptr_t prev_shadow = 0; + for (intptr_t i = -32; i < (intptr_t)size + 32; i++) { + uintptr_t shadow = __asan::MemToShadow(ptr + i); + if (i == 0 || i == (intptr_t)size) + fprintf(stderr, "."); + if (shadow != prev_shadow) { + prev_shadow = shadow; + fprintf(stderr, "%02x", (int)*(uint8_t*)shadow); + } + } + fprintf(stderr, "\n"); +} + +TEST(AddressSanitizer, DISABLED_InternalPrintShadow) { + for (size_t size = 1; size <= 513; size++) { + char *ptr = new char[size]; + PrintShadow("m", (uintptr_t)ptr, size); + delete [] ptr; + PrintShadow("f", (uintptr_t)ptr, size); + } +} + +static uintptr_t pc_array[] = { +#if __WORDSIZE == 64 + 0x7effbf756068ULL, + 0x7effbf75e5abULL, + 0x7effc0625b7cULL, + 0x7effc05b8997ULL, + 0x7effbf990577ULL, + 0x7effbf990c56ULL, + 0x7effbf992f3cULL, + 0x7effbf950c22ULL, + 0x7effc036dba0ULL, + 0x7effc03638a3ULL, + 0x7effc035be4aULL, + 0x7effc0539c45ULL, + 0x7effc0539a65ULL, + 0x7effc03db9b3ULL, + 0x7effc03db100ULL, + 0x7effc037c7b8ULL, + 0x7effc037bfffULL, + 0x7effc038b777ULL, + 0x7effc038021cULL, + 0x7effc037c7d1ULL, + 0x7effc037bfffULL, + 0x7effc038b777ULL, + 0x7effc038021cULL, + 0x7effc037c7d1ULL, + 0x7effc037bfffULL, + 0x7effc038b777ULL, + 0x7effc038021cULL, + 0x7effc037c7d1ULL, + 0x7effc037bfffULL, + 0x7effc0520d26ULL, + 0x7effc009ddffULL, + 0x7effbf90bb50ULL, + 0x7effbdddfa69ULL, + 0x7effbdde1fe2ULL, + 0x7effbdde2424ULL, + 0x7effbdde27b3ULL, + 0x7effbddee53bULL, + 0x7effbdde1988ULL, + 0x7effbdde0904ULL, + 0x7effc106ce0dULL, + 0x7effbcc3fa04ULL, + 0x7effbcc3f6a4ULL, + 0x7effbcc3e726ULL, + 0x7effbcc40852ULL, + 0x7effb681ec4dULL, +#endif // __WORDSIZE + 0xB0B5E768, + 0x7B682EC1, + 0x367F9918, + 0xAE34E13, + 0xBA0C6C6, + 0x13250F46, + 0xA0D6A8AB, + 0x2B07C1A8, + 0x6C844F4A, + 0x2321B53, + 0x1F3D4F8F, + 0x3FE2924B, + 0xB7A2F568, + 0xBD23950A, + 0x61020930, + 0x33E7970C, + 0x405998A1, + 0x59F3551D, + 0x350E3028, + 0xBC55A28D, + 0x361F3AED, + 0xBEAD0F73, + 0xAEF28479, + 0x757E971F, + 0xAEBA450, + 0x43AD22F5, + 0x8C2C50C4, + 0x7AD8A2E1, + 0x69EE4EE8, + 0xC08DFF, + 0x4BA6538, + 0x3708AB2, + 0xC24B6475, + 0x7C8890D7, + 0x6662495F, + 0x9B641689, + 0xD3596B, + 0xA1049569, + 0x44CBC16, + 0x4D39C39F +}; + +void CompressStackTraceTest(size_t n_iter) { + uint32_t seed = my_rand(&global_seed); + const size_t kNumPcs = ASAN_ARRAY_SIZE(pc_array); + uint32_t compressed[2 * kNumPcs]; + + for (size_t iter = 0; iter < n_iter; iter++) { + std::random_shuffle(pc_array, pc_array + kNumPcs); + __asan::AsanStackTrace stack0, stack1; + stack0.CopyFrom(pc_array, kNumPcs); + stack0.size = std::max((size_t)1, (size_t)my_rand(&seed) % stack0.size); + size_t compress_size = + std::max((size_t)2, (size_t)my_rand(&seed) % (2 * kNumPcs)); + size_t n_frames = + __asan::AsanStackTrace::CompressStack(&stack0, compressed, compress_size); + assert(n_frames <= stack0.size); + __asan::AsanStackTrace::UncompressStack(&stack1, compressed, compress_size); + assert(stack1.size == n_frames); + for (size_t i = 0; i < stack1.size; i++) { + assert(stack0.trace[i] == stack1.trace[i]); + } + } +} + +TEST(AddressSanitizer, CompressStackTraceTest) { + CompressStackTraceTest(10000); +} + +void CompressStackTraceBenchmark(size_t n_iter) { + const size_t kNumPcs = ASAN_ARRAY_SIZE(pc_array); + uint32_t compressed[2 * kNumPcs]; + std::random_shuffle(pc_array, pc_array + kNumPcs); + + __asan::AsanStackTrace stack0; + stack0.CopyFrom(pc_array, kNumPcs); + stack0.size = kNumPcs; + for (size_t iter = 0; iter < n_iter; iter++) { + size_t compress_size = kNumPcs; + size_t n_frames = + __asan::AsanStackTrace::CompressStack(&stack0, compressed, compress_size); + Ident(n_frames); + } +} + +TEST(AddressSanitizer, CompressStackTraceBenchmark) { + CompressStackTraceBenchmark(1 << 24); +} + +TEST(AddressSanitizer, QuarantineTest) { + __asan::AsanStackTrace stack; + stack.trace[0] = 0x890; + stack.size = 1; + + const int size = 32; + void *p = __asan::asan_malloc(size, &stack); + __asan::asan_free(p, &stack); + size_t i; + size_t max_i = 1 << 30; + for (i = 0; i < max_i; i++) { + void *p1 = __asan::asan_malloc(size, &stack); + __asan::asan_free(p1, &stack); + if (p1 == p) break; + } + // fprintf(stderr, "i=%ld\n", i); + EXPECT_GE(i, 100000U); + EXPECT_LT(i, max_i); +} + +void *ThreadedQuarantineTestWorker(void *unused) { + uint32_t seed = my_rand(&global_seed); + __asan::AsanStackTrace stack; + stack.trace[0] = 0x890; + stack.size = 1; + + for (size_t i = 0; i < 1000; i++) { + void *p = __asan::asan_malloc(1 + (my_rand(&seed) % 4000), &stack); + __asan::asan_free(p, &stack); + } + return NULL; +} + +// Check that the thread local allocators are flushed when threads are +// destroyed. +TEST(AddressSanitizer, ThreadedQuarantineTest) { + const int n_threads = 3000; + size_t mmaped1 = __asan_get_heap_size(); + for (int i = 0; i < n_threads; i++) { + pthread_t t; + pthread_create(&t, NULL, ThreadedQuarantineTestWorker, 0); + pthread_join(t, 0); + size_t mmaped2 = __asan_get_heap_size(); + EXPECT_LT(mmaped2 - mmaped1, 320U * (1 << 20)); + } +} + +void *ThreadedOneSizeMallocStress(void *unused) { + __asan::AsanStackTrace stack; + stack.trace[0] = 0x890; + stack.size = 1; + const size_t kNumMallocs = 1000; + for (int iter = 0; iter < 1000; iter++) { + void *p[kNumMallocs]; + for (size_t i = 0; i < kNumMallocs; i++) { + p[i] = __asan::asan_malloc(32, &stack); + } + for (size_t i = 0; i < kNumMallocs; i++) { + __asan::asan_free(p[i], &stack); + } + } + return NULL; +} + +TEST(AddressSanitizer, ThreadedOneSizeMallocStressTest) { + const int kNumThreads = 4; + pthread_t t[kNumThreads]; + for (int i = 0; i < kNumThreads; i++) { + pthread_create(&t[i], 0, ThreadedOneSizeMallocStress, 0); + } + for (int i = 0; i < kNumThreads; i++) { + pthread_join(t[i], 0); + } +} diff --git a/lib/asan/tests/asan_test.cc b/lib/asan/tests/asan_test.cc new file mode 100644 index 000000000000..0ff72d3cf6d8 --- /dev/null +++ b/lib/asan/tests/asan_test.cc @@ -0,0 +1,2022 @@ +//===-- asan_test.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +//===----------------------------------------------------------------------===// +#include <stdio.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <pthread.h> +#include <stdint.h> +#include <setjmp.h> +#include <assert.h> + +#if defined(__i386__) or defined(__x86_64__) +#include <emmintrin.h> +#endif + +#include "asan_test_config.h" +#include "asan_test_utils.h" + +#ifndef __APPLE__ +#include <malloc.h> +#endif // __APPLE__ + +#ifdef __APPLE__ +static bool APPLE = true; +#else +static bool APPLE = false; +#endif + +#if ASAN_HAS_EXCEPTIONS +# define ASAN_THROW(x) throw (x) +#else +# define ASAN_THROW(x) +#endif + +#include <sys/mman.h> + +typedef uint8_t U1; +typedef uint16_t U2; +typedef uint32_t U4; +typedef uint64_t U8; + +static const char *progname; +static const int kPageSize = 4096; + +// Simple stand-alone pseudorandom number generator. +// Current algorithm is ANSI C linear congruential PRNG. +static inline uint32_t my_rand(uint32_t* state) { + return (*state = *state * 1103515245 + 12345) >> 16; +} + +static uint32_t global_seed = 0; + +class ObjdumpOfMyself { + public: + explicit ObjdumpOfMyself(const string &binary) { + is_correct = true; + string objdump_name = APPLE ? "gobjdump" : "objdump"; + string prog = objdump_name + " -d " + binary; + // TODO(glider): popen() succeeds even if the file does not exist. + FILE *pipe = popen(prog.c_str(), "r"); + string objdump; + if (pipe) { + const int kBuffSize = 4096; + char buff[kBuffSize+1]; + int read_bytes; + while ((read_bytes = fread(buff, 1, kBuffSize, pipe)) > 0) { + buff[read_bytes] = 0; + objdump.append(buff); + } + pclose(pipe); + } else { + is_correct = false; + } + // cut the objdump into functions + string fn, next_fn; + size_t next_start; + for (size_t start = fn_start(objdump, 0, &fn); + start != string::npos; + start = next_start, fn = next_fn) { + next_start = fn_start(objdump, start, &next_fn); + // fprintf(stderr, "start: %d next_start = %d fn: %s\n", + // (int)start, (int)next_start, fn.c_str()); + // Mac OS adds the "_" prefix to function names. + if (fn.find(APPLE ? "_Disasm" : "Disasm") == string::npos) { + continue; + } + string fn_body = objdump.substr(start, next_start - start); + // fprintf(stderr, "%s:\n%s", fn.c_str(), fn_body.c_str()); + functions_[fn] = fn_body; + } + } + + string &GetFuncDisasm(const string &fn) { + return functions_[fn]; + } + + int CountInsnInFunc(const string &fn, const vector<string> &insns) { + // Mac OS adds the "_" prefix to function names. + string fn_ref = APPLE ? "_" + fn : fn; + const string &disasm = GetFuncDisasm(fn_ref); + if (disasm.empty()) return -1; + size_t counter = 0; + for (size_t i = 0; i < insns.size(); i++) { + size_t pos = 0; + while ((pos = disasm.find(insns[i], pos)) != string::npos) { + counter++; + pos++; + } + } + return counter; + } + + bool IsCorrect() { return is_correct; } + + private: + size_t fn_start(const string &objdump, size_t start_pos, string *fn) { + size_t pos = objdump.find(">:\n", start_pos); + if (pos == string::npos) + return string::npos; + size_t beg = pos; + while (beg > 0 && objdump[beg - 1] != '<') + beg--; + *fn = objdump.substr(beg, pos - beg); + return pos + 3; + } + + map<string, string> functions_; + bool is_correct; +}; + +static ObjdumpOfMyself *objdump_of_myself() { + static ObjdumpOfMyself *o = new ObjdumpOfMyself(progname); + return o; +} + +const size_t kLargeMalloc = 1 << 24; + +template<class T> +__attribute__((noinline)) +void asan_write(T *a) { + *a = 0; +} + +__attribute__((noinline)) +void asan_write_sized_aligned(uint8_t *p, size_t size) { + EXPECT_EQ(0, ((uintptr_t)p % size)); + if (size == 1) asan_write((uint8_t*)p); + else if (size == 2) asan_write((uint16_t*)p); + else if (size == 4) asan_write((uint32_t*)p); + else if (size == 8) asan_write((uint64_t*)p); +} + +__attribute__((noinline)) void *malloc_fff(size_t size) { + void *res = malloc/**/(size); break_optimization(0); return res;} +__attribute__((noinline)) void *malloc_eee(size_t size) { + void *res = malloc_fff(size); break_optimization(0); return res;} +__attribute__((noinline)) void *malloc_ddd(size_t size) { + void *res = malloc_eee(size); break_optimization(0); return res;} +__attribute__((noinline)) void *malloc_ccc(size_t size) { + void *res = malloc_ddd(size); break_optimization(0); return res;} +__attribute__((noinline)) void *malloc_bbb(size_t size) { + void *res = malloc_ccc(size); break_optimization(0); return res;} +__attribute__((noinline)) void *malloc_aaa(size_t size) { + void *res = malloc_bbb(size); break_optimization(0); return res;} + +#ifndef __APPLE__ +__attribute__((noinline)) void *memalign_fff(size_t alignment, size_t size) { + void *res = memalign/**/(alignment, size); break_optimization(0); return res;} +__attribute__((noinline)) void *memalign_eee(size_t alignment, size_t size) { + void *res = memalign_fff(alignment, size); break_optimization(0); return res;} +__attribute__((noinline)) void *memalign_ddd(size_t alignment, size_t size) { + void *res = memalign_eee(alignment, size); break_optimization(0); return res;} +__attribute__((noinline)) void *memalign_ccc(size_t alignment, size_t size) { + void *res = memalign_ddd(alignment, size); break_optimization(0); return res;} +__attribute__((noinline)) void *memalign_bbb(size_t alignment, size_t size) { + void *res = memalign_ccc(alignment, size); break_optimization(0); return res;} +__attribute__((noinline)) void *memalign_aaa(size_t alignment, size_t size) { + void *res = memalign_bbb(alignment, size); break_optimization(0); return res;} +#endif // __APPLE__ + + +__attribute__((noinline)) + void free_ccc(void *p) { free(p); break_optimization(0);} +__attribute__((noinline)) + void free_bbb(void *p) { free_ccc(p); break_optimization(0);} +__attribute__((noinline)) + void free_aaa(void *p) { free_bbb(p); break_optimization(0);} + +template<class T> +__attribute__((noinline)) +void oob_test(int size, int off) { + char *p = (char*)malloc_aaa(size); + // fprintf(stderr, "writing %d byte(s) into [%p,%p) with offset %d\n", + // sizeof(T), p, p + size, off); + asan_write((T*)(p + off)); + free_aaa(p); +} + + +template<class T> +__attribute__((noinline)) +void uaf_test(int size, int off) { + char *p = (char *)malloc_aaa(size); + free_aaa(p); + for (int i = 1; i < 100; i++) + free_aaa(malloc_aaa(i)); + fprintf(stderr, "writing %ld byte(s) at %p with offset %d\n", + (long)sizeof(T), p, off); + asan_write((T*)(p + off)); +} + +TEST(AddressSanitizer, HasFeatureAddressSanitizerTest) { +#if defined(__has_feature) && __has_feature(address_sanitizer) + bool asan = 1; +#else + bool asan = 0; +#endif + EXPECT_EQ(true, asan); +} + +TEST(AddressSanitizer, SimpleDeathTest) { + EXPECT_DEATH(exit(1), ""); +} + +TEST(AddressSanitizer, VariousMallocsTest) { + // fprintf(stderr, "malloc:\n"); + int *a = (int*)malloc(100 * sizeof(int)); + a[50] = 0; + free(a); + + // fprintf(stderr, "realloc:\n"); + int *r = (int*)malloc(10); + r = (int*)realloc(r, 2000 * sizeof(int)); + r[1000] = 0; + free(r); + + // fprintf(stderr, "operator new []\n"); + int *b = new int[100]; + b[50] = 0; + delete [] b; + + // fprintf(stderr, "operator new\n"); + int *c = new int; + *c = 0; + delete c; + +#ifndef __APPLE__ + // fprintf(stderr, "posix_memalign\n"); + int *pm; + int pm_res = posix_memalign((void**)&pm, kPageSize, kPageSize); + EXPECT_EQ(0, pm_res); + free(pm); + + int *ma = (int*)memalign(kPageSize, kPageSize); + EXPECT_EQ(0, (uintptr_t)ma % kPageSize); + ma[123] = 0; + free(ma); +#endif // __APPLE__ +} + +TEST(AddressSanitizer, CallocTest) { + int *a = (int*)calloc(100, sizeof(int)); + EXPECT_EQ(0, a[10]); + free(a); +} + +TEST(AddressSanitizer, VallocTest) { + void *a = valloc(100); + EXPECT_EQ(0, (uintptr_t)a % kPageSize); + free(a); +} + +#ifndef __APPLE__ +TEST(AddressSanitizer, PvallocTest) { + char *a = (char*)pvalloc(kPageSize + 100); + EXPECT_EQ(0, (uintptr_t)a % kPageSize); + a[kPageSize + 101] = 1; // we should not report an error here. + free(a); + + a = (char*)pvalloc(0); // pvalloc(0) should allocate at least one page. + EXPECT_EQ(0, (uintptr_t)a % kPageSize); + a[101] = 1; // we should not report an error here. + free(a); +} +#endif // __APPLE__ + +void NoOpSignalHandler(int unused) { + fprintf(stderr, "NoOpSignalHandler (should not happen). Aborting\n"); + abort(); +} + +void NoOpSigaction(int, siginfo_t *siginfo, void *context) { + fprintf(stderr, "NoOpSigaction (should not happen). Aborting\n"); + abort(); +} + +TEST(AddressSanitizer, SignalTest) { + signal(SIGSEGV, NoOpSignalHandler); + signal(SIGILL, NoOpSignalHandler); + // If asan did not intercept sigaction NoOpSigaction will fire. + char *x = Ident((char*)malloc(5)); + EXPECT_DEATH(x[6]++, "is located 1 bytes to the right"); + free(Ident(x)); +} + +TEST(AddressSanitizer, SigactionTest) { + { + struct sigaction sigact; + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_sigaction = NoOpSigaction;; + sigact.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sigact, 0); + } + + { + struct sigaction sigact; + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_sigaction = NoOpSigaction;; + sigact.sa_flags = SA_SIGINFO; + sigaction(SIGILL, &sigact, 0); + } + + // If asan did not intercept sigaction NoOpSigaction will fire. + char *x = Ident((char*)malloc(5)); + EXPECT_DEATH(x[6]++, "is located 1 bytes to the right"); + free(Ident(x)); +} + +void *TSDWorker(void *test_key) { + if (test_key) { + pthread_setspecific(*(pthread_key_t*)test_key, (void*)0xfeedface); + } + return NULL; +} + +void TSDDestructor(void *tsd) { + // Spawning a thread will check that the current thread id is not -1. + pthread_t th; + pthread_create(&th, NULL, TSDWorker, NULL); + pthread_join(th, NULL); +} + +// This tests triggers the thread-specific data destruction fiasco which occurs +// if we don't manage the TSD destructors ourselves. We create a new pthread +// key with a non-NULL destructor which is likely to be put after the destructor +// of AsanThread in the list of destructors. +// In this case the TSD for AsanThread will be destroyed before TSDDestructor +// is called for the child thread, and a CHECK will fail when we call +// pthread_create() to spawn the grandchild. +TEST(AddressSanitizer, DISABLED_TSDTest) { + pthread_t th; + pthread_key_t test_key; + pthread_key_create(&test_key, TSDDestructor); + pthread_create(&th, NULL, TSDWorker, &test_key); + pthread_join(th, NULL); + pthread_key_delete(test_key); +} + +template<class T> +void OOBTest() { + char expected_str[100]; + for (int size = sizeof(T); size < 20; size += 5) { + for (int i = -5; i < 0; i++) { + const char *str = + "is located.*%d byte.*to the left"; + sprintf(expected_str, str, abs(i)); + EXPECT_DEATH(oob_test<T>(size, i), expected_str); + } + + for (int i = 0; i < size - sizeof(T) + 1; i++) + oob_test<T>(size, i); + + for (int i = size - sizeof(T) + 1; i <= size + 3 * sizeof(T); i++) { + const char *str = + "is located.*%d byte.*to the right"; + int off = i >= size ? (i - size) : 0; + // we don't catch unaligned partially OOB accesses. + if (i % sizeof(T)) continue; + sprintf(expected_str, str, off); + EXPECT_DEATH(oob_test<T>(size, i), expected_str); + } + } + + EXPECT_DEATH(oob_test<T>(kLargeMalloc, -1), + "is located.*1 byte.*to the left"); + EXPECT_DEATH(oob_test<T>(kLargeMalloc, kLargeMalloc), + "is located.*0 byte.*to the right"); +} + +// TODO(glider): the following tests are EXTREMELY slow on Darwin: +// AddressSanitizer.OOB_char (125503 ms) +// AddressSanitizer.OOB_int (126890 ms) +// AddressSanitizer.OOBRightTest (315605 ms) +// AddressSanitizer.SimpleStackTest (366559 ms) + +TEST(AddressSanitizer, OOB_char) { + OOBTest<U1>(); +} + +TEST(AddressSanitizer, OOB_int) { + OOBTest<U4>(); +} + +TEST(AddressSanitizer, OOBRightTest) { + for (size_t access_size = 1; access_size <= 8; access_size *= 2) { + for (size_t alloc_size = 1; alloc_size <= 8; alloc_size++) { + for (size_t offset = 0; offset <= 8; offset += access_size) { + void *p = malloc(alloc_size); + // allocated: [p, p + alloc_size) + // accessed: [p + offset, p + offset + access_size) + uint8_t *addr = (uint8_t*)p + offset; + if (offset + access_size <= alloc_size) { + asan_write_sized_aligned(addr, access_size); + } else { + int outside_bytes = offset > alloc_size ? (offset - alloc_size) : 0; + const char *str = + "is located.%d *byte.*to the right"; + char expected_str[100]; + sprintf(expected_str, str, outside_bytes); + EXPECT_DEATH(asan_write_sized_aligned(addr, access_size), + expected_str); + } + free(p); + } + } + } +} + +TEST(AddressSanitizer, UAF_char) { + const char *uaf_string = "AddressSanitizer.*heap-use-after-free"; + EXPECT_DEATH(uaf_test<U1>(1, 0), uaf_string); + EXPECT_DEATH(uaf_test<U1>(10, 0), uaf_string); + EXPECT_DEATH(uaf_test<U1>(10, 10), uaf_string); + EXPECT_DEATH(uaf_test<U1>(kLargeMalloc, 0), uaf_string); + EXPECT_DEATH(uaf_test<U1>(kLargeMalloc, kLargeMalloc / 2), uaf_string); +} + +#if ASAN_HAS_BLACKLIST +TEST(AddressSanitizer, IgnoreTest) { + int *x = Ident(new int); + delete Ident(x); + *x = 0; +} +#endif // ASAN_HAS_BLACKLIST + +struct StructWithBitField { + int bf1:1; + int bf2:1; + int bf3:1; + int bf4:29; +}; + +TEST(AddressSanitizer, BitFieldPositiveTest) { + StructWithBitField *x = new StructWithBitField; + delete Ident(x); + EXPECT_DEATH(x->bf1 = 0, "use-after-free"); + EXPECT_DEATH(x->bf2 = 0, "use-after-free"); + EXPECT_DEATH(x->bf3 = 0, "use-after-free"); + EXPECT_DEATH(x->bf4 = 0, "use-after-free"); +}; + +struct StructWithBitFields_8_24 { + int a:8; + int b:24; +}; + +TEST(AddressSanitizer, BitFieldNegativeTest) { + StructWithBitFields_8_24 *x = Ident(new StructWithBitFields_8_24); + x->a = 0; + x->b = 0; + delete Ident(x); +} + +TEST(AddressSanitizer, OutOfMemoryTest) { + size_t size = __WORDSIZE == 64 ? (size_t)(1ULL << 48) : (0xf0000000); + EXPECT_EQ(0, realloc(0, size)); + EXPECT_EQ(0, realloc(0, ~Ident(0))); + EXPECT_EQ(0, malloc(size)); + EXPECT_EQ(0, malloc(~Ident(0))); + EXPECT_EQ(0, calloc(1, size)); + EXPECT_EQ(0, calloc(1, ~Ident(0))); +} + +#if ASAN_NEEDS_SEGV +TEST(AddressSanitizer, WildAddressTest) { + char *c = (char*)0x123; + EXPECT_DEATH(*c = 0, "AddressSanitizer crashed on unknown address"); +} +#endif + +static void MallocStress(size_t n) { + uint32_t seed = my_rand(&global_seed); + for (size_t iter = 0; iter < 10; iter++) { + vector<void *> vec; + for (size_t i = 0; i < n; i++) { + if ((i % 3) == 0) { + if (vec.empty()) continue; + size_t idx = my_rand(&seed) % vec.size(); + void *ptr = vec[idx]; + vec[idx] = vec.back(); + vec.pop_back(); + free_aaa(ptr); + } else { + size_t size = my_rand(&seed) % 1000 + 1; +#ifndef __APPLE__ + size_t alignment = 1 << (my_rand(&seed) % 7 + 3); + char *ptr = (char*)memalign_aaa(alignment, size); +#else + char *ptr = (char*) malloc_aaa(size); +#endif + vec.push_back(ptr); + ptr[0] = 0; + ptr[size-1] = 0; + ptr[size/2] = 0; + } + } + for (size_t i = 0; i < vec.size(); i++) + free_aaa(vec[i]); + } +} + +TEST(AddressSanitizer, MallocStressTest) { + MallocStress(200000); +} + +static void TestLargeMalloc(size_t size) { + char buff[1024]; + sprintf(buff, "is located 1 bytes to the left of %lu-byte", (long)size); + EXPECT_DEATH(Ident((char*)malloc(size))[-1] = 0, buff); +} + +TEST(AddressSanitizer, LargeMallocTest) { + for (int i = 113; i < (1 << 28); i = i * 2 + 13) { + TestLargeMalloc(i); + } +} + +TEST(AddressSanitizer, HugeMallocTest) { +#ifdef __APPLE__ + // It was empirically found out that 1215 megabytes is the maximum amount of + // memory available to the process under AddressSanitizer on Darwin. + // (the libSystem malloc() allows allocating up to 2300 megabytes without + // ASan). + size_t n_megs = __WORDSIZE == 32 ? 1200 : 4100; +#else + size_t n_megs = __WORDSIZE == 32 ? 2600 : 4100; +#endif + TestLargeMalloc(n_megs << 20); +} + +TEST(AddressSanitizer, ThreadedMallocStressTest) { + const int kNumThreads = 4; + pthread_t t[kNumThreads]; + for (int i = 0; i < kNumThreads; i++) { + pthread_create(&t[i], 0, (void* (*)(void *x))MallocStress, (void*)100000); + } + for (int i = 0; i < kNumThreads; i++) { + pthread_join(t[i], 0); + } +} + +void *ManyThreadsWorker(void *a) { + for (int iter = 0; iter < 100; iter++) { + for (size_t size = 100; size < 2000; size *= 2) { + free(Ident(malloc(size))); + } + } + return 0; +} + +TEST(AddressSanitizer, ManyThreadsTest) { + const size_t kNumThreads = __WORDSIZE == 32 ? 30 : 1000; + pthread_t t[kNumThreads]; + for (size_t i = 0; i < kNumThreads; i++) { + pthread_create(&t[i], 0, (void* (*)(void *x))ManyThreadsWorker, (void*)i); + } + for (size_t i = 0; i < kNumThreads; i++) { + pthread_join(t[i], 0); + } +} + +TEST(AddressSanitizer, ReallocTest) { + const int kMinElem = 5; + int *ptr = (int*)malloc(sizeof(int) * kMinElem); + ptr[3] = 3; + for (int i = 0; i < 10000; i++) { + ptr = (int*)realloc(ptr, + (my_rand(&global_seed) % 1000 + kMinElem) * sizeof(int)); + EXPECT_EQ(3, ptr[3]); + } +} + +void WrongFree() { + int *x = (int*)malloc(100 * sizeof(int)); + // Use the allocated memory, otherwise Clang will optimize it out. + Ident(x); + free(x + 1); +} + +TEST(AddressSanitizer, WrongFreeTest) { + EXPECT_DEATH(WrongFree(), + "ERROR: AddressSanitizer attempting free.*not malloc"); +} + +void DoubleFree() { + int *x = (int*)malloc(100 * sizeof(int)); + fprintf(stderr, "DoubleFree: x=%p\n", x); + free(x); + free(x); + fprintf(stderr, "should have failed in the second free(%p)\n", x); + abort(); +} + +TEST(AddressSanitizer, DoubleFreeTest) { + EXPECT_DEATH(DoubleFree(), "ERROR: AddressSanitizer attempting double-free"); +} + +template<int kSize> +__attribute__((noinline)) +void SizedStackTest() { + char a[kSize]; + char *A = Ident((char*)&a); + for (size_t i = 0; i < kSize; i++) + A[i] = i; + EXPECT_DEATH(A[-1] = 0, ""); + EXPECT_DEATH(A[-20] = 0, ""); + EXPECT_DEATH(A[-31] = 0, ""); + EXPECT_DEATH(A[kSize] = 0, ""); + EXPECT_DEATH(A[kSize + 1] = 0, ""); + EXPECT_DEATH(A[kSize + 10] = 0, ""); + EXPECT_DEATH(A[kSize + 31] = 0, ""); +} + +TEST(AddressSanitizer, SimpleStackTest) { + SizedStackTest<1>(); + SizedStackTest<2>(); + SizedStackTest<3>(); + SizedStackTest<4>(); + SizedStackTest<5>(); + SizedStackTest<6>(); + SizedStackTest<7>(); + SizedStackTest<16>(); + SizedStackTest<25>(); + SizedStackTest<34>(); + SizedStackTest<43>(); + SizedStackTest<51>(); + SizedStackTest<62>(); + SizedStackTest<64>(); + SizedStackTest<128>(); +} + +TEST(AddressSanitizer, ManyStackObjectsTest) { + char XXX[10]; + char YYY[20]; + char ZZZ[30]; + Ident(XXX); + Ident(YYY); + EXPECT_DEATH(Ident(ZZZ)[-1] = 0, ASAN_PCRE_DOTALL "XXX.*YYY.*ZZZ"); +} + +__attribute__((noinline)) +static void Frame0(int frame, char *a, char *b, char *c) { + char d[4] = {0}; + char *D = Ident(d); + switch (frame) { + case 3: a[5]++; break; + case 2: b[5]++; break; + case 1: c[5]++; break; + case 0: D[5]++; break; + } +} +__attribute__((noinline)) static void Frame1(int frame, char *a, char *b) { + char c[4] = {0}; Frame0(frame, a, b, c); + break_optimization(0); +} +__attribute__((noinline)) static void Frame2(int frame, char *a) { + char b[4] = {0}; Frame1(frame, a, b); + break_optimization(0); +} +__attribute__((noinline)) static void Frame3(int frame) { + char a[4] = {0}; Frame2(frame, a); + break_optimization(0); +} + +TEST(AddressSanitizer, GuiltyStackFrame0Test) { + EXPECT_DEATH(Frame3(0), "located .*in frame <.*Frame0"); +} +TEST(AddressSanitizer, GuiltyStackFrame1Test) { + EXPECT_DEATH(Frame3(1), "located .*in frame <.*Frame1"); +} +TEST(AddressSanitizer, GuiltyStackFrame2Test) { + EXPECT_DEATH(Frame3(2), "located .*in frame <.*Frame2"); +} +TEST(AddressSanitizer, GuiltyStackFrame3Test) { + EXPECT_DEATH(Frame3(3), "located .*in frame <.*Frame3"); +} + +__attribute__((noinline)) +void LongJmpFunc1(jmp_buf buf) { + // create three red zones for these two stack objects. + int a; + int b; + + int *A = Ident(&a); + int *B = Ident(&b); + *A = *B; + longjmp(buf, 1); +} + +__attribute__((noinline)) +void UnderscopeLongJmpFunc1(jmp_buf buf) { + // create three red zones for these two stack objects. + int a; + int b; + + int *A = Ident(&a); + int *B = Ident(&b); + *A = *B; + _longjmp(buf, 1); +} + +__attribute__((noinline)) +void SigLongJmpFunc1(sigjmp_buf buf) { + // create three red zones for these two stack objects. + int a; + int b; + + int *A = Ident(&a); + int *B = Ident(&b); + *A = *B; + siglongjmp(buf, 1); +} + + +__attribute__((noinline)) +void TouchStackFunc() { + int a[100]; // long array will intersect with redzones from LongJmpFunc1. + int *A = Ident(a); + for (int i = 0; i < 100; i++) + A[i] = i*i; +} + +// Test that we handle longjmp and do not report fals positives on stack. +TEST(AddressSanitizer, LongJmpTest) { + static jmp_buf buf; + if (!setjmp(buf)) { + LongJmpFunc1(buf); + } else { + TouchStackFunc(); + } +} + +TEST(AddressSanitizer, UnderscopeLongJmpTest) { + static jmp_buf buf; + if (!_setjmp(buf)) { + UnderscopeLongJmpFunc1(buf); + } else { + TouchStackFunc(); + } +} + +TEST(AddressSanitizer, SigLongJmpTest) { + static sigjmp_buf buf; + if (!sigsetjmp(buf, 1)) { + SigLongJmpFunc1(buf); + } else { + TouchStackFunc(); + } +} + +#ifdef __EXCEPTIONS +__attribute__((noinline)) +void ThrowFunc() { + // create three red zones for these two stack objects. + int a; + int b; + + int *A = Ident(&a); + int *B = Ident(&b); + *A = *B; + ASAN_THROW(1); +} + +TEST(AddressSanitizer, CxxExceptionTest) { + if (ASAN_UAR) return; + // TODO(kcc): this test crashes on 32-bit for some reason... + if (__WORDSIZE == 32) return; + try { + ThrowFunc(); + } catch(...) {} + TouchStackFunc(); +} +#endif + +void *ThreadStackReuseFunc1(void *unused) { + // create three red zones for these two stack objects. + int a; + int b; + + int *A = Ident(&a); + int *B = Ident(&b); + *A = *B; + pthread_exit(0); + return 0; +} + +void *ThreadStackReuseFunc2(void *unused) { + TouchStackFunc(); + return 0; +} + +TEST(AddressSanitizer, ThreadStackReuseTest) { + pthread_t t; + pthread_create(&t, 0, ThreadStackReuseFunc1, 0); + pthread_join(t, 0); + pthread_create(&t, 0, ThreadStackReuseFunc2, 0); + pthread_join(t, 0); +} + +#if defined(__i386__) or defined(__x86_64__) +TEST(AddressSanitizer, Store128Test) { + char *a = Ident((char*)malloc(Ident(12))); + char *p = a; + if (((uintptr_t)a % 16) != 0) + p = a + 8; + assert(((uintptr_t)p % 16) == 0); + __m128i value_wide = _mm_set1_epi16(0x1234); + EXPECT_DEATH(_mm_store_si128((__m128i*)p, value_wide), + "AddressSanitizer heap-buffer-overflow"); + EXPECT_DEATH(_mm_store_si128((__m128i*)p, value_wide), + "WRITE of size 16"); + EXPECT_DEATH(_mm_store_si128((__m128i*)p, value_wide), + "located 0 bytes to the right of 12-byte"); + free(a); +} +#endif + +static string RightOOBErrorMessage(int oob_distance) { + assert(oob_distance >= 0); + char expected_str[100]; + sprintf(expected_str, "located %d bytes to the right", oob_distance); + return string(expected_str); +} + +static string LeftOOBErrorMessage(int oob_distance) { + assert(oob_distance > 0); + char expected_str[100]; + sprintf(expected_str, "located %d bytes to the left", oob_distance); + return string(expected_str); +} + +template<class T> +void MemSetOOBTestTemplate(size_t length) { + if (length == 0) return; + size_t size = Ident(sizeof(T) * length); + T *array = Ident((T*)malloc(size)); + int element = Ident(42); + int zero = Ident(0); + // memset interval inside array + memset(array, element, size); + memset(array, element, size - 1); + memset(array + length - 1, element, sizeof(T)); + memset(array, element, 1); + + // memset 0 bytes + memset(array - 10, element, zero); + memset(array - 1, element, zero); + memset(array, element, zero); + memset(array + length, 0, zero); + memset(array + length + 1, 0, zero); + + // try to memset bytes to the right of array + EXPECT_DEATH(memset(array, 0, size + 1), + RightOOBErrorMessage(0)); + EXPECT_DEATH(memset((char*)(array + length) - 1, element, 6), + RightOOBErrorMessage(4)); + EXPECT_DEATH(memset(array + 1, element, size + sizeof(T)), + RightOOBErrorMessage(2 * sizeof(T) - 1)); + // whole interval is to the right + EXPECT_DEATH(memset(array + length + 1, 0, 10), + RightOOBErrorMessage(sizeof(T))); + + // try to memset bytes to the left of array + EXPECT_DEATH(memset((char*)array - 1, element, size), + LeftOOBErrorMessage(1)); + EXPECT_DEATH(memset((char*)array - 5, 0, 6), + LeftOOBErrorMessage(5)); + EXPECT_DEATH(memset(array - 5, element, size + 5 * sizeof(T)), + LeftOOBErrorMessage(5 * sizeof(T))); + // whole interval is to the left + EXPECT_DEATH(memset(array - 2, 0, sizeof(T)), + LeftOOBErrorMessage(2 * sizeof(T))); + + // try to memset bytes both to the left & to the right + EXPECT_DEATH(memset((char*)array - 2, element, size + 4), + LeftOOBErrorMessage(2)); + + free(array); +} + +TEST(AddressSanitizer, MemSetOOBTest) { + MemSetOOBTestTemplate<char>(100); + MemSetOOBTestTemplate<int>(5); + MemSetOOBTestTemplate<double>(256); + // We can test arrays of structres/classes here, but what for? +} + +// Same test for memcpy and memmove functions +template <class T, class M> +void MemTransferOOBTestTemplate(size_t length) { + if (length == 0) return; + size_t size = Ident(sizeof(T) * length); + T *src = Ident((T*)malloc(size)); + T *dest = Ident((T*)malloc(size)); + int zero = Ident(0); + + // valid transfer of bytes between arrays + M::transfer(dest, src, size); + M::transfer(dest + 1, src, size - sizeof(T)); + M::transfer(dest, src + length - 1, sizeof(T)); + M::transfer(dest, src, 1); + + // transfer zero bytes + M::transfer(dest - 1, src, 0); + M::transfer(dest + length, src, zero); + M::transfer(dest, src - 1, zero); + M::transfer(dest, src, zero); + + // try to change mem to the right of dest + EXPECT_DEATH(M::transfer(dest + 1, src, size), + RightOOBErrorMessage(sizeof(T) - 1)); + EXPECT_DEATH(M::transfer((char*)(dest + length) - 1, src, 5), + RightOOBErrorMessage(3)); + + // try to change mem to the left of dest + EXPECT_DEATH(M::transfer(dest - 2, src, size), + LeftOOBErrorMessage(2 * sizeof(T))); + EXPECT_DEATH(M::transfer((char*)dest - 3, src, 4), + LeftOOBErrorMessage(3)); + + // try to access mem to the right of src + EXPECT_DEATH(M::transfer(dest, src + 2, size), + RightOOBErrorMessage(2 * sizeof(T) - 1)); + EXPECT_DEATH(M::transfer(dest, (char*)(src + length) - 3, 6), + RightOOBErrorMessage(2)); + + // try to access mem to the left of src + EXPECT_DEATH(M::transfer(dest, src - 1, size), + LeftOOBErrorMessage(sizeof(T))); + EXPECT_DEATH(M::transfer(dest, (char*)src - 6, 7), + LeftOOBErrorMessage(6)); + + // Generally we don't need to test cases where both accessing src and writing + // to dest address to poisoned memory. + + T *big_src = Ident((T*)malloc(size * 2)); + T *big_dest = Ident((T*)malloc(size * 2)); + // try to change mem to both sides of dest + EXPECT_DEATH(M::transfer(dest - 1, big_src, size * 2), + LeftOOBErrorMessage(sizeof(T))); + // try to access mem to both sides of src + EXPECT_DEATH(M::transfer(big_dest, src - 2, size * 2), + LeftOOBErrorMessage(2 * sizeof(T))); + + free(src); + free(dest); + free(big_src); + free(big_dest); +} + +class MemCpyWrapper { + public: + static void* transfer(void *to, const void *from, size_t size) { + return memcpy(to, from, size); + } +}; +TEST(AddressSanitizer, MemCpyOOBTest) { + MemTransferOOBTestTemplate<char, MemCpyWrapper>(100); + MemTransferOOBTestTemplate<int, MemCpyWrapper>(1024); +} + +class MemMoveWrapper { + public: + static void* transfer(void *to, const void *from, size_t size) { + return memmove(to, from, size); + } +}; +TEST(AddressSanitizer, MemMoveOOBTest) { + MemTransferOOBTestTemplate<char, MemMoveWrapper>(100); + MemTransferOOBTestTemplate<int, MemMoveWrapper>(1024); +} + +// Tests for string functions + +// Used for string functions tests +static char global_string[] = "global"; +static size_t global_string_length = 6; + +// Input to a test is a zero-terminated string str with given length +// Accesses to the bytes to the left and to the right of str +// are presumed to produce OOB errors +void StrLenOOBTestTemplate(char *str, size_t length, bool is_global) { + // Normal strlen calls + EXPECT_EQ(strlen(str), length); + if (length > 0) { + EXPECT_EQ(strlen(str + 1), length - 1); + EXPECT_EQ(strlen(str + length), 0); + } + // Arg of strlen is not malloced, OOB access + if (!is_global) { + // We don't insert RedZones to the left of global variables + EXPECT_DEATH(Ident(strlen(str - 1)), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(strlen(str - 5)), LeftOOBErrorMessage(5)); + } + EXPECT_DEATH(Ident(strlen(str + length + 1)), RightOOBErrorMessage(0)); + // Overwrite terminator + str[length] = 'a'; + // String is not zero-terminated, strlen will lead to OOB access + EXPECT_DEATH(Ident(strlen(str)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strlen(str + length)), RightOOBErrorMessage(0)); + // Restore terminator + str[length] = 0; +} +TEST(AddressSanitizer, StrLenOOBTest) { + // Check heap-allocated string + size_t length = Ident(10); + char *heap_string = Ident((char*)malloc(length + 1)); + char stack_string[10 + 1]; + for (int i = 0; i < length; i++) { + heap_string[i] = 'a'; + stack_string[i] = 'b'; + } + heap_string[length] = 0; + stack_string[length] = 0; + StrLenOOBTestTemplate(heap_string, length, false); + // TODO(samsonov): Fix expected messages in StrLenOOBTestTemplate to + // make test for stack_string work. Or move it to output tests. + // StrLenOOBTestTemplate(stack_string, length, false); + StrLenOOBTestTemplate(global_string, global_string_length, true); + free(heap_string); +} + +static inline char* MallocAndMemsetString(size_t size) { + char *s = Ident((char*)malloc(size)); + memset(s, 'z', size); + return s; +} + +#ifndef __APPLE__ +TEST(AddressSanitizer, StrNLenOOBTest) { + size_t size = Ident(123); + char *str = MallocAndMemsetString(size); + // Normal strnlen calls. + Ident(strnlen(str - 1, 0)); + Ident(strnlen(str, size)); + Ident(strnlen(str + size - 1, 1)); + str[size - 1] = '\0'; + Ident(strnlen(str, 2 * size)); + // Argument points to not allocated memory. + EXPECT_DEATH(Ident(strnlen(str - 1, 1)), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(strnlen(str + size, 1)), RightOOBErrorMessage(0)); + // Overwrite the terminating '\0' and hit unallocated memory. + str[size - 1] = 'z'; + EXPECT_DEATH(Ident(strnlen(str, size + 1)), RightOOBErrorMessage(0)); + free(str); +} +#endif + +TEST(AddressSanitizer, StrDupOOBTest) { + size_t size = Ident(42); + char *str = MallocAndMemsetString(size); + char *new_str; + // Normal strdup calls. + str[size - 1] = '\0'; + new_str = strdup(str); + free(new_str); + new_str = strdup(str + size - 1); + free(new_str); + // Argument points to not allocated memory. + EXPECT_DEATH(Ident(strdup(str - 1)), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(strdup(str + size)), RightOOBErrorMessage(0)); + // Overwrite the terminating '\0' and hit unallocated memory. + str[size - 1] = 'z'; + EXPECT_DEATH(Ident(strdup(str)), RightOOBErrorMessage(0)); + free(str); +} + +TEST(AddressSanitizer, StrCpyOOBTest) { + size_t to_size = Ident(30); + size_t from_size = Ident(6); // less than to_size + char *to = Ident((char*)malloc(to_size)); + char *from = Ident((char*)malloc(from_size)); + // Normal strcpy calls. + strcpy(from, "hello"); + strcpy(to, from); + strcpy(to + to_size - from_size, from); + // Length of "from" is too small. + EXPECT_DEATH(Ident(strcpy(from, "hello2")), RightOOBErrorMessage(0)); + // "to" or "from" points to not allocated memory. + EXPECT_DEATH(Ident(strcpy(to - 1, from)), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(strcpy(to, from - 1)), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(strcpy(to, from + from_size)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strcpy(to + to_size, from)), RightOOBErrorMessage(0)); + // Overwrite the terminating '\0' character and hit unallocated memory. + from[from_size - 1] = '!'; + EXPECT_DEATH(Ident(strcpy(to, from)), RightOOBErrorMessage(0)); + free(to); + free(from); +} + +TEST(AddressSanitizer, StrNCpyOOBTest) { + size_t to_size = Ident(20); + size_t from_size = Ident(6); // less than to_size + char *to = Ident((char*)malloc(to_size)); + // From is a zero-terminated string "hello\0" of length 6 + char *from = Ident((char*)malloc(from_size)); + strcpy(from, "hello"); + // copy 0 bytes + strncpy(to, from, 0); + strncpy(to - 1, from - 1, 0); + // normal strncpy calls + strncpy(to, from, from_size); + strncpy(to, from, to_size); + strncpy(to, from + from_size - 1, to_size); + strncpy(to + to_size - 1, from, 1); + // One of {to, from} points to not allocated memory + EXPECT_DEATH(Ident(strncpy(to, from - 1, from_size)), + LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(strncpy(to - 1, from, from_size)), + LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(strncpy(to, from + from_size, 1)), + RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strncpy(to + to_size, from, 1)), + RightOOBErrorMessage(0)); + // Length of "to" is too small + EXPECT_DEATH(Ident(strncpy(to + to_size - from_size + 1, from, from_size)), + RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strncpy(to + 1, from, to_size)), + RightOOBErrorMessage(0)); + // Overwrite terminator in from + from[from_size - 1] = '!'; + // normal strncpy call + strncpy(to, from, from_size); + // Length of "from" is too small + EXPECT_DEATH(Ident(strncpy(to, from, to_size)), + RightOOBErrorMessage(0)); + free(to); + free(from); +} + +typedef char*(*PointerToStrChr)(const char*, int); +void RunStrChrTest(PointerToStrChr StrChr) { + size_t size = Ident(100); + char *str = MallocAndMemsetString(size); + str[10] = 'q'; + str[11] = '\0'; + EXPECT_EQ(str, StrChr(str, 'z')); + EXPECT_EQ(str + 10, StrChr(str, 'q')); + EXPECT_EQ(NULL, StrChr(str, 'a')); + // StrChr argument points to not allocated memory. + EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBErrorMessage(0)); + // Overwrite the terminator and hit not allocated memory. + str[11] = 'z'; + EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBErrorMessage(0)); + free(str); +} +TEST(AddressSanitizer, StrChrAndIndexOOBTest) { + RunStrChrTest(&strchr); + RunStrChrTest(&index); +} + +TEST(AddressSanitizer, StrCmpAndFriendsLogicTest) { + // strcmp + EXPECT_EQ(0, strcmp("", "")); + EXPECT_EQ(0, strcmp("abcd", "abcd")); + EXPECT_GT(0, strcmp("ab", "ac")); + EXPECT_GT(0, strcmp("abc", "abcd")); + EXPECT_LT(0, strcmp("acc", "abc")); + EXPECT_LT(0, strcmp("abcd", "abc")); + + // strncmp + EXPECT_EQ(0, strncmp("a", "b", 0)); + EXPECT_EQ(0, strncmp("abcd", "abcd", 10)); + EXPECT_EQ(0, strncmp("abcd", "abcef", 3)); + EXPECT_GT(0, strncmp("abcde", "abcfa", 4)); + EXPECT_GT(0, strncmp("a", "b", 5)); + EXPECT_GT(0, strncmp("bc", "bcde", 4)); + EXPECT_LT(0, strncmp("xyz", "xyy", 10)); + EXPECT_LT(0, strncmp("baa", "aaa", 1)); + EXPECT_LT(0, strncmp("zyx", "", 2)); + + // strcasecmp + EXPECT_EQ(0, strcasecmp("", "")); + EXPECT_EQ(0, strcasecmp("zzz", "zzz")); + EXPECT_EQ(0, strcasecmp("abCD", "ABcd")); + EXPECT_GT(0, strcasecmp("aB", "Ac")); + EXPECT_GT(0, strcasecmp("ABC", "ABCd")); + EXPECT_LT(0, strcasecmp("acc", "abc")); + EXPECT_LT(0, strcasecmp("ABCd", "abc")); + + // strncasecmp + EXPECT_EQ(0, strncasecmp("a", "b", 0)); + EXPECT_EQ(0, strncasecmp("abCD", "ABcd", 10)); + EXPECT_EQ(0, strncasecmp("abCd", "ABcef", 3)); + EXPECT_GT(0, strncasecmp("abcde", "ABCfa", 4)); + EXPECT_GT(0, strncasecmp("a", "B", 5)); + EXPECT_GT(0, strncasecmp("bc", "BCde", 4)); + EXPECT_LT(0, strncasecmp("xyz", "xyy", 10)); + EXPECT_LT(0, strncasecmp("Baa", "aaa", 1)); + EXPECT_LT(0, strncasecmp("zyx", "", 2)); + + // memcmp + EXPECT_EQ(0, memcmp("a", "b", 0)); + EXPECT_EQ(0, memcmp("ab\0c", "ab\0c", 4)); + EXPECT_GT(0, memcmp("\0ab", "\0ac", 3)); + EXPECT_GT(0, memcmp("abb\0", "abba", 4)); + EXPECT_LT(0, memcmp("ab\0cd", "ab\0c\0", 5)); + EXPECT_LT(0, memcmp("zza", "zyx", 3)); +} + +typedef int(*PointerToStrCmp)(const char*, const char*); +void RunStrCmpTest(PointerToStrCmp StrCmp) { + size_t size = Ident(100); + char *s1 = MallocAndMemsetString(size); + char *s2 = MallocAndMemsetString(size); + s1[size - 1] = '\0'; + s2[size - 1] = '\0'; + // Normal StrCmp calls + Ident(StrCmp(s1, s2)); + Ident(StrCmp(s1, s2 + size - 1)); + Ident(StrCmp(s1 + size - 1, s2 + size - 1)); + s1[size - 1] = 'z'; + s2[size - 1] = 'x'; + Ident(StrCmp(s1, s2)); + // One of arguments points to not allocated memory. + EXPECT_DEATH(Ident(StrCmp)(s1 - 1, s2), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(StrCmp)(s1, s2 - 1), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(StrCmp)(s1 + size, s2), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrCmp)(s1, s2 + size), RightOOBErrorMessage(0)); + // Hit unallocated memory and die. + s2[size - 1] = 'z'; + EXPECT_DEATH(Ident(StrCmp)(s1, s1), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrCmp)(s1 + size - 1, s2), RightOOBErrorMessage(0)); + free(s1); + free(s2); +} + +TEST(AddressSanitizer, StrCmpOOBTest) { + RunStrCmpTest(&strcmp); +} + +TEST(AddressSanitizer, StrCaseCmpOOBTest) { + RunStrCmpTest(&strcasecmp); +} + +typedef int(*PointerToStrNCmp)(const char*, const char*, size_t); +void RunStrNCmpTest(PointerToStrNCmp StrNCmp) { + size_t size = Ident(100); + char *s1 = MallocAndMemsetString(size); + char *s2 = MallocAndMemsetString(size); + s1[size - 1] = '\0'; + s2[size - 1] = '\0'; + // Normal StrNCmp calls + Ident(StrNCmp(s1, s2, size + 2)); + s1[size - 1] = 'z'; + s2[size - 1] = 'x'; + Ident(StrNCmp(s1 + size - 2, s2 + size - 2, size)); + s2[size - 1] = 'z'; + Ident(StrNCmp(s1 - 1, s2 - 1, 0)); + Ident(StrNCmp(s1 + size - 1, s2 + size - 1, 1)); + // One of arguments points to not allocated memory. + EXPECT_DEATH(Ident(StrNCmp)(s1 - 1, s2, 1), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(StrNCmp)(s1, s2 - 1, 1), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(StrNCmp)(s1 + size, s2, 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrNCmp)(s1, s2 + size, 1), RightOOBErrorMessage(0)); + // Hit unallocated memory and die. + EXPECT_DEATH(Ident(StrNCmp)(s1 + 1, s2 + 1, size), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrNCmp)(s1 + size - 1, s2, 2), RightOOBErrorMessage(0)); + free(s1); + free(s2); +} + +TEST(AddressSanitizer, StrNCmpOOBTest) { + RunStrNCmpTest(&strncmp); +} + +TEST(AddressSanitizer, StrNCaseCmpOOBTest) { + RunStrNCmpTest(&strncasecmp); +} + +TEST(AddressSanitizer, MemCmpOOBTest) { + size_t size = Ident(100); + char *s1 = MallocAndMemsetString(size); + char *s2 = MallocAndMemsetString(size); + // Normal memcmp calls. + Ident(memcmp(s1, s2, size)); + Ident(memcmp(s1 + size - 1, s2 + size - 1, 1)); + Ident(memcmp(s1 - 1, s2 - 1, 0)); + // One of arguments points to not allocated memory. + EXPECT_DEATH(Ident(memcmp)(s1 - 1, s2, 1), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(memcmp)(s1, s2 - 1, 1), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Ident(memcmp)(s1 + size, s2, 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(memcmp)(s1, s2 + size, 1), RightOOBErrorMessage(0)); + // Hit unallocated memory and die. + EXPECT_DEATH(Ident(memcmp)(s1 + 1, s2 + 1, size), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(memcmp)(s1 + size - 1, s2, 2), RightOOBErrorMessage(0)); + // Zero bytes are not terminators and don't prevent from OOB. + s1[size - 1] = '\0'; + s2[size - 1] = '\0'; + EXPECT_DEATH(Ident(memcmp)(s1, s2, size + 1), RightOOBErrorMessage(0)); + free(s1); + free(s2); +} + +TEST(AddressSanitizer, StrCatOOBTest) { + size_t to_size = Ident(100); + char *to = MallocAndMemsetString(to_size); + to[0] = '\0'; + size_t from_size = Ident(20); + char *from = MallocAndMemsetString(from_size); + from[from_size - 1] = '\0'; + // Normal strcat calls. + strcat(to, from); + strcat(to, from); + strcat(to + from_size, from + from_size - 2); + // Catenate empty string is not always an error. + strcat(to - 1, from + from_size - 1); + // One of arguments points to not allocated memory. + EXPECT_DEATH(strcat(to - 1, from), LeftOOBErrorMessage(1)); + EXPECT_DEATH(strcat(to, from - 1), LeftOOBErrorMessage(1)); + EXPECT_DEATH(strcat(to + to_size, from), RightOOBErrorMessage(0)); + EXPECT_DEATH(strcat(to, from + from_size), RightOOBErrorMessage(0)); + + // "from" is not zero-terminated. + from[from_size - 1] = 'z'; + EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0)); + from[from_size - 1] = '\0'; + // "to" is not zero-terminated. + memset(to, 'z', to_size); + EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0)); + // "to" is too short to fit "from". + to[to_size - from_size + 1] = '\0'; + EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0)); + // length of "to" is just enough. + strcat(to, from + 1); +} + +static string OverlapErrorMessage(const string &func) { + return func + "-param-overlap"; +} + +TEST(AddressSanitizer, StrArgsOverlapTest) { + size_t size = Ident(100); + char *str = Ident((char*)malloc(size)); + + // Check "memcpy". Use Ident() to avoid inlining. + memset(str, 'z', size); + Ident(memcpy)(str + 1, str + 11, 10); + Ident(memcpy)(str, str, 0); + EXPECT_DEATH(Ident(memcpy)(str, str + 14, 15), OverlapErrorMessage("memcpy")); + EXPECT_DEATH(Ident(memcpy)(str + 14, str, 15), OverlapErrorMessage("memcpy")); + EXPECT_DEATH(Ident(memcpy)(str + 20, str + 20, 1), + OverlapErrorMessage("memcpy")); + + // Check "strcpy". + memset(str, 'z', size); + str[9] = '\0'; + strcpy(str + 10, str); + EXPECT_DEATH(strcpy(str + 9, str), OverlapErrorMessage("strcpy")); + EXPECT_DEATH(strcpy(str, str + 4), OverlapErrorMessage("strcpy")); + strcpy(str, str + 5); + + // Check "strncpy". + memset(str, 'z', size); + strncpy(str, str + 10, 10); + EXPECT_DEATH(strncpy(str, str + 9, 10), OverlapErrorMessage("strncpy")); + EXPECT_DEATH(strncpy(str + 9, str, 10), OverlapErrorMessage("strncpy")); + str[10] = '\0'; + strncpy(str + 11, str, 20); + EXPECT_DEATH(strncpy(str + 10, str, 20), OverlapErrorMessage("strncpy")); + + // Check "strcat". + memset(str, 'z', size); + str[10] = '\0'; + str[20] = '\0'; + strcat(str, str + 10); + strcat(str, str + 11); + str[10] = '\0'; + strcat(str + 11, str); + EXPECT_DEATH(strcat(str, str + 9), OverlapErrorMessage("strcat")); + EXPECT_DEATH(strcat(str + 9, str), OverlapErrorMessage("strcat")); + EXPECT_DEATH(strcat(str + 10, str), OverlapErrorMessage("strcat")); + + free(str); +} + +// At the moment we instrument memcpy/memove/memset calls at compile time so we +// can't handle OOB error if these functions are called by pointer, see disabled +// MemIntrinsicCallByPointerTest below +typedef void*(*PointerToMemTransfer)(void*, const void*, size_t); +typedef void*(*PointerToMemSet)(void*, int, size_t); + +void CallMemSetByPointer(PointerToMemSet MemSet) { + size_t size = Ident(100); + char *array = Ident((char*)malloc(size)); + EXPECT_DEATH(MemSet(array, 0, 101), RightOOBErrorMessage(0)); + free(array); +} + +void CallMemTransferByPointer(PointerToMemTransfer MemTransfer) { + size_t size = Ident(100); + char *src = Ident((char*)malloc(size)); + char *dst = Ident((char*)malloc(size)); + EXPECT_DEATH(MemTransfer(dst, src, 101), RightOOBErrorMessage(0)); + free(src); + free(dst); +} + +TEST(AddressSanitizer, DISABLED_MemIntrinsicCallByPointerTest) { + CallMemSetByPointer(&memset); + CallMemTransferByPointer(&memcpy); + CallMemTransferByPointer(&memmove); +} + +// This test case fails +// Clang optimizes memcpy/memset calls which lead to unaligned access +TEST(AddressSanitizer, DISABLED_MemIntrinsicUnalignedAccessTest) { + int size = Ident(4096); + char *s = Ident((char*)malloc(size)); + EXPECT_DEATH(memset(s + size - 1, 0, 2), RightOOBErrorMessage(0)); + free(s); +} + +// TODO(samsonov): Add a test with malloc(0) +// TODO(samsonov): Add tests for str* and mem* functions. + +__attribute__((noinline)) +static int LargeFunction(bool do_bad_access) { + int *x = new int[100]; + x[0]++; + x[1]++; + x[2]++; + x[3]++; + x[4]++; + x[5]++; + x[6]++; + x[7]++; + x[8]++; + x[9]++; + + x[do_bad_access ? 100 : 0]++; int res = __LINE__; + + x[10]++; + x[11]++; + x[12]++; + x[13]++; + x[14]++; + x[15]++; + x[16]++; + x[17]++; + x[18]++; + x[19]++; + + delete x; + return res; +} + +// Test the we have correct debug info for the failing instruction. +// This test requires the in-process symbolizer to be enabled by default. +TEST(AddressSanitizer, DISABLED_LargeFunctionSymbolizeTest) { + int failing_line = LargeFunction(false); + char expected_warning[128]; + sprintf(expected_warning, "LargeFunction.*asan_test.cc:%d", failing_line); + EXPECT_DEATH(LargeFunction(true), expected_warning); +} + +// Check that we unwind and symbolize correctly. +TEST(AddressSanitizer, DISABLED_MallocFreeUnwindAndSymbolizeTest) { + int *a = (int*)malloc_aaa(sizeof(int)); + *a = 1; + free_aaa(a); + EXPECT_DEATH(*a = 1, "free_ccc.*free_bbb.*free_aaa.*" + "malloc_fff.*malloc_eee.*malloc_ddd"); +} + +void *ThreadedTestAlloc(void *a) { + int **p = (int**)a; + *p = new int; + return 0; +} + +void *ThreadedTestFree(void *a) { + int **p = (int**)a; + delete *p; + return 0; +} + +void *ThreadedTestUse(void *a) { + int **p = (int**)a; + **p = 1; + return 0; +} + +void ThreadedTestSpawn() { + pthread_t t; + int *x; + pthread_create(&t, 0, ThreadedTestAlloc, &x); + pthread_join(t, 0); + pthread_create(&t, 0, ThreadedTestFree, &x); + pthread_join(t, 0); + pthread_create(&t, 0, ThreadedTestUse, &x); + pthread_join(t, 0); +} + +TEST(AddressSanitizer, ThreadedTest) { + EXPECT_DEATH(ThreadedTestSpawn(), + ASAN_PCRE_DOTALL + "Thread T.*created" + ".*Thread T.*created" + ".*Thread T.*created"); +} + +#if ASAN_NEEDS_SEGV +TEST(AddressSanitizer, ShadowGapTest) { +#if __WORDSIZE == 32 + char *addr = (char*)0x22000000; +#else + char *addr = (char*)0x0000100000080000; +#endif + EXPECT_DEATH(*addr = 1, "AddressSanitizer crashed on unknown"); +} +#endif // ASAN_NEEDS_SEGV + +extern "C" { +__attribute__((noinline)) +static void UseThenFreeThenUse() { + char *x = Ident((char*)malloc(8)); + *x = 1; + free_aaa(x); + *x = 2; +} +} + +TEST(AddressSanitizer, UseThenFreeThenUseTest) { + EXPECT_DEATH(UseThenFreeThenUse(), "freed by thread"); +} + +TEST(AddressSanitizer, StrDupTest) { + free(strdup(Ident("123"))); +} + +TEST(AddressSanitizer, ObjdumpTest) { + ObjdumpOfMyself *o = objdump_of_myself(); + EXPECT_TRUE(o->IsCorrect()); +} + +extern "C" { +__attribute__((noinline)) +static void DisasmSimple() { + Ident(0); +} + +__attribute__((noinline)) +static void DisasmParamWrite(int *a) { + *a = 1; +} + +__attribute__((noinline)) +static void DisasmParamInc(int *a) { + (*a)++; +} + +__attribute__((noinline)) +static void DisasmParamReadIfWrite(int *a) { + if (*a) + *a = 1; +} + +__attribute__((noinline)) +static int DisasmParamIfReadWrite(int *a, int cond) { + int res = 0; + if (cond) + res = *a; + *a = 0; + return res; +} + +static int GLOBAL; + +__attribute__((noinline)) +static void DisasmWriteGlob() { + GLOBAL = 1; +} +} // extern "C" + +TEST(AddressSanitizer, DisasmTest) { + int a; + DisasmSimple(); + DisasmParamWrite(&a); + DisasmParamInc(&a); + Ident(DisasmWriteGlob)(); + DisasmParamReadIfWrite(&a); + + a = 7; + EXPECT_EQ(7, DisasmParamIfReadWrite(&a, Ident(1))); + EXPECT_EQ(0, a); + + ObjdumpOfMyself *o = objdump_of_myself(); + vector<string> insns; + insns.push_back("ud2"); + insns.push_back("__asan_report_"); + EXPECT_EQ(0, o->CountInsnInFunc("DisasmSimple", insns)); + EXPECT_EQ(1, o->CountInsnInFunc("DisasmParamWrite", insns)); + EXPECT_EQ(1, o->CountInsnInFunc("DisasmParamInc", insns)); + EXPECT_EQ(0, o->CountInsnInFunc("DisasmWriteGlob", insns)); + + // TODO(kcc): implement these (needs just one __asan_report). + EXPECT_EQ(2, o->CountInsnInFunc("DisasmParamReadIfWrite", insns)); + EXPECT_EQ(2, o->CountInsnInFunc("DisasmParamIfReadWrite", insns)); +} + +// Currently we create and poison redzone at right of global variables. +char glob5[5]; +static char static110[110]; +const char ConstGlob[7] = {1, 2, 3, 4, 5, 6, 7}; +static const char StaticConstGlob[3] = {9, 8, 7}; +extern int GlobalsTest(int x); + +TEST(AddressSanitizer, GlobalTest) { + static char func_static15[15]; + + static char fs1[10]; + static char fs2[10]; + static char fs3[10]; + + glob5[Ident(0)] = 0; + glob5[Ident(1)] = 0; + glob5[Ident(2)] = 0; + glob5[Ident(3)] = 0; + glob5[Ident(4)] = 0; + + EXPECT_DEATH(glob5[Ident(5)] = 0, + "0 bytes to the right of global variable.*glob5.* size 5"); + EXPECT_DEATH(glob5[Ident(5+6)] = 0, + "6 bytes to the right of global variable.*glob5.* size 5"); + Ident(static110); // avoid optimizations + static110[Ident(0)] = 0; + static110[Ident(109)] = 0; + EXPECT_DEATH(static110[Ident(110)] = 0, + "0 bytes to the right of global variable"); + EXPECT_DEATH(static110[Ident(110+7)] = 0, + "7 bytes to the right of global variable"); + + Ident(func_static15); // avoid optimizations + func_static15[Ident(0)] = 0; + EXPECT_DEATH(func_static15[Ident(15)] = 0, + "0 bytes to the right of global variable"); + EXPECT_DEATH(func_static15[Ident(15 + 9)] = 0, + "9 bytes to the right of global variable"); + + Ident(fs1); + Ident(fs2); + Ident(fs3); + + // We don't create left redzones, so this is not 100% guaranteed to fail. + // But most likely will. + EXPECT_DEATH(fs2[Ident(-1)] = 0, "is located.*of global variable"); + + EXPECT_DEATH(Ident(Ident(ConstGlob)[8]), + "is located 1 bytes to the right of .*ConstGlob"); + EXPECT_DEATH(Ident(Ident(StaticConstGlob)[5]), + "is located 2 bytes to the right of .*StaticConstGlob"); + + // call stuff from another file. + GlobalsTest(0); +} + +TEST(AddressSanitizer, GlobalStringConstTest) { + static const char *zoo = "FOOBAR123"; + const char *p = Ident(zoo); + EXPECT_DEATH(Ident(p[15]), "is ascii string 'FOOBAR123'"); +} + +TEST(AddressSanitizer, FileNameInGlobalReportTest) { + static char zoo[10]; + const char *p = Ident(zoo); + // The file name should be present in the report. + EXPECT_DEATH(Ident(p[15]), "zoo.*asan_test.cc"); +} + +int *ReturnsPointerToALocalObject() { + int a = 0; + return Ident(&a); +} + +#if ASAN_UAR == 1 +TEST(AddressSanitizer, LocalReferenceReturnTest) { + int *(*f)() = Ident(ReturnsPointerToALocalObject); + int *p = f(); + // Call 'f' a few more times, 'p' should still be poisoned. + for (int i = 0; i < 32; i++) + f(); + EXPECT_DEATH(*p = 1, "AddressSanitizer stack-use-after-return"); + EXPECT_DEATH(*p = 1, "is located.*in frame .*ReturnsPointerToALocal"); +} +#endif + +template <int kSize> +__attribute__((noinline)) +static void FuncWithStack() { + char x[kSize]; + Ident(x)[0] = 0; + Ident(x)[kSize-1] = 0; +} + +static void LotsOfStackReuse() { + int LargeStack[10000]; + Ident(LargeStack)[0] = 0; + for (int i = 0; i < 10000; i++) { + FuncWithStack<128 * 1>(); + FuncWithStack<128 * 2>(); + FuncWithStack<128 * 4>(); + FuncWithStack<128 * 8>(); + FuncWithStack<128 * 16>(); + FuncWithStack<128 * 32>(); + FuncWithStack<128 * 64>(); + FuncWithStack<128 * 128>(); + FuncWithStack<128 * 256>(); + FuncWithStack<128 * 512>(); + Ident(LargeStack)[0] = 0; + } +} + +TEST(AddressSanitizer, StressStackReuseTest) { + LotsOfStackReuse(); +} + +TEST(AddressSanitizer, ThreadedStressStackReuseTest) { + const int kNumThreads = 20; + pthread_t t[kNumThreads]; + for (int i = 0; i < kNumThreads; i++) { + pthread_create(&t[i], 0, (void* (*)(void *x))LotsOfStackReuse, 0); + } + for (int i = 0; i < kNumThreads; i++) { + pthread_join(t[i], 0); + } +} + +#ifdef __EXCEPTIONS +__attribute__((noinline)) +static void StackReuseAndException() { + int large_stack[1000]; + Ident(large_stack); + ASAN_THROW(1); +} + +// TODO(kcc): support exceptions with use-after-return. +TEST(AddressSanitizer, DISABLED_StressStackReuseAndExceptionsTest) { + for (int i = 0; i < 10000; i++) { + try { + StackReuseAndException(); + } catch(...) { + } + } +} +#endif + +TEST(AddressSanitizer, MlockTest) { + EXPECT_EQ(0, mlockall(MCL_CURRENT)); + EXPECT_EQ(0, mlock((void*)0x12345, 0x5678)); + EXPECT_EQ(0, munlockall()); + EXPECT_EQ(0, munlock((void*)0x987, 0x654)); +} + +// ------------------ demo tests; run each one-by-one ------------- +// e.g. --gtest_filter=*DemoOOBLeftHigh --gtest_also_run_disabled_tests +TEST(AddressSanitizer, DISABLED_DemoThreadedTest) { + ThreadedTestSpawn(); +} + +void *SimpleBugOnSTack(void *x = 0) { + char a[20]; + Ident(a)[20] = 0; + return 0; +} + +TEST(AddressSanitizer, DISABLED_DemoStackTest) { + SimpleBugOnSTack(); +} + +TEST(AddressSanitizer, DISABLED_DemoThreadStackTest) { + pthread_t t; + pthread_create(&t, 0, SimpleBugOnSTack, 0); + pthread_join(t, 0); +} + +TEST(AddressSanitizer, DISABLED_DemoUAFLowIn) { + uaf_test<U1>(10, 0); +} +TEST(AddressSanitizer, DISABLED_DemoUAFLowLeft) { + uaf_test<U1>(10, -2); +} +TEST(AddressSanitizer, DISABLED_DemoUAFLowRight) { + uaf_test<U1>(10, 10); +} + +TEST(AddressSanitizer, DISABLED_DemoUAFHigh) { + uaf_test<U1>(kLargeMalloc, 0); +} + +TEST(AddressSanitizer, DISABLED_DemoOOBLeftLow) { + oob_test<U1>(10, -1); +} + +TEST(AddressSanitizer, DISABLED_DemoOOBLeftHigh) { + oob_test<U1>(kLargeMalloc, -1); +} + +TEST(AddressSanitizer, DISABLED_DemoOOBRightLow) { + oob_test<U1>(10, 10); +} + +TEST(AddressSanitizer, DISABLED_DemoOOBRightHigh) { + oob_test<U1>(kLargeMalloc, kLargeMalloc); +} + +TEST(AddressSanitizer, DISABLED_DemoOOM) { + size_t size = __WORDSIZE == 64 ? (size_t)(1ULL << 40) : (0xf0000000); + printf("%p\n", malloc(size)); +} + +TEST(AddressSanitizer, DISABLED_DemoDoubleFreeTest) { + DoubleFree(); +} + +TEST(AddressSanitizer, DISABLED_DemoNullDerefTest) { + int *a = 0; + Ident(a)[10] = 0; +} + +TEST(AddressSanitizer, DISABLED_DemoFunctionStaticTest) { + static char a[100]; + static char b[100]; + static char c[100]; + Ident(a); + Ident(b); + Ident(c); + Ident(a)[5] = 0; + Ident(b)[105] = 0; + Ident(a)[5] = 0; +} + +TEST(AddressSanitizer, DISABLED_DemoTooMuchMemoryTest) { + const size_t kAllocSize = (1 << 28) - 1024; + size_t total_size = 0; + while (true) { + char *x = (char*)malloc(kAllocSize); + memset(x, 0, kAllocSize); + total_size += kAllocSize; + fprintf(stderr, "total: %ldM\n", (long)total_size >> 20); + } +} + +#ifdef __APPLE__ +#include "asan_mac_test.h" +// TODO(glider): figure out whether we still need these tests. Is it correct +// to intercept CFAllocator? +TEST(AddressSanitizerMac, DISABLED_CFAllocatorDefaultDoubleFree) { + EXPECT_DEATH( + CFAllocatorDefaultDoubleFree(), + "attempting double-free"); +} + +TEST(AddressSanitizerMac, DISABLED_CFAllocatorSystemDefaultDoubleFree) { + EXPECT_DEATH( + CFAllocatorSystemDefaultDoubleFree(), + "attempting double-free"); +} + +TEST(AddressSanitizerMac, DISABLED_CFAllocatorMallocDoubleFree) { + EXPECT_DEATH(CFAllocatorMallocDoubleFree(), "attempting double-free"); +} + +TEST(AddressSanitizerMac, DISABLED_CFAllocatorMallocZoneDoubleFree) { + EXPECT_DEATH(CFAllocatorMallocZoneDoubleFree(), "attempting double-free"); +} + +TEST(AddressSanitizerMac, GCDDispatchAsync) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDDispatchAsync(), "Shadow byte and word"); +} + +TEST(AddressSanitizerMac, GCDDispatchSync) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDDispatchSync(), "Shadow byte and word"); +} + + +TEST(AddressSanitizerMac, GCDReuseWqthreadsAsync) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDReuseWqthreadsAsync(), "Shadow byte and word"); +} + +TEST(AddressSanitizerMac, GCDReuseWqthreadsSync) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDReuseWqthreadsSync(), "Shadow byte and word"); +} + +TEST(AddressSanitizerMac, GCDDispatchAfter) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte and word"); +} + +TEST(AddressSanitizerMac, GCDSourceEvent) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDSourceEvent(), "Shadow byte and word"); +} + +TEST(AddressSanitizerMac, GCDSourceCancel) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDSourceCancel(), "Shadow byte and word"); +} + +TEST(AddressSanitizerMac, GCDGroupAsync) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDGroupAsync(), "Shadow byte and word"); +} + +void *MallocIntrospectionLockWorker(void *_) { + const int kNumPointers = 100; + int i; + void *pointers[kNumPointers]; + for (i = 0; i < kNumPointers; i++) { + pointers[i] = malloc(i + 1); + } + for (i = 0; i < kNumPointers; i++) { + free(pointers[i]); + } + + return NULL; +} + +void *MallocIntrospectionLockForker(void *_) { + pid_t result = fork(); + if (result == -1) { + perror("fork"); + } + assert(result != -1); + if (result == 0) { + // Call malloc in the child process to make sure we won't deadlock. + void *ptr = malloc(42); + free(ptr); + exit(0); + } else { + // Return in the parent process. + return NULL; + } +} + +TEST(AddressSanitizerMac, MallocIntrospectionLock) { + // Incorrect implementation of force_lock and force_unlock in our malloc zone + // will cause forked processes to deadlock. + // TODO(glider): need to detect that none of the child processes deadlocked. + const int kNumWorkers = 5, kNumIterations = 100; + int i, iter; + for (iter = 0; iter < kNumIterations; iter++) { + pthread_t workers[kNumWorkers], forker; + for (i = 0; i < kNumWorkers; i++) { + pthread_create(&workers[i], 0, MallocIntrospectionLockWorker, 0); + } + pthread_create(&forker, 0, MallocIntrospectionLockForker, 0); + for (i = 0; i < kNumWorkers; i++) { + pthread_join(workers[i], 0); + } + pthread_join(forker, 0); + } +} + +void *TSDAllocWorker(void *test_key) { + if (test_key) { + void *mem = malloc(10); + pthread_setspecific(*(pthread_key_t*)test_key, mem); + } + return NULL; +} + +TEST(AddressSanitizerMac, DISABLED_TSDWorkqueueTest) { + pthread_t th; + pthread_key_t test_key; + pthread_key_create(&test_key, CallFreeOnWorkqueue); + pthread_create(&th, NULL, TSDAllocWorker, &test_key); + pthread_join(th, NULL); + pthread_key_delete(test_key); +} +#endif // __APPLE__ + +int main(int argc, char **argv) { + progname = argv[0]; + testing::GTEST_FLAG(death_test_style) = "threadsafe"; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/lib/asan/tests/asan_test.ignore b/lib/asan/tests/asan_test.ignore new file mode 100644 index 000000000000..7bafa83bf62e --- /dev/null +++ b/lib/asan/tests/asan_test.ignore @@ -0,0 +1,2 @@ +fun:*IgnoreTest* +fun:*SomeOtherFunc* diff --git a/lib/asan/tests/asan_test_config.h b/lib/asan/tests/asan_test_config.h new file mode 100644 index 000000000000..de4ae95bd244 --- /dev/null +++ b/lib/asan/tests/asan_test_config.h @@ -0,0 +1,44 @@ +//===-- asan_test_config.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 a part of AddressSanitizer, an address sanity checker. +// +//===----------------------------------------------------------------------===// +#ifndef ASAN_TEST_CONFIG_H +#define ASAN_TEST_CONFIG_H + +#include <vector> +#include <string> +#include <map> + +#include "gtest/gtest.h" + +using std::string; +using std::vector; +using std::map; + +#ifndef ASAN_UAR +# error "please define ASAN_UAR" +#endif + +#ifndef ASAN_HAS_EXCEPTIONS +# error "please define ASAN_HAS_EXCEPTIONS" +#endif + +#ifndef ASAN_HAS_BLACKLIST +# error "please define ASAN_HAS_BLACKLIST" +#endif + +#ifndef ASAN_NEEDS_SEGV +# error "please define ASAN_NEEDS_SEGV" +#endif + +#define ASAN_PCRE_DOTALL "" + +#endif // ASAN_TEST_CONFIG_H diff --git a/lib/asan/tests/asan_test_utils.h b/lib/asan/tests/asan_test_utils.h new file mode 100644 index 000000000000..a4809812dcf0 --- /dev/null +++ b/lib/asan/tests/asan_test_utils.h @@ -0,0 +1,30 @@ +//===-- asan_test_utils.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 a part of AddressSanitizer, an address sanity checker. +// +//===----------------------------------------------------------------------===// + +#ifndef ASAN_TEST_UTILS_H +#define ASAN_TEST_UTILS_H + +// Make the compiler think that something is going on there. +extern "C" void break_optimization(void *); + +// This function returns its parameter but in such a way that compiler +// can not prove it. +template<class T> +__attribute__((noinline)) +static T Ident(T t) { + T ret = t; + break_optimization(&ret); + return ret; +} + +#endif // ASAN_TEST_UTILS_H diff --git a/lib/asan/tests/dlclose-test-so.cc b/lib/asan/tests/dlclose-test-so.cc new file mode 100644 index 000000000000..fae2f813abb7 --- /dev/null +++ b/lib/asan/tests/dlclose-test-so.cc @@ -0,0 +1,33 @@ +//===-- asan_rtl.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Regression test for +// http://code.google.com/p/address-sanitizer/issues/detail?id=19 +//===----------------------------------------------------------------------===// +#include <stdio.h> + +static int pad1; +static int static_var; +static int pad2; + +extern "C" +int *get_address_of_static_var() { + return &static_var; +} + +__attribute__((constructor)) +void at_dlopen() { + printf("%s: I am being dlopened\n", __FILE__); +} +__attribute__((destructor)) +void at_dlclose() { + printf("%s: I am being dlclosed\n", __FILE__); +} diff --git a/lib/asan/tests/dlclose-test.cc b/lib/asan/tests/dlclose-test.cc new file mode 100644 index 000000000000..307886667b15 --- /dev/null +++ b/lib/asan/tests/dlclose-test.cc @@ -0,0 +1,73 @@ +//===-- asan_rtl.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +// Regression test for +// http://code.google.com/p/address-sanitizer/issues/detail?id=19 +// Bug description: +// 1. application dlopens foo.so +// 2. asan registers all globals from foo.so +// 3. application dlcloses foo.so +// 4. application mmaps some memory to the location where foo.so was before +// 5. application starts using this mmaped memory, but asan still thinks there +// are globals. +// 6. BOOM +//===----------------------------------------------------------------------===// +#include <assert.h> +#include <dlfcn.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> + +#include <string> + +using std::string; + +static const int kPageSize = 4096; + +typedef int *(fun_t)(); + +int main(int argc, char *argv[]) { + string path = string(argv[0]) + "-so.so"; + printf("opening %s ... \n", path.c_str()); + void *lib = dlopen(path.c_str(), RTLD_NOW); + if (!lib) { + printf("error in dlopen(): %s\n", dlerror()); + return 1; + } + fun_t *get = (fun_t*)dlsym(lib, "get_address_of_static_var"); + if (!get) { + printf("failed dlsym\n"); + return 1; + } + int *addr = get(); + assert(((size_t)addr % 32) == 0); // should be 32-byte aligned. + printf("addr: %p\n", addr); + addr[0] = 1; // make sure we can write there. + + // Now dlclose the shared library. + printf("attempting to dlclose\n"); + if (dlclose(lib)) { + printf("failed to dlclose\n"); + return 1; + } + // Now, the page where 'addr' is unmapped. Map it. + size_t page_beg = ((size_t)addr) & ~(kPageSize - 1); + void *res = mmap((void*)(page_beg), kPageSize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, 0, 0); + if (res == (char*)-1L) { + printf("failed to mmap\n"); + return 1; + } + addr[1] = 2; // BOOM (if the bug is not fixed). + printf("PASS\n"); + return 0; +} diff --git a/lib/asan/tests/dlclose-test.tmpl b/lib/asan/tests/dlclose-test.tmpl new file mode 100644 index 000000000000..7ef22e9a431a --- /dev/null +++ b/lib/asan/tests/dlclose-test.tmpl @@ -0,0 +1 @@ +PASS diff --git a/lib/asan/tests/global-overflow.cc b/lib/asan/tests/global-overflow.cc new file mode 100644 index 000000000000..b85c4d2a18bf --- /dev/null +++ b/lib/asan/tests/global-overflow.cc @@ -0,0 +1,12 @@ +#include <string.h> +int main(int argc, char **argv) { + static char XXX[10]; + static char YYY[10]; + static char ZZZ[10]; + memset(XXX, 0, 10); + memset(YYY, 0, 10); + memset(ZZZ, 0, 10); + int res = YYY[argc * 10]; // BOOOM + res += XXX[argc] + ZZZ[argc]; + return res; +} diff --git a/lib/asan/tests/global-overflow.tmpl b/lib/asan/tests/global-overflow.tmpl new file mode 100644 index 000000000000..c5d54428143b --- /dev/null +++ b/lib/asan/tests/global-overflow.tmpl @@ -0,0 +1,3 @@ +READ of size 1 at 0x.* thread T0 + #0 0x.* in main .*global-overflow.cc:9 +0x.* is located 0 bytes to the right of global variable .*YYY.* of size 10 diff --git a/lib/asan/tests/heap-overflow.cc b/lib/asan/tests/heap-overflow.cc new file mode 100644 index 000000000000..475d1637385a --- /dev/null +++ b/lib/asan/tests/heap-overflow.cc @@ -0,0 +1,9 @@ +#include <stdlib.h> +#include <string.h> +int main(int argc, char **argv) { + char *x = (char*)malloc(10 * sizeof(char)); + memset(x, 0, 10); + int res = x[argc * 10]; // BOOOM + free(x); + return res; +} diff --git a/lib/asan/tests/heap-overflow.tmpl b/lib/asan/tests/heap-overflow.tmpl new file mode 100644 index 000000000000..e2ab65f17d82 --- /dev/null +++ b/lib/asan/tests/heap-overflow.tmpl @@ -0,0 +1,6 @@ +READ of size 1 at 0x.* thread T0 + #0 0x.* in main .*heap-overflow.cc:6 +0x.* is located 0 bytes to the right of 10-byte region +allocated by thread T0 here: + #0 0x.* in malloc + #1 0x.* in main .*heap-overflow.cc:[45] diff --git a/lib/asan/tests/heap-overflow.tmpl.Darwin b/lib/asan/tests/heap-overflow.tmpl.Darwin new file mode 100644 index 000000000000..e4611d067abe --- /dev/null +++ b/lib/asan/tests/heap-overflow.tmpl.Darwin @@ -0,0 +1,8 @@ +READ of size 1 at 0x.* thread T0 + #0 0x.* in main .*heap-overflow.cc:6 +0x.* is located 0 bytes to the right of 10-byte region +allocated by thread T0 here: + #0 0x.* in .*mz_malloc.* _asan_rtl_ + #1 0x.* in malloc_zone_malloc.* + #2 0x.* in malloc.* + #3 0x.* in main heap-overflow.cc:4 diff --git a/lib/asan/tests/large_func_test.cc b/lib/asan/tests/large_func_test.cc new file mode 100644 index 000000000000..70bc36f40b87 --- /dev/null +++ b/lib/asan/tests/large_func_test.cc @@ -0,0 +1,33 @@ +#include <stdlib.h> +__attribute__((noinline)) +static void LargeFunction(int *x, int zero) { + x[0]++; + x[1]++; + x[2]++; + x[3]++; + x[4]++; + x[5]++; + x[6]++; + x[7]++; + x[8]++; + x[9]++; + + x[zero + 111]++; // we should report this exact line + + x[10]++; + x[11]++; + x[12]++; + x[13]++; + x[14]++; + x[15]++; + x[16]++; + x[17]++; + x[18]++; + x[19]++; +} + +int main(int argc, char **argv) { + int *x = new int[100]; + LargeFunction(x, argc - 1); + delete x; +} diff --git a/lib/asan/tests/large_func_test.tmpl b/lib/asan/tests/large_func_test.tmpl new file mode 100644 index 000000000000..45a13d0bc5bd --- /dev/null +++ b/lib/asan/tests/large_func_test.tmpl @@ -0,0 +1,8 @@ +.*ERROR: AddressSanitizer heap-buffer-overflow on address 0x.* at pc 0x.* bp 0x.* sp 0x.* +READ of size 4 at 0x.* thread T0 + #0 0x.* in LargeFunction .*large_func_test.cc:15 + #1 0x.* in main .*large_func_test.cc:3[012] +0x.* is located 44 bytes to the right of 400-byte region +allocated by thread T0 here: + #0 0x.* in operator new.* + #1 0x.* in main .*large_func_test.cc:30 diff --git a/lib/asan/tests/match_output.py b/lib/asan/tests/match_output.py new file mode 100755 index 000000000000..31095f3f62f2 --- /dev/null +++ b/lib/asan/tests/match_output.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +import re +import sys + +def matchFile(f, f_re): + for line_re in f_re: + line_re = line_re.rstrip() + if not line_re: + continue + if line_re[0] == '#': + continue + match = False + for line in f: + line = line.rstrip() + # print line + if re.search(line_re, line): + match = True + #print 'match: %s =~ %s' % (line, line_re) + break + if not match: + print 'no match for: %s' % (line_re) + return False + return True + +if len(sys.argv) != 2: + print >>sys.stderr, 'Usage: %s <template file>' + sys.exit(1) + +f = sys.stdin +f_re = open(sys.argv[1]) + +if not matchFile(f, f_re): + print >>sys.stderr, 'File does not match the template' + sys.exit(1) diff --git a/lib/asan/tests/null_deref.cc b/lib/asan/tests/null_deref.cc new file mode 100644 index 000000000000..f7ba4dd5f63f --- /dev/null +++ b/lib/asan/tests/null_deref.cc @@ -0,0 +1,7 @@ +__attribute__((noinline)) +static void NullDeref(int *ptr) { + ptr[10]++; +} +int main() { + NullDeref((int*)0); +} diff --git a/lib/asan/tests/null_deref.tmpl b/lib/asan/tests/null_deref.tmpl new file mode 100644 index 000000000000..d27cccc06bc8 --- /dev/null +++ b/lib/asan/tests/null_deref.tmpl @@ -0,0 +1,4 @@ +.*ERROR: AddressSanitizer crashed on unknown address 0x0*00028 .*pc 0x.* +AddressSanitizer can not provide additional info. ABORTING + #0 0x.* in NullDeref.*null_deref.cc:3 + #1 0x.* in main.*null_deref.cc:[67] diff --git a/lib/asan/tests/shared-lib-test-so.cc b/lib/asan/tests/shared-lib-test-so.cc new file mode 100644 index 000000000000..c3b3bc22aed2 --- /dev/null +++ b/lib/asan/tests/shared-lib-test-so.cc @@ -0,0 +1,21 @@ +//===-- asan_rtl.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +//===----------------------------------------------------------------------===// +#include <stdio.h> + +int pad[10]; +int GLOB[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +extern "C" +void inc(int index) { + GLOB[index]++; +} diff --git a/lib/asan/tests/shared-lib-test.cc b/lib/asan/tests/shared-lib-test.cc new file mode 100644 index 000000000000..e492572c7280 --- /dev/null +++ b/lib/asan/tests/shared-lib-test.cc @@ -0,0 +1,37 @@ +//===-- asan_rtl.cc ------------*- 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 a part of AddressSanitizer, an address sanity checker. +// +//===----------------------------------------------------------------------===// +#include <dlfcn.h> +#include <stdio.h> +#include <string.h> + +#include <string> + +using std::string; + +typedef void (fun_t)(int x); + +int main(int argc, char *argv[]) { + string path = string(argv[0]) + "-so.so"; + printf("opening %s ... \n", path.c_str()); + void *lib = dlopen(path.c_str(), RTLD_NOW); + if (!lib) { + printf("error in dlopen(): %s\n", dlerror()); + return 1; + } + fun_t *inc = (fun_t*)dlsym(lib, "inc"); + if (!inc) return 1; + printf("ok\n"); + inc(1); + inc(-1); + return 0; +} diff --git a/lib/asan/tests/shared-lib-test.tmpl b/lib/asan/tests/shared-lib-test.tmpl new file mode 100644 index 000000000000..564e3eb5cb8c --- /dev/null +++ b/lib/asan/tests/shared-lib-test.tmpl @@ -0,0 +1,7 @@ +#.*ERROR: AddressSanitizer global-buffer-overflow on address 0x.* at pc 0x.* bp 0x.* sp 0x.* +#READ of size 4 at 0x.* thread T0 +# #0 0x.* in inc .*shared-lib-test-so.cc:11 +# #1 0x.* in main .*shared-lib-test.cc:33 +# #2 0x.* in __libc_start_main.* +#0x.* is located 4 bytes to the left of global variable 'GLOB' (.*) of size 40 +#0x.* is located 52 bytes to the right of global variable 'pad' (.*) of size 40 diff --git a/lib/asan/tests/stack-overflow.cc b/lib/asan/tests/stack-overflow.cc new file mode 100644 index 000000000000..dd86aa32514a --- /dev/null +++ b/lib/asan/tests/stack-overflow.cc @@ -0,0 +1,7 @@ +#include <string.h> +int main(int argc, char **argv) { + char x[10]; + memset(x, 0, 10); + int res = x[argc * 10]; // BOOOM + return res; +} diff --git a/lib/asan/tests/stack-overflow.tmpl b/lib/asan/tests/stack-overflow.tmpl new file mode 100644 index 000000000000..6aa717a82b28 --- /dev/null +++ b/lib/asan/tests/stack-overflow.tmpl @@ -0,0 +1,3 @@ +READ of size 1 at 0x.* thread T0 + #0 0x.* in main .*stack-overflow.cc:5 +Address 0x.* is .* frame <main> diff --git a/lib/asan/tests/stack-use-after-return.cc b/lib/asan/tests/stack-use-after-return.cc new file mode 100644 index 000000000000..9098edf0adb8 --- /dev/null +++ b/lib/asan/tests/stack-use-after-return.cc @@ -0,0 +1,24 @@ +#include <stdio.h> + +__attribute__((noinline)) +char *Ident(char *x) { + fprintf(stderr, "1: %p\n", x); + return x; +} + +__attribute__((noinline)) +char *Func1() { + char local; + return Ident(&local); +} + +__attribute__((noinline)) +void Func2(char *x) { + fprintf(stderr, "2: %p\n", x); + *x = 1; +} + +int main(int argc, char **argv) { + Func2(Func1()); + return 0; +} diff --git a/lib/asan/tests/stack-use-after-return.disabled b/lib/asan/tests/stack-use-after-return.disabled new file mode 100644 index 000000000000..02729bc43a74 --- /dev/null +++ b/lib/asan/tests/stack-use-after-return.disabled @@ -0,0 +1,3 @@ +WRITE of size 1 .* thread T0 +#0.*Func2.*stack-use-after-return.cc:18 +is located in frame <.*Func1.*> of T0's stack diff --git a/lib/asan/tests/strncpy-overflow.cc b/lib/asan/tests/strncpy-overflow.cc new file mode 100644 index 000000000000..044f6494bfa1 --- /dev/null +++ b/lib/asan/tests/strncpy-overflow.cc @@ -0,0 +1,9 @@ +#include <string.h> +#include <stdlib.h> +int main(int argc, char **argv) { + char *hello = (char*)malloc(6); + strcpy(hello, "hello"); + char *short_buffer = (char*)malloc(9); + strncpy(short_buffer, hello, 10); // BOOM + return short_buffer[8]; +} diff --git a/lib/asan/tests/strncpy-overflow.tmpl b/lib/asan/tests/strncpy-overflow.tmpl new file mode 100644 index 000000000000..3780aa81921f --- /dev/null +++ b/lib/asan/tests/strncpy-overflow.tmpl @@ -0,0 +1,7 @@ +WRITE of size 1 at 0x.* thread T0 + #0 0x.* in strncpy + #1 0x.* in main .*strncpy-overflow.cc:[78] +0x.* is located 0 bytes to the right of 9-byte region +allocated by thread T0 here: + #0 0x.* in malloc + #1 0x.* in main .*strncpy-overflow.cc:6 diff --git a/lib/asan/tests/test_output.sh b/lib/asan/tests/test_output.sh new file mode 100755 index 000000000000..c54b2364b2c3 --- /dev/null +++ b/lib/asan/tests/test_output.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +set -e # fail on any error + +OS=`uname` +CXX=$1 +CC=$2 +CXXFLAGS="-mno-omit-leaf-frame-pointer -fno-omit-frame-pointer" +SYMBOLIZER=../scripts/asan_symbolize.py + +C_TEST=use-after-free +echo "Sanity checking a test in pure C" +$CC -g -faddress-sanitizer -O2 $C_TEST.c +./a.out 2>&1 | grep "heap-use-after-free" > /dev/null +rm ./a.out + +echo "Sanity checking a test in pure C with -pie" +$CC -g -faddress-sanitizer -O2 $C_TEST.c -pie +./a.out 2>&1 | grep "heap-use-after-free" > /dev/null +rm ./a.out + +for t in *.tmpl; do + for b in 32 64; do + for O in 0 1 2 3; do + c=`basename $t .tmpl` + c_so=$c-so + exe=$c.$b.O$O + so=$c.$b.O$O-so.so + echo testing $exe + $CXX $CXXFLAGS -g -m$b -faddress-sanitizer -O$O $c.cc -o $exe + [ -e "$c_so.cc" ] && $CXX $CXXFLAGS -g -m$b -faddress-sanitizer -O$O $c_so.cc -fPIC -shared -o $so + # If there's an OS-specific template, use it. + # Please minimize the use of OS-specific templates. + if [ -e "$t.$OS" ] + then + actual_t="$t.$OS" + else + actual_t="$t" + fi + ./$exe 2>&1 | $SYMBOLIZER 2> /dev/null | c++filt | ./match_output.py $actual_t + rm ./$exe + [ -e "$so" ] && rm ./$so + done + done +done + +exit 0 diff --git a/lib/asan/tests/use-after-free.c b/lib/asan/tests/use-after-free.c new file mode 100644 index 000000000000..60626bff778a --- /dev/null +++ b/lib/asan/tests/use-after-free.c @@ -0,0 +1,6 @@ +#include <stdlib.h> +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; +} diff --git a/lib/asan/tests/use-after-free.cc b/lib/asan/tests/use-after-free.cc new file mode 100644 index 000000000000..60626bff778a --- /dev/null +++ b/lib/asan/tests/use-after-free.cc @@ -0,0 +1,6 @@ +#include <stdlib.h> +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; +} diff --git a/lib/asan/tests/use-after-free.tmpl b/lib/asan/tests/use-after-free.tmpl new file mode 100644 index 000000000000..c4b5c74d9887 --- /dev/null +++ b/lib/asan/tests/use-after-free.tmpl @@ -0,0 +1,10 @@ +.*ERROR: AddressSanitizer heap-use-after-free on address 0x.* at pc 0x.* bp 0x.* sp 0x.* +READ of size 1 at 0x.* thread T0 + #0 0x.* in main .*use-after-free.cc:5 +0x.* is located 5 bytes inside of 10-byte region .0x.*,0x.* +freed by thread T0 here: + #0 0x.* in free + #1 0x.* in main .*use-after-free.cc:[45] +previously allocated by thread T0 here: + #0 0x.* in malloc + #1 0x.* in main .*use-after-free.cc:3 |