Index: docs/ShadowCallStack.rst =================================================================== --- /dev/null +++ docs/ShadowCallStack.rst @@ -0,0 +1,74 @@ +=============== +ShadowCallStack +=============== + +.. contents:: + :local: + +Introduction +============ + +ShadowCallStack is an experimental instrumentation pass, currently only +implemented for x86_64, that protects programs against attacks based on stack +buffer overflows. It works by saving a function's return address to a +separately allocated 'shadow call stack' in the function prolog and checking the +return address on the stack against the shadow call stack in the function +epilog. To optimize for memory consumption and cache locality, the shadow call +stack stores an index followed by an array of return addresses. This is in +contrast to other schemes, like :doc:`SafeStack`, that mirror the entire stack +and trade-off consuming more memory for shorter function prologs and epilogs +with fewer memory accesses. + +Compatibility +------------- + +ShadowCallStack currently only supports x86_64. A runtime is not currently +provided in compiler-rt so one must be provided by the compiled application. + +Security +-------- + +ShadowCallStack is intended to be a stronger alternative to `-fstack-protector`. +It protects from non-linear overflows and arbitrary memory writes to the return +address slot; however, similarly to `-fstack-protector` this protection suffers +from race conditions because of the call-return semantics on x86_64. There is a +short race between the call instruction and the first instruction in the +function that reads the return address where an attacker could overwrite the +return address and bypass ShadowCallStack. Similarly, there is a time-of-check- +to-time-of-use race in the function prolog where an attacker could overwrite the +return address after it has been checked and before it has been returned to. +Modifying the call-return semantics to fix this on x86_64 would incur an +unacceptable performance overhead. + +The instrumentation makes use of the `gs` segment register to reference the +shadow call stack. This means that references to the shadow call stack do not +have to be written to memory and attackers that can read arbitrary memory can +not easily leak its address. + +Usage +===== + +To enable ShadowCallStack, just pass ``-fsanitize=shadow-call-stack`` flag to +both compile and link command lines. + +``__has_feature(shadow_call_stack)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases one may need to execute different code depending on whether +ShadowCallStack is enabled. The macro ``__has_feature(shadow_call_stack)`` can +be used for this purpose. + +.. code-block:: c + + #if defined(__has_feature) + # if __has_feature(shadow_call_stack) + // code that builds only under ShadowCallStack + # endif + #endif + +``__attribute__((no_sanitize("shadow-call-stack")))`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use ``__attribute__((no_sanitize("shadow-call-stack")))`` on a function +declaration to specify that the shadow call stack instrumentation should not be +applied to that function, even if enabled globally. Index: docs/index.rst =================================================================== --- docs/index.rst +++ docs/index.rst @@ -36,6 +36,7 @@ ControlFlowIntegrity LTOVisibility SafeStack + ShadowCallStack SourceBasedCodeCoverage Modules MSVCCompatibility Index: include/clang/Basic/Sanitizers.def =================================================================== --- include/clang/Basic/Sanitizers.def +++ include/clang/Basic/Sanitizers.def @@ -110,6 +110,9 @@ // Safe Stack SANITIZER("safe-stack", SafeStack) +// Shadow Call Stack +SANITIZER("shadow-call-stack", ShadowCallStack) + // -fsanitize=undefined includes all the sanitizers which have low overhead, no // ABI or address space layout implications, and only catch undefined behavior. SANITIZER_GROUP("undefined", Undefined, Index: lib/CodeGen/CGDeclCXX.cpp =================================================================== --- lib/CodeGen/CGDeclCXX.cpp +++ lib/CodeGen/CGDeclCXX.cpp @@ -343,6 +343,10 @@ !isInSanitizerBlacklist(SanitizerKind::SafeStack, Fn, Loc)) Fn->addFnAttr(llvm::Attribute::SafeStack); + if (getLangOpts().Sanitize.has(SanitizerKind::ShadowCallStack) && + !isInSanitizerBlacklist(SanitizerKind::ShadowCallStack, Fn, Loc)) + Fn->addFnAttr(llvm::Attribute::ShadowCallStack); + return Fn; } Index: lib/CodeGen/CodeGenFunction.cpp =================================================================== --- lib/CodeGen/CodeGenFunction.cpp +++ lib/CodeGen/CodeGenFunction.cpp @@ -861,6 +861,8 @@ Fn->addFnAttr(llvm::Attribute::SanitizeMemory); if (SanOpts.has(SanitizerKind::SafeStack)) Fn->addFnAttr(llvm::Attribute::SafeStack); + if (SanOpts.has(SanitizerKind::ShadowCallStack)) + Fn->addFnAttr(llvm::Attribute::ShadowCallStack); // Ignore TSan memory acesses from within ObjC/ObjC++ dealloc, initialize, // .cxx_destruct, __destroy_helper_block_ and all of their calees at run time. Index: lib/Driver/SanitizerArgs.cpp =================================================================== --- lib/Driver/SanitizerArgs.cpp +++ lib/Driver/SanitizerArgs.cpp @@ -343,7 +343,10 @@ std::make_pair(Scudo, Address | HWAddress | Leak | Thread | Memory | KernelAddress | Efficiency), std::make_pair(SafeStack, Address | HWAddress | Leak | Thread | Memory | - KernelAddress | Efficiency)}; + KernelAddress | Efficiency), + std::make_pair(ShadowCallStack, Address | HWAddress | Leak | Thread | + Memory | KernelAddress | Efficiency | + SafeStack)}; // Enable toolchain specific default sanitizers if not explicitly disabled. SanitizerMask Default = TC.getDefaultSanitizers() & ~AllRemove; Index: lib/Driver/ToolChain.cpp =================================================================== --- lib/Driver/ToolChain.cpp +++ lib/Driver/ToolChain.cpp @@ -814,6 +814,8 @@ getTriple().getArch() == llvm::Triple::wasm32 || getTriple().getArch() == llvm::Triple::wasm64) Res |= CFIICall; + if (getTriple().getArch() == llvm::Triple::x86_64) + Res |= ShadowCallStack; return Res; } Index: lib/Lex/PPMacroExpansion.cpp =================================================================== --- lib/Lex/PPMacroExpansion.cpp +++ lib/Lex/PPMacroExpansion.cpp @@ -1275,6 +1275,8 @@ .Case("is_union", LangOpts.CPlusPlus) .Case("modules", LangOpts.Modules) .Case("safe_stack", LangOpts.Sanitize.has(SanitizerKind::SafeStack)) + .Case("shadow_call_stack", + LangOpts.Sanitize.has(SanitizerKind::ShadowCallStack)) .Case("tls", PP.getTargetInfo().isTLSSupported()) .Case("underlying_type", LangOpts.CPlusPlus) .Default(false); Index: test/CodeGen/shadowcallstack-attr.c =================================================================== --- /dev/null +++ test/CodeGen/shadowcallstack-attr.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck %s + +__attribute__((no_sanitize("shadow-call-stack"))) +int foo(int *a) { return *a; } + + int bar(int *a) { return *a; } + +// CHECK: define i32 @foo(i32* %a) #[[FOO_ATTR:[0-9]+]] { +// CHECK: define i32 @bar(i32* %a) #[[BAR_ATTR:[0-9]+]] { + +// CHECK-NOT: attributes #[[FOO_ATTR]] = { {{.*}}shadowcallstack{{.*}} } +// CHECK: attributes #[[BAR_ATTR]] = { {{.*}}shadowcallstack{{.*}} } Index: test/Driver/sanitizer-ld.c =================================================================== --- test/Driver/sanitizer-ld.c +++ test/Driver/sanitizer-ld.c @@ -557,6 +557,21 @@ // CHECK-SAFESTACK-LINUX: "-lpthread" // CHECK-SAFESTACK-LINUX: "-ldl" +// RUN: %clang -fsanitize=shadow-call-stack %s -### -o %t.o 2>&1 \ +// RUN: -target x86_64-unknown-linux -fuse-ld=ld \ +// RUN: | FileCheck --check-prefix=CHECK-SHADOWCALLSTACK-LINUX-X86-64 %s +// CHECK-SHADOWCALLSTACK-LINUX-X86-64-NOT: error: + +// RUN: %clang -fsanitize=shadow-call-stack %s -### -o %t.o 2>&1 \ +// RUN: -target x86-unknown-linux -fuse-ld=ld \ +// RUN: | FileCheck --check-prefix=CHECK-SHADOWCALLSTACK-LINUX-X86 %s +// CHECK-SHADOWCALLSTACK-LINUX-X86: error: unsupported option '-fsanitize=shadow-call-stack' for target 'x86-unknown-linux' + +// RUN: %clang -fsanitize=shadow-call-stack %s -### -o %t.o 2>&1 \ +// RUN: -fsanitize=safe-stack -target x86_64-unknown-linux -fuse-ld=ld \ +// RUN: | FileCheck --check-prefix=CHECK-SHADOWCALLSTACK-SAFESTACK %s +// CHECK-SHADOWCALLSTACK-SAFESTACK: error: invalid argument '-fsanitize=shadow-call-stack' not allowed with '-fsanitize=safe-stack' + // RUN: %clang -fsanitize=cfi -fsanitize-stats %s -### -o %t.o 2>&1 \ // RUN: -target x86_64-unknown-linux -fuse-ld=ld \ // RUN: --sysroot=%S/Inputs/basic_linux_tree \