diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake --- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake +++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake @@ -62,6 +62,7 @@ ${RISCV32} ${RISCV64}) set(ALL_TSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${PPC64} ${S390X} ${LOONGARCH64}) +set(ALL_TYSAN_SUPPORTED_ARCH ${X86_64} ${ARM64}) set(ALL_UBSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${RISCV64} ${MIPS32} ${MIPS64} ${PPC64} ${S390X} ${SPARC} ${SPARCV9} ${HEXAGON} ${LOONGARCH64}) diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake --- a/compiler-rt/cmake/config-ix.cmake +++ b/compiler-rt/cmake/config-ix.cmake @@ -439,6 +439,7 @@ set(SANITIZER_COMMON_SUPPORTED_OS osx) set(PROFILE_SUPPORTED_OS osx) set(TSAN_SUPPORTED_OS osx) + set(TYSAN_SUPPORTED_OS osx) set(XRAY_SUPPORTED_OS osx) set(FUZZER_SUPPORTED_OS osx) set(ORC_SUPPORTED_OS osx) @@ -566,6 +567,7 @@ list(APPEND FUZZER_SUPPORTED_OS ${platform}) list(APPEND ORC_SUPPORTED_OS ${platform}) list(APPEND UBSAN_SUPPORTED_OS ${platform}) + list(APPEND TYSAN_SUPPORTED_OS ${platform}) list(APPEND LSAN_SUPPORTED_OS ${platform}) list(APPEND STATS_SUPPORTED_OS ${platform}) endif() @@ -614,6 +616,9 @@ list_intersect(PROFILE_SUPPORTED_ARCH ALL_PROFILE_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) + list_intersect(TYSAN_SUPPORTED_ARCH + ALL_TYSAN_SUPPORTED_ARCH + SANITIZER_COMMON_SUPPORTED_ARCH) list_intersect(TSAN_SUPPORTED_ARCH ALL_TSAN_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) @@ -660,6 +665,7 @@ filter_available_targets(HWASAN_SUPPORTED_ARCH ${ALL_HWASAN_SUPPORTED_ARCH}) filter_available_targets(MEMPROF_SUPPORTED_ARCH ${ALL_MEMPROF_SUPPORTED_ARCH}) filter_available_targets(PROFILE_SUPPORTED_ARCH ${ALL_PROFILE_SUPPORTED_ARCH}) + filter_available_targets(TYSAN_SUPPORTED_ARCH ${ALL_TYSAN_SUPPORTED_ARCH}) filter_available_targets(TSAN_SUPPORTED_ARCH ${ALL_TSAN_SUPPORTED_ARCH}) filter_available_targets(UBSAN_SUPPORTED_ARCH ${ALL_UBSAN_SUPPORTED_ARCH}) filter_available_targets(SAFESTACK_SUPPORTED_ARCH @@ -704,7 +710,7 @@ endif() message(STATUS "Compiler-RT supported architectures: ${COMPILER_RT_SUPPORTED_ARCH}") -set(ALL_SANITIZERS asan;dfsan;msan;hwasan;tsan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan) +set(ALL_SANITIZERS asan;dfsan;msan;hwasan;tsan;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan) set(COMPILER_RT_SANITIZERS_TO_BUILD all CACHE STRING "sanitizers to build if supported on the target (all;${ALL_SANITIZERS})") list_replace(COMPILER_RT_SANITIZERS_TO_BUILD all "${ALL_SANITIZERS}") @@ -780,6 +786,13 @@ set(COMPILER_RT_HAS_PROFILE FALSE) endif() +if (COMPILER_RT_HAS_SANITIZER_COMMON AND TYSAN_SUPPORTED_ARCH AND + OS_NAME MATCHES "Linux|Darwin") + set(COMPILER_RT_HAS_TYSAN TRUE) +else() + set(COMPILER_RT_HAS_TYSAN FALSE) +endif() + if (COMPILER_RT_HAS_SANITIZER_COMMON AND TSAN_SUPPORTED_ARCH) if (OS_NAME MATCHES "Linux|Darwin|FreeBSD|NetBSD") set(COMPILER_RT_HAS_TSAN TRUE) diff --git a/compiler-rt/lib/tysan/CMakeLists.txt b/compiler-rt/lib/tysan/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/CMakeLists.txt @@ -0,0 +1,49 @@ +include_directories(..) + +# Runtime library sources and build flags. +set(TYSAN_SOURCES + tysan.cpp + tysan_interceptors.cpp) +set(TYSAN_COMMON_CFLAGS ${SANITIZER_COMMON_CFLAGS}) +append_rtti_flag(OFF TYSAN_COMMON_CFLAGS) +# Prevent clang from generating libc calls. +append_list_if(COMPILER_RT_HAS_FFREESTANDING_FLAG -ffreestanding TYSAN_COMMON_CFLAGS) + +add_compiler_rt_object_libraries(RTTysan_dynamic + OS ${SANITIZER_COMMON_SUPPORTED_OS} + ARCHS ${TYSAN_SUPPORTED_ARCH} + SOURCES ${TYSAN_SOURCES} + ADDITIONAL_HEADERS ${TYSAN_HEADERS} + CFLAGS ${TYSAN_DYNAMIC_CFLAGS} + DEFS ${TYSAN_DYNAMIC_DEFINITIONS}) + + +# Static runtime library. +add_compiler_rt_component(tysan) + + +if(APPLE) + add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS) + + add_compiler_rt_runtime(clang_rt.tysan + SHARED + OS ${SANITIZER_COMMON_SUPPORTED_OS} + ARCHS ${TYSAN_SUPPORTED_ARCH} + OBJECT_LIBS RTTysan_dynamic + RTInterception + RTSanitizerCommon + RTSanitizerCommonLibc + RTSanitizerCommonSymbolizer + CFLAGS ${TYSAN_DYNAMIC_CFLAGS} + LINK_FLAGS ${WEAK_SYMBOL_LINK_FLAGS} + DEFS ${TYSAN_DYNAMIC_DEFINITIONS} + PARENT_TARGET tysan) + + add_compiler_rt_runtime(clang_rt.tysan_static + STATIC + ARCHS ${TYSAN_SUPPORTED_ARCH} + OBJECT_LIBS RTTysan_static + CFLAGS ${TYSAN_CFLAGS} + DEFS ${TYSAN_COMMON_DEFINITIONS} + PARENT_TARGET tysan) +endif() diff --git a/compiler-rt/lib/tysan/lit.cfg b/compiler-rt/lib/tysan/lit.cfg new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/lit.cfg @@ -0,0 +1,35 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = 'TypeSanitizer' + getattr(config, 'name_suffix', 'default') + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +# Setup default compiler flags used with -fsanitize=type option. +clang_tysan_cflags = (["-fsanitize=type", + "-mno-omit-leaf-frame-pointer", + "-fno-omit-frame-pointer", + "-fno-optimize-sibling-calls"] + + [config.target_cflags] + + config.debug_info_flags) +clang_tysan_cxxflags = config.cxx_mode_flags + clang_tysan_cflags + +def build_invocation(compile_flags): + return " " + " ".join([config.clang] + compile_flags) + " " + +config.substitutions.append( ("%clang_tysan ", build_invocation(clang_tysan_cflags)) ) +config.substitutions.append( ("%clangxx_tysan ", build_invocation(clang_tysan_cxxflags)) ) + +# Default test suffixes. +config.suffixes = ['.c', '.cc', '.cpp'] + +# TypeSanitizer tests are currently supported on Linux only. +if config.host_os not in ['Linux']: + config.unsupported = True + +if config.target_arch != 'aarch64': + config.available_features.add('stable-runtime') + diff --git a/compiler-rt/lib/tysan/lit.site.cfg.in b/compiler-rt/lib/tysan/lit.site.cfg.in new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/lit.site.cfg.in @@ -0,0 +1,12 @@ +@LIT_SITE_CFG_IN_HEADER@ + +# Tool-specific config options. +config.name_suffix = "@TYSAN_TEST_CONFIG_SUFFIX@" +config.target_cflags = "@TYSAN_TEST_TARGET_CFLAGS@" +config.target_arch = "@TYSAN_TEST_TARGET_ARCH@" + +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@TYSAN_LIT_SOURCE_DIR@/lit.cfg") diff --git a/compiler-rt/lib/tysan/tysan.h b/compiler-rt/lib/tysan/tysan.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/tysan.h @@ -0,0 +1,79 @@ +//===-- tysan.h -------------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is a part of TypeSanitizer. +// +// Private TySan header. +//===----------------------------------------------------------------------===// + +#ifndef TYSAN_H +#define TYSAN_H + +#include "sanitizer_common/sanitizer_internal_defs.h" + +using __sanitizer::sptr; +using __sanitizer::u16; +using __sanitizer::uptr; + +#include "tysan_platform.h" + +extern "C" { +void tysan_set_type_unknown(const void *addr, uptr size); +void tysan_copy_types(const void *daddr, const void *saddr, uptr size); +} + +namespace __tysan { +extern bool tysan_inited; +extern bool tysan_init_is_running; + +void InitializeInterceptors(); + +enum { TYSAN_MEMBER_TD = 1, TYSAN_STRUCT_TD = 2 }; + +struct tysan_member_type_descriptor { + struct tysan_type_descriptor *Base; + struct tysan_type_descriptor *Access; + uptr Offset; +}; + +struct tysan_struct_type_descriptor { + uptr MemberCount; + struct { + struct tysan_type_descriptor *Type; + uptr Offset; + } Members[1]; // Tail allocated. + // char Name[]; // Tail allocated. +}; + +struct tysan_type_descriptor { + uptr Tag; + union { + tysan_member_type_descriptor Member; + tysan_struct_type_descriptor Struct; + }; +}; + +inline tysan_type_descriptor **shadow_for(const void *ptr) { + return (tysan_type_descriptor **)((((uptr)ptr) & AppMask()) * sizeof(ptr) + + ShadowAddr()); +} + +struct Flags { +#define TYSAN_FLAG(Type, Name, DefaultValue, Description) Type Name; +#include "tysan_flags.inc" +#undef TYSAN_FLAG + + void SetDefaults(); +}; + +extern Flags flags_data; +inline Flags &flags() { return flags_data; } + +} // namespace __tysan + +#endif // TYSAN_H diff --git a/compiler-rt/lib/tysan/tysan.cpp b/compiler-rt/lib/tysan/tysan.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/tysan.cpp @@ -0,0 +1,339 @@ +//===-- tysan.cpp ---------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is a part of TypeSanitizer. +// +// TypeSanitizer runtime. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_flag_parser.h" +#include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_report_decorator.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer_common/sanitizer_symbolizer.h" + +#include "tysan/tysan.h" + +using namespace __sanitizer; +using namespace __tysan; + +extern "C" SANITIZER_INTERFACE_ATTRIBUTE void +tysan_set_type_unknown(const void *addr, uptr size) { + if (tysan_inited) + internal_memset(shadow_for(addr), 0, size * sizeof(uptr)); +} + +extern "C" SANITIZER_INTERFACE_ATTRIBUTE void +tysan_copy_types(const void *daddr, const void *saddr, uptr size) { + if (tysan_inited) + internal_memmove(shadow_for(daddr), shadow_for(saddr), size * sizeof(uptr)); +} + +static const char *getDisplayName(const char *Name) { + if (Name[0] == '\0') + return ""; + + // Clang generates tags for C++ types that demangle as typeinfo. Remove the + // prefix from the generated string. + const char TIPrefix[] = "typeinfo name for "; + + const char *DName = Symbolizer::GetOrInit()->Demangle(Name); + if (!internal_strncmp(DName, TIPrefix, sizeof(TIPrefix) - 1)) + DName += sizeof(TIPrefix) - 1; + + return DName; +} + +static void printTDName(tysan_type_descriptor *td) { + if (((sptr)td) <= 0) { + Printf(""); + return; + } + + switch (td->Tag) { + default: + DCHECK(0); + break; + case TYSAN_MEMBER_TD: + printTDName(td->Member.Access); + if (td->Member.Access != td->Member.Base) { + Printf(" (in "); + printTDName(td->Member.Base); + Printf(" at offset %zu)", td->Member.Offset); + } + break; + case TYSAN_STRUCT_TD: + Printf("%s", getDisplayName( + (char *)(td->Struct.Members + td->Struct.MemberCount))); + break; + } +} + +static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) { + tysan_type_descriptor *RootTD = TD; + + do { + RootTD = TD; + + if (TD->Tag == TYSAN_STRUCT_TD) { + if (TD->Struct.MemberCount > 0) + TD = TD->Struct.Members[0].Type; + else + TD = nullptr; + } else if (TD->Tag == TYSAN_MEMBER_TD) { + TD = TD->Member.Access; + } else { + DCHECK(0); + break; + } + } while (TD); + + return RootTD; +} + +static bool isAliasingLegalUp(tysan_type_descriptor *TDA, + tysan_type_descriptor *TDB) { + // Walk up the tree starting with TDA to see if we reach TDB. + uptr OffsetA = 0, OffsetB = 0; + if (TDB->Tag == TYSAN_MEMBER_TD) { + OffsetB = TDB->Member.Offset; + TDB = TDB->Member.Base; + } + + if (TDA->Tag == TYSAN_MEMBER_TD) { + OffsetA = TDA->Member.Offset; + TDA = TDA->Member.Base; + } + + do { + if (TDA == TDB) + return OffsetA == OffsetB; + + if (TDA->Tag == TYSAN_STRUCT_TD) { + // Reached root type descriptor. + if (!TDA->Struct.MemberCount) + break; + + uptr Idx = 0; + for (; Idx < TDA->Struct.MemberCount - 1; ++Idx) { + if (TDA->Struct.Members[Idx].Offset >= OffsetA) + break; + } + + OffsetA -= TDA->Struct.Members[Idx].Offset; + TDA = TDA->Struct.Members[Idx].Type; + } else { + DCHECK(0); + break; + } + } while (TDA); + + return false; +} + +static bool isAliasingLegal(tysan_type_descriptor *TDA, + tysan_type_descriptor *TDB) { + if (TDA == TDB || !TDB || !TDA) + return true; + + // Aliasing is legal is the two types have different root nodes. + if (getRootTD(TDA) != getRootTD(TDB)) + return true; + + return isAliasingLegalUp(TDA, TDB) || isAliasingLegalUp(TDB, TDA); +} + +namespace __tysan { +class Decorator : public __sanitizer::SanitizerCommonDecorator { +public: + Decorator() : SanitizerCommonDecorator() {} + const char *Warning() { return Red(); } + const char *Name() { return Green(); } + const char *End() { return Default(); } +}; +} // namespace __tysan + +ALWAYS_INLINE +static void reportError(void *Addr, int Size, tysan_type_descriptor *TD, + tysan_type_descriptor *OldTD, const char *AccessStr, + const char *DescStr, int Offset, uptr pc, uptr bp, + uptr sp) { + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: TypeSanitizer: type-aliasing-violation on address %p" + " (pc %p bp %p sp %p tid %llu)\n", + Addr, (void *)pc, (void *)bp, (void *)sp, GetTid()); + Printf("%s", d.End()); + Printf("%s of size %d at %p with type ", AccessStr, Size, Addr); + + Printf("%s", d.Name()); + printTDName(TD); + Printf("%s", d.End()); + + Printf(" %s of type ", DescStr); + + Printf("%s", d.Name()); + printTDName(OldTD); + Printf("%s", d.End()); + + if (Offset != 0) + Printf(" that starts at offset %d\n", Offset); + else + Printf("\n"); + + if (pc) { + + bool request_fast = StackTrace::WillUseFastUnwind(true); + BufferedStackTrace ST; + ST.Unwind(kStackTraceMax, pc, bp, 0, 0, 0, request_fast); + ST.Print(); + } else { + Printf("\n"); + } +} + +extern "C" SANITIZER_INTERFACE_ATTRIBUTE void +__tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) { + GET_CALLER_PC_BP_SP; + + bool IsRead = flags & 1; + bool IsWrite = flags & 2; + const char *AccessStr; + if (IsRead && !IsWrite) + AccessStr = "READ"; + else if (!IsRead && IsWrite) + AccessStr = "WRITE"; + else + AccessStr = "ATOMIC UPDATE"; + + tysan_type_descriptor **OldTDPtr = shadow_for(addr); + tysan_type_descriptor *OldTD = *OldTDPtr; + if (((sptr)OldTD) < 0) { + int i = -((sptr)OldTD); + OldTDPtr -= i; + OldTD = *OldTDPtr; + + if (!isAliasingLegal(td, OldTD)) + reportError(addr, size, td, OldTD, AccessStr, + "accesses part of an existing object", -i, pc, bp, sp); + + return; + } + + if (!isAliasingLegal(td, OldTD)) { + reportError(addr, size, td, OldTD, AccessStr, "accesses an existing object", + 0, pc, bp, sp); + return; + } + + // These types are allowed to alias (or the stored type is unknown), report + // an error if we find an interior type. + + for (int i = 0; i < size; ++i) { + OldTDPtr = shadow_for((void *)(((uptr)addr) + i)); + OldTD = *OldTDPtr; + if (((sptr)OldTD) >= 0 && !isAliasingLegal(td, OldTD)) + reportError(addr, size, td, OldTD, AccessStr, + "partially accesses an object", i, pc, bp, sp); + } +} + +Flags __tysan::flags_data; + +SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_shadow_memory_address; +SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_app_memory_mask; + +#ifdef TYSAN_RUNTIME_VMA +// Runtime detected VMA size. +int __tysan::vmaSize; +#endif + +void Flags::SetDefaults() { +#define TYSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue; +#include "tysan_flags.inc" +#undef TYSAN_FLAG +} + +static void RegisterTySanFlags(FlagParser *parser, Flags *f) { +#define TYSAN_FLAG(Type, Name, DefaultValue, Description) \ + RegisterFlag(parser, #Name, Description, &f->Name); +#include "tysan_flags.inc" +#undef TYSAN_FLAG +} + +static void InitializeFlags() { + SetCommonFlagsDefaults(); + { + CommonFlags cf; + cf.CopyFrom(*common_flags()); + cf.external_symbolizer_path = GetEnv("TYSAN_SYMBOLIZER_PATH"); + OverrideCommonFlags(cf); + } + + flags().SetDefaults(); + + FlagParser parser; + RegisterCommonFlags(&parser); + RegisterTySanFlags(&parser, &flags()); + parser.ParseString(GetEnv("TYSAN_OPTIONS")); + InitializeCommonFlags(); + if (Verbosity()) + ReportUnrecognizedFlags(); + if (common_flags()->help) + parser.PrintFlagDescriptions(); +} + +static void TySanInitializePlatformEarly() { + AvoidCVE_2016_2143(); +#ifdef TYSAN_RUNTIME_VMA + vmaSize = (MostSignificantSetBitIndex(GET_CURRENT_FRAME()) + 1); +#if defined(__aarch64__) && !SANITIZER_APPLE + if (vmaSize != 39 && vmaSize != 42 && vmaSize != 48) { + Printf("FATAL: TypeSanitizer: unsupported VMA range\n"); + Printf("FATAL: Found %d - Supported 39, 42 and 48\n", vmaSize); + Die(); + } +#endif +#endif + + __sanitizer::InitializePlatformEarly(); + + __tysan_shadow_memory_address = ShadowAddr(); + __tysan_app_memory_mask = AppMask(); +} + +namespace __tysan { +bool tysan_inited = false; +bool tysan_init_is_running; +} // namespace __tysan + +extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __tysan_init() { + CHECK(!tysan_init_is_running); + if (tysan_inited) + return; + tysan_init_is_running = true; + + InitializeFlags(); + TySanInitializePlatformEarly(); + + InitializeInterceptors(); + + if (!MmapFixedNoReserve(ShadowAddr(), AppAddr() - ShadowAddr())) + Die(); + + tysan_init_is_running = false; + tysan_inited = true; +} + +#if SANITIZER_CAN_USE_PREINIT_ARRAY +__attribute__((section(".preinit_array"), + used)) static void (*tysan_init_ptr)() = __tysan_init; +#endif diff --git a/compiler-rt/lib/tysan/tysan.syms.extra b/compiler-rt/lib/tysan/tysan.syms.extra new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/tysan.syms.extra @@ -0,0 +1,2 @@ +tysan_* +__tysan_* diff --git a/compiler-rt/lib/tysan/tysan_flags.inc b/compiler-rt/lib/tysan/tysan_flags.inc new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/tysan_flags.inc @@ -0,0 +1,17 @@ +//===-- tysan_flags.inc ---------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TySan runtime flags. +// +//===----------------------------------------------------------------------===// +#ifndef TYSAN_FLAG +#error "Define TYSAN_FLAG prior to including this file!" +#endif + +// TYSAN_FLAG(Type, Name, DefaultValue, Description) +// See COMMON_FLAG in sanitizer_flags.inc for more details. diff --git a/compiler-rt/lib/tysan/tysan_interceptors.cpp b/compiler-rt/lib/tysan/tysan_interceptors.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/tysan_interceptors.cpp @@ -0,0 +1,231 @@ +//===-- tysan_interceptors.cpp --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is a part of TypeSanitizer. +// +// Interceptors for standard library functions. +//===----------------------------------------------------------------------===// + +#include "interception/interception.h" +#include "sanitizer_common/sanitizer_common.h" +#include "tysan/tysan.h" + +#if SANITIZER_LINUX && !SANITIZER_ANDROID +#define TYSAN_INTERCEPT___STRDUP 1 +#else +#define TYSAN_INTERCEPT___STRDUP 0 +#endif + +#if SANITIZER_LINUX +extern "C" int mallopt(int param, int value); +#endif + +using namespace __sanitizer; +using namespace __tysan; + +static const uptr early_alloc_buf_size = 16384; +static uptr allocated_bytes; +static char early_alloc_buf[early_alloc_buf_size]; + +static bool isInEarlyAllocBuf(const void *ptr) { + return ((uptr)ptr >= (uptr)early_alloc_buf && + ((uptr)ptr - (uptr)early_alloc_buf) < sizeof(early_alloc_buf)); +} + +// Handle allocation requests early (before all interceptors are setup). dlsym, +// for example, calls calloc. +static void *handleEarlyAlloc(uptr size) { + void *mem = (void *)&early_alloc_buf[allocated_bytes]; + allocated_bytes += size; + CHECK_LT(allocated_bytes, early_alloc_buf_size); + return mem; +} + +INTERCEPTOR(void *, memset, void *dst, int v, uptr size) { + if (!tysan_inited && REAL(memset) == nullptr) + return internal_memset(dst, v, size); + + void *res = REAL(memset)(dst, v, size); + tysan_set_type_unknown(dst, size); + return res; +} + +INTERCEPTOR(void *, memmove, void *dst, const void *src, uptr size) { + if (!tysan_inited && REAL(memmove) == nullptr) + return internal_memmove(dst, src, size); + + void *res = REAL(memmove)(dst, src, size); + tysan_copy_types(dst, src, size); + return res; +} + +INTERCEPTOR(void *, memcpy, void *dst, const void *src, uptr size) { + if (!tysan_inited && REAL(memcpy) == nullptr) { + // memmove is used here because on some platforms this will also + // intercept the memmove implementation. + return internal_memmove(dst, src, size); + } + + void *res = REAL(memcpy)(dst, src, size); + tysan_copy_types(dst, src, size); + return res; +} + +INTERCEPTOR(void *, mmap, void *addr, SIZE_T length, int prot, int flags, + int fd, OFF_T offset) { + void *res = REAL(mmap)(addr, length, prot, flags, fd, offset); + if (res != (void *)-1) + tysan_set_type_unknown(res, RoundUpTo(length, GetPageSize())); + return res; +} + +#ifndef SANITIZER_APPLE +INTERCEPTOR(void *, mmap64, void *addr, SIZE_T length, int prot, int flags, + int fd, OFF64_T offset) { + void *res = REAL(mmap64)(addr, length, prot, flags, fd, offset); + if (res != (void *)-1) + tysan_set_type_unknown(res, RoundUpTo(length, GetPageSize())); + return res; +} +#endif + +INTERCEPTOR(char *, strdup, const char *s) { + char *res = REAL(strdup)(s); + if (res) + tysan_copy_types(res, const_cast(s), internal_strlen(s)); + return res; +} + +#if TYSAN_INTERCEPT___STRDUP +INTERCEPTOR(char *, __strdup, const char *s) { + char *res = REAL(__strdup)(s); + if (res) + tysan_copy_types(res, const_cast(s), internal_strlen(s)); + return res; +} +#endif // TYSAN_INTERCEPT___STRDUP + +INTERCEPTOR(void *, malloc, uptr size) { + if (tysan_init_is_running && REAL(malloc) == nullptr) + return handleEarlyAlloc(size); + + void *res = REAL(malloc)(size); + if (res) + tysan_set_type_unknown(res, size); + return res; +} + +INTERCEPTOR(void *, realloc, void *ptr, uptr size) { + void *res = REAL(realloc)(ptr, size); + // We might want to copy the types from the original allocation (although + // that would require that we knew its size). + if (res) + tysan_set_type_unknown(res, size); + return res; +} + +INTERCEPTOR(void *, calloc, uptr nmemb, uptr size) { + if (tysan_init_is_running && REAL(calloc) == nullptr) + return handleEarlyAlloc(nmemb * size); + + void *res = REAL(calloc)(nmemb, size); + if (res) + tysan_set_type_unknown(res, nmemb * size); + return res; +} + +INTERCEPTOR(void, free, void *p) { + // There are only a few early allocation requests, + // so we simply skip the free. + if (isInEarlyAllocBuf(p)) + return; + REAL(free)(p); +} + +INTERCEPTOR(void *, valloc, uptr size) { + void *res = REAL(valloc)(size); + if (res) + tysan_set_type_unknown(res, size); + return res; +} + +#ifndef SANITIZER_APPLE +INTERCEPTOR(void *, memalign, uptr alignment, uptr size) { + void *res = REAL(memalign)(alignment, size); + if (res) + tysan_set_type_unknown(res, size); + return res; +} + +INTERCEPTOR(void *, __libc_memalign, uptr alignment, uptr size) { + void *res = REAL(__libc_memalign)(alignment, size); + if (res) + tysan_set_type_unknown(res, size); + return res; +} + +INTERCEPTOR(void *, pvalloc, uptr size) { + void *res = REAL(pvalloc)(size); + if (res) + tysan_set_type_unknown(res, size); + return res; +} +#endif + +INTERCEPTOR(void *, aligned_alloc, uptr alignment, uptr size) { + void *res = REAL(aligned_alloc)(alignment, size); + if (res) + tysan_set_type_unknown(res, size); + return res; +} + +INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) { + int res = REAL(posix_memalign)(memptr, alignment, size); + if (res == 0 && *memptr) + tysan_set_type_unknown(*memptr, size); + return res; +} + +namespace __tysan { +void InitializeInterceptors() { + static int inited = 0; + CHECK_EQ(inited, 0); + + // Instruct libc malloc to consume less memory. +#if SANITIZER_LINUX + mallopt(1, 0); // M_MXFAST + mallopt(-3, 32 * 1024); // M_MMAP_THRESHOLD +#endif + + INTERCEPT_FUNCTION(mmap); + + INTERCEPT_FUNCTION(mmap64); + + INTERCEPT_FUNCTION(strdup); +#if TYSAN_INTERCEPT___STRDUP + INTERCEPT_FUNCTION(__strdup); +#endif + + INTERCEPT_FUNCTION(malloc); + INTERCEPT_FUNCTION(calloc); + INTERCEPT_FUNCTION(free); + INTERCEPT_FUNCTION(realloc); + INTERCEPT_FUNCTION(valloc); + INTERCEPT_FUNCTION(memalign); + INTERCEPT_FUNCTION(__libc_memalign); + INTERCEPT_FUNCTION(pvalloc); + INTERCEPT_FUNCTION(aligned_alloc); + INTERCEPT_FUNCTION(posix_memalign); + + INTERCEPT_FUNCTION(memset); + INTERCEPT_FUNCTION(memmove); + INTERCEPT_FUNCTION(memcpy); + + inited = 1; +} +} // namespace __tysan diff --git a/compiler-rt/lib/tysan/tysan_platform.h b/compiler-rt/lib/tysan/tysan_platform.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/tysan/tysan_platform.h @@ -0,0 +1,93 @@ +//===------------------------ tysan_platform.h ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is a part of TypeSanitizer. +// +// Platform specific information for TySan. +//===----------------------------------------------------------------------===// + +#ifndef TYSAN_PLATFORM_H +#define TYSAN_PLATFORM_H + +namespace __tysan { + +#if defined(__x86_64__) || SANITIZER_APPLE +struct Mapping { + static const uptr kShadowAddr = 0x010000000000ull; + static const uptr kAppAddr = 0x550000000000ull; + static const uptr kAppMemMsk = ~0x780000000000ull; +}; +#elif defined(__aarch64__) +struct Mapping39 { + static const uptr kShadowAddr = 0x0800000000ull; + static const uptr kAppAddr = 0x5500000000ull; + static const uptr kAppMemMsk = ~0x7800000000ull; +}; + +struct Mapping42 { + static const uptr kShadowAddr = 0x10000000000ull; + static const uptr kAppAddr = 0x2aa00000000ull; + static const uptr kAppMemMsk = ~0x3c000000000ull; +}; + +struct Mapping48 { + static const uptr kShadowAddr = 0x0002000000000ull; + static const uptr kAppAddr = 0x0aaaa00000000ull; + static const uptr kAppMemMsk = ~0x0fff800000000ull; +}; +#define TYSAN_RUNTIME_VMA 1 +#else +#error "TySan not supported for this platform!" +#endif + +#if TYSAN_RUNTIME_VMA +extern int vmaSize; +#endif + +enum MappingType { MAPPING_SHADOW_ADDR, MAPPING_APP_ADDR, MAPPING_APP_MASK }; + +template uptr MappingImpl(void) { + switch (Type) { + case MAPPING_SHADOW_ADDR: + return Mapping::kShadowAddr; + case MAPPING_APP_ADDR: + return Mapping::kAppAddr; + case MAPPING_APP_MASK: + return Mapping::kAppMemMsk; + } +} + +template uptr MappingArchImpl(void) { +#if defined(__aarch64__) && !SANITIZER_APPLE + switch (vmaSize) { + case 39: + return MappingImpl(); + case 42: + return MappingImpl(); + case 48: + return MappingImpl(); + } + DCHECK(0); + return 0; +#else + return MappingImpl(); +#endif +} + +ALWAYS_INLINE +uptr ShadowAddr() { return MappingArchImpl(); } + +ALWAYS_INLINE +uptr AppAddr() { return MappingArchImpl(); } + +ALWAYS_INLINE +uptr AppMask() { return MappingArchImpl(); } + +} // namespace __tysan + +#endif diff --git a/compiler-rt/lib/tysan/weak_symbols.txt b/compiler-rt/lib/tysan/weak_symbols.txt new file mode 100644 diff --git a/compiler-rt/test/tysan/CMakeLists.txt b/compiler-rt/test/tysan/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/CMakeLists.txt @@ -0,0 +1,32 @@ +set(TYSAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(TYSAN_TESTSUITES) + +set(TYSAN_TEST_ARCH ${TYSAN_SUPPORTED_ARCH}) +if(APPLE) + darwin_filter_host_archs(TYSAN_SUPPORTED_ARCH TYSAN_TEST_ARCH) +endif() + +foreach(arch ${TYSAN_TEST_ARCH}) + set(TYSAN_TEST_TARGET_ARCH ${arch}) + string(TOLOWER "-${arch}" TYSAN_TEST_CONFIG_SUFFIX) + get_test_cc_for_arch(${arch} TYSAN_TEST_TARGET_CC TYSAN_TEST_TARGET_CFLAGS) + string(TOUPPER ${arch} ARCH_UPPER_CASE) + set(CONFIG_NAME ${ARCH_UPPER_CASE}Config) + + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py) + list(APPEND TYSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}) +endforeach() + +set(TYSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +if(NOT COMPILER_RT_STANDALONE_BUILD) + list(APPEND TYSAN_TEST_DEPS tysan) +endif() + +add_lit_testsuite(check-tysan "Running the TypeSanitizer tests" + ${TYSAN_TESTSUITES} + DEPENDS ${TYSAN_TEST_DEPS} + ) +set_target_properties(check-tysan PROPERTIES FOLDER "Compiler-RT Misc") diff --git a/compiler-rt/test/tysan/anon-ns.cpp b/compiler-rt/test/tysan/anon-ns.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/anon-ns.cpp @@ -0,0 +1,41 @@ +// RUN: %clangxx_tysan -O0 %s -c -o %t.o +// RUN: %clangxx_tysan -O0 %s -DPMAIN -c -o %tm.o +// RUN: %clangxx_tysan -O0 %t.o %tm.o -o %t +// RUN: %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include + +// This test demonstrates that the types from anonymous namespaces are +// different in different translation units (while the char* type is the same). + +namespace { +struct X { + X(int i, int j) : a(i), b(j) {} + int a; + int b; +}; +} // namespace + +#ifdef PMAIN +void foo(void *context, int i); +char fbyte(void *context); + +int main() { + X x(5, 6); + foo((void *)&x, 8); + std::cout << "fbyte: " << fbyte((void *)&x) << "\n"; +} +#else +void foo(void *context, int i) { + X *x = (X *)context; + x->b = i; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type int (in (anonymous namespace)::X at offset 4) accesses an existing object of type int (in (anonymous namespace)::X at offset 4) + // CHECK: {{#0 0x.* in foo\(void\*, int\) .*anon-ns.cpp:}}[[@LINE-3]] +} + +char fbyte(void *context) { return *(char *)context; } +#endif + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/anon-same-struct.c b/compiler-rt/test/tysan/anon-same-struct.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/anon-same-struct.c @@ -0,0 +1,26 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include + +// The two anonymous structs are structurally identical. As a result, we don't +// report an aliasing violation here. +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation + +typedef struct { + int i1; +} s1; +typedef struct { + int i2; +} s2; + +void f(s1 *s1p, s2 *s2p) { + s1p->i1 = 2; + s2p->i2 = 3; + printf("%i\n", s1p->i1); +} + +int main() { + s1 s = {.i1 = 1}; + f(&s, (s2 *)&s); +} diff --git a/compiler-rt/test/tysan/anon-struct.c b/compiler-rt/test/tysan/anon-struct.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/anon-struct.c @@ -0,0 +1,27 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include + +typedef struct { + int i1, i1b; +} s1; +typedef struct { + int i2, i2b, i2c; +} s2; + +void f(s1 *s1p, s2 *s2p) { + s1p->i1 = 2; + s2p->i2 = 3; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type int (in at offset 0) accesses an existing object of type int (in at offset 0) + // CHECK: {{#0 0x.* in f .*anon-struct.c:}}[[@LINE-3]] + printf("%i\n", s1p->i1); +} + +int main() { + s1 s = {.i1 = 1, .i1b = 5}; + f(&s, (s2 *)&s); +} + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/basic.c b/compiler-rt/test/tysan/basic.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/basic.c @@ -0,0 +1,65 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t 10 >%t.out.0 2>&1 +// RUN: FileCheck %s < %t.out.0 +// RUN: %clang_tysan -O2 %s -o %t && %run %t 10 >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include +#include +#include + +void __attribute__((noinline)) add_flt(float *a) { + *a += 2.0f; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: READ of size 4 at {{.*}} with type float accesses an existing object of type int + // CHECK: {{#0 0x.* in add_flt .*basic.c:}}[[@LINE-3]] + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type float accesses an existing object of type int + // CHECK: {{#0 0x.* in add_flt .*basic.c:}}[[@LINE-6]] + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: READ of size 4 at {{.*}} with type float accesses an existing object of type long + // CHECK: {{#0 0x.* in add_flt .*basic.c:}}[[@LINE-9]] + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type float accesses an existing object of type long + // CHECK: {{#0 0x.* in add_flt .*basic.c:}}[[@LINE-12]] + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: READ of size 4 at {{.*}} with type float accesses part of an existing object of type long that starts at offset -4 + // CHECK: {{#0 0x.* in add_flt .*basic.c:}}[[@LINE-15]] + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type float accesses part of an existing object of type long that starts at offset -4 + // CHECK: {{#0 0x.* in add_flt .*basic.c:}}[[@LINE-18]] + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: READ of size 4 at {{.*}} with type float partially accesses an object of type short that starts at offset 2 + // CHECK: {{#0 0x.* in add_flt .*basic.c:}}[[@LINE-21]] +} + +int main(int argc, char *argv[]) { + int x = atoi(argv[1]); + add_flt((float *)&x); + printf("x = %d\n", x); + + long y = x; + add_flt((float *)&y); + printf("y = %ld\n", y); + + add_flt(((float *)&y) + 1); + printf("y = %ld\n", y); + + char *mem = (char *)malloc(4 * sizeof(short)); + memset(mem, 0, 4 * sizeof(short)); + *(short *)(mem + 2) = x; + add_flt((float *)mem); + short s1 = *(short *)mem; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: READ of size 2 at {{.*}} with type short accesses an existing object of type float + // CHECK: {{#0 0x.* in main .*basic.c:}}[[@LINE-3]] + short s2 = *(short *)(mem + 2); + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: READ of size 2 at {{.*}} with type short accesses part of an existing object of type float that starts at offset -2 + // CHECK: {{#0 0x.* in main .*basic.c:}}[[@LINE-3]] + printf("m[0] = %d, m[1] = %d\n", s1, s2); + free(mem); + + return 0; +} + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/char-memcpy.c b/compiler-rt/test/tysan/char-memcpy.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/char-memcpy.c @@ -0,0 +1,45 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out.0 2>&1 +// RUN: FileCheck %s < %t.out.0 +// RUN: %clang_tysan -O2 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include + +// There's no type-based-aliasing violation here: the memcpy is implemented +// using only char* or unsigned char* (both of which may alias anything). +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation + +void my_memcpy_uchar(void *dest, void *src, int n) { + unsigned char *p = dest, *q = src, *end = p + n; + while (p < end) + *p++ = *q++; +} + +void my_memcpy_char(void *dest, void *src, int n) { + char *p = dest, *q = src, *end = p + n; + while (p < end) + *p++ = *q++; +} + +void test_uchar() { + struct S { + short x; + short *r; + } s = {10, &s.x}, s2; + my_memcpy_uchar(&s2, &s, sizeof(struct S)); + printf("%d\n", *(s2.r)); +} + +void test_char() { + struct S { + short x; + short *r; + } s = {10, &s.x}, s2; + my_memcpy_char(&s2, &s, sizeof(struct S)); + printf("%d\n", *(s2.r)); +} + +int main() { + test_uchar(); + test_char(); +} diff --git a/compiler-rt/test/tysan/global.c b/compiler-rt/test/tysan/global.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/global.c @@ -0,0 +1,31 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out +#include +#include + +float P; +long L; + +int main() { + *(int *)&P = 5; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type int accesses an existing object of type float + // CHECK: {{#0 0x.* in main .*global.c:}}[[@LINE-3]] + + void *mem = malloc(sizeof(long)); + *(int *)mem = 6; + memcpy(mem, &L, sizeof(L)); + *(int *)mem = 8; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type int accesses an existing object of type long + // CHECK: {{#0 0x.* in main .*global.c:}}[[@LINE-3]] + int r = *(((int *)mem) + 1); + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: READ of size 4 at {{.*}} with type int accesses part of an existing object of type long that starts at offset -4 + // CHECK: {{#0 0x.* in main .*global.c:}}[[@LINE-3]] + free(mem); + + return r; +} + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/int-long.c b/compiler-rt/test/tysan/int-long.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/int-long.c @@ -0,0 +1,21 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include + +long foo(int *x, long *y) { + *x = 0; + *y = 1; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 8 at {{.*}} with type long accesses an existing object of type int + // CHECK: {{#0 0x.* in foo .*int-long.c:}}[[@LINE-3]] + + return *x; +} + +int main(void) { + long l; + printf("%ld\n", foo((int *)&l, &l)); +} + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/lit.cfg.py b/compiler-rt/test/tysan/lit.cfg.py new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/lit.cfg.py @@ -0,0 +1,139 @@ +# -*- Python -*- + +import os +import platform +import re + +import lit.formats + +# Get shlex.quote if available (added in 3.3), and fall back to pipes.quote if +# it's not available. +try: + import shlex + sh_quote = shlex.quote +except: + import pipes + sh_quote = pipes.quote + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if attr_value == None: + lit_config.fatal( + "No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg.py " % attr_name) + return attr_value + +def push_dynamic_library_lookup_path(config, new_path): + if platform.system() == 'Windows': + dynamic_library_lookup_var = 'PATH' + elif platform.system() == 'Darwin': + dynamic_library_lookup_var = 'DYLD_LIBRARY_PATH' + else: + dynamic_library_lookup_var = 'LD_LIBRARY_PATH' + + new_ld_library_path = os.path.pathsep.join( + (new_path, config.environment.get(dynamic_library_lookup_var, ''))) + config.environment[dynamic_library_lookup_var] = new_ld_library_path + + if platform.system() == 'FreeBSD': + dynamic_library_lookup_var = 'LD_32_LIBRARY_PATH' + new_ld_32_library_path = os.path.pathsep.join( + (new_path, config.environment.get(dynamic_library_lookup_var, ''))) + config.environment[dynamic_library_lookup_var] = new_ld_32_library_path + + if platform.system() == 'SunOS': + dynamic_library_lookup_var = 'LD_LIBRARY_PATH_32' + new_ld_library_path_32 = os.path.pathsep.join( + (new_path, config.environment.get(dynamic_library_lookup_var, ''))) + config.environment[dynamic_library_lookup_var] = new_ld_library_path_32 + + dynamic_library_lookup_var = 'LD_LIBRARY_PATH_64' + new_ld_library_path_64 = os.path.pathsep.join( + (new_path, config.environment.get(dynamic_library_lookup_var, ''))) + config.environment[dynamic_library_lookup_var] = new_ld_library_path_64 + +# Setup config name. +config.name = 'TypeSanitizer' + config.name_suffix + +# Platform-specific default TYSAN_OPTIONS for lit tests. +default_tysan_opts = list(config.default_sanitizer_opts) + +# On Darwin, leak checking is not enabled by default. Enable on macOS +# tests to prevent regressions +if config.host_os == 'Darwin' and config.apple_platform == 'osx': + default_tysan_opts += ['detect_leaks=1'] + +default_tysan_opts_str = ':'.join(default_tysan_opts) +if default_tysan_opts_str: + config.environment['TYSAN_OPTIONS'] = default_tysan_opts_str + default_tysan_opts_str += ':' +config.substitutions.append(('%env_tysan_opts=', + 'env TYSAN_OPTIONS=' + default_tysan_opts_str)) + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +if config.host_os not in ['FreeBSD', 'NetBSD']: + libdl_flag = "-ldl" +else: + libdl_flag = "" + +# GCC-ASan doesn't link in all the necessary libraries automatically, so +# we have to do it ourselves. +if config.compiler_id == 'GNU': + extra_link_flags = ["-pthread", "-lstdc++", libdl_flag] +else: + extra_link_flags = [] + +# Setup default compiler flags used with -fsanitize=address option. +# FIXME: Review the set of required flags and check if it can be reduced. +target_cflags = [get_required_attr(config, "target_cflags")] + extra_link_flags +target_cxxflags = config.cxx_mode_flags + target_cflags +clang_tysan_static_cflags = (["-fsanitize=type", + "-mno-omit-leaf-frame-pointer", + "-fno-omit-frame-pointer", + "-fno-optimize-sibling-calls"] + + config.debug_info_flags + target_cflags) +if config.target_arch == 's390x': + clang_tysan_static_cflags.append("-mbackchain") +clang_tysan_static_cxxflags = config.cxx_mode_flags + clang_tysan_static_cflags + +clang_tysan_cflags = clang_tysan_static_cflags +clang_tysan_cxxflags = clang_tysan_static_cxxflags + +def build_invocation(compile_flags): + return " " + " ".join([config.clang] + compile_flags) + " " + +config.substitutions.append( ("%clang ", build_invocation(target_cflags)) ) +config.substitutions.append( ("%clangxx ", build_invocation(target_cxxflags)) ) +config.substitutions.append( ("%clang_tysan ", build_invocation(clang_tysan_cflags)) ) +config.substitutions.append( ("%clangxx_tysan ", build_invocation(clang_tysan_cxxflags)) ) + + +# FIXME: De-hardcode this path. +tysan_source_dir = os.path.join( + get_required_attr(config, "compiler_rt_src_root"), "lib", "tysan") +python_exec = sh_quote(get_required_attr(config, "python_executable")) + +# Set LD_LIBRARY_PATH to pick dynamic runtime up properly. +push_dynamic_library_lookup_path(config, config.compiler_rt_libdir) + +# Default test suffixes. +config.suffixes = ['.c', '.cpp'] + +if config.host_os == 'Darwin': + config.suffixes.append('.mm') + +if config.host_os == 'Windows': + config.substitutions.append(('%fPIC', '')) + config.substitutions.append(('%fPIE', '')) + config.substitutions.append(('%pie', '')) +else: + config.substitutions.append(('%fPIC', '-fPIC')) + config.substitutions.append(('%fPIE', '-fPIE')) + config.substitutions.append(('%pie', '-pie')) + +# Only run the tests on supported OSs. +if config.host_os not in ['Linux', 'Darwin',]: + config.unsupported = True diff --git a/compiler-rt/test/tysan/lit.site.cfg.py.in b/compiler-rt/test/tysan/lit.site.cfg.py.in new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/lit.site.cfg.py.in @@ -0,0 +1,17 @@ +@LIT_SITE_CFG_IN_HEADER@ + +# Tool-specific config options. +config.name_suffix = "@TYSAN_TEST_CONFIG_SUFFIX@" +config.target_cflags = "@TYSAN_TEST_TARGET_CFLAGS@" +config.clang = "@TYSAN_TEST_TARGET_CC@" +config.bits = "@TYSAN_TEST_BITS@" +config.arm_thumb = "@COMPILER_RT_ARM_THUMB@" +config.apple_platform = "@TYSAN_TEST_APPLE_PLATFORM@" +config.apple_platform_min_deployment_target_flag = "@TYSAN_TEST_MIN_DEPLOYMENT_TARGET_FLAG@" +config.target_arch = "@TYSAN_TEST_TARGET_ARCH@" + +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@TYSAN_LIT_SOURCE_DIR@/lit.cfg.py") diff --git a/compiler-rt/test/tysan/ptr-float.c b/compiler-rt/test/tysan/ptr-float.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/ptr-float.c @@ -0,0 +1,19 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +float *P; +void zero_array() { + int i; + for (i = 0; i < 1; ++i) + P[i] = 0.0f; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type float accesses an existing object of type any pointer + // CHECK: {{#0 0x.* in zero_array .*ptr-float.c:}}[[@LINE-3]] +} + +int main() { + P = (float *)&P; + zero_array(); +} + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/struct-offset-multiple-compilation-units.cpp b/compiler-rt/test/tysan/struct-offset-multiple-compilation-units.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/struct-offset-multiple-compilation-units.cpp @@ -0,0 +1,51 @@ +// RUN: %clangxx_tysan -O0 %s -c -o %t.o +// RUN: %clangxx_tysan -O0 %s -DPMAIN -c -o %tm.o +// RUN: %clangxx_tysan -O0 %s -DPINIT -c -o %tinit.o +// RUN: %clangxx_tysan -O0 %t.o %tm.o %tinit.o -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include +#include + +extern "C" { +typedef struct X { + int *start; + int *end; + int i; +} X; +}; + +#ifdef PMAIN +int foo(struct X *); +void bar(struct X *); +void init(struct X *); + +int main() { + struct X x; + init(&x); + printf("%d\n", foo(&x)); + free(x.start); + return 0; +} + +#elif PINIT + +void init(struct X *x) { + x->start = (int *)calloc(100, sizeof(int)); + x->end = x->start + 99; + x->i = 0; +} + +#else + +__attribute__((noinline)) int foo(struct X *x) { + if (x->start < x->end) + return 30; + return 10; +} + +void bar(struct X *x) { x->end = NULL; } + +#endif + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/struct-offset.c b/compiler-rt/test/tysan/struct-offset.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/struct-offset.c @@ -0,0 +1,26 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include +#include + +struct X { + int i; + int j; +}; + +int foo(struct X *p, struct X *q) { + q->j = 1; + p->i = 0; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type int (in X at offset 0) accesses an existing object of type int (in X at offset 4) + // CHECK: {{#0 0x.* in foo .*struct-offset.c:}}[[@LINE-3]] + return q->j; +} + +int main() { + unsigned char *p = malloc(3 * sizeof(int)); + printf("%i\n", foo((struct X *)(p + sizeof(int)), (struct X *)p)); +} + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/struct.c b/compiler-rt/test/tysan/struct.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/struct.c @@ -0,0 +1,39 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include + +typedef struct S1 { + int i1; +} s1; +typedef struct S2 { + int i2; +} s2; + +void g(int *i) { + *i = 5; + printf("%i\n", *i); +} + +void h(char *c) { + *c = 5; + printf("%i\n", (int)*c); +} + +void f(s1 *s1p, s2 *s2p) { + s1p->i1 = 2; + s2p->i2 = 3; + // CHECK: ERROR: TypeSanitizer: type-aliasing-violation + // CHECK: WRITE of size 4 at {{.*}} with type int (in S2 at offset 0) accesses an existing object of type int (in S1 at offset 0) + // CHECK: {{#0 0x.* in f .*struct.c:}}[[@LINE-3]] + printf("%i\n", s1p->i1); +} + +int main() { + s1 s = {.i1 = 1}; + f(&s, (s2 *)&s); + g(&s.i1); + h((char *)&s.i1); +} + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation diff --git a/compiler-rt/test/tysan/union-wr-wr.c b/compiler-rt/test/tysan/union-wr-wr.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tysan/union-wr-wr.c @@ -0,0 +1,18 @@ +// RUN: %clang_tysan -O0 %s -o %t && %run %t >%t.out 2>&1 +// RUN: FileCheck %s < %t.out + +#include + +// CHECK-NOT: ERROR: TypeSanitizer: type-aliasing-violation + +int main() { + union { + int i; + short s; + } u; + + u.i = 42; + u.s = 1; + + printf("%d\n", u.i); +}