Skip to content

Commit

Permalink
Add the -fsanitize=shadow-call-stack flag
Browse files Browse the repository at this point in the history
Summary:
Add support for the -fsanitize=shadow-call-stack flag which causes clang
to add ShadowCallStack attribute to functions compiled with that flag
enabled.

Reviewers: pcc, kcc

Reviewed By: pcc, kcc

Subscribers: cryptoad, cfe-commits, kcc

Differential Revision: https://reviews.llvm.org/D44801

llvm-svn: 329122
  • Loading branch information
vlad902 committed Apr 3, 2018
1 parent 81b3b10 commit e55aa03
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 1 deletion.
150 changes: 150 additions & 0 deletions clang/docs/ShadowCallStack.rst
@@ -0,0 +1,150 @@
===============
ShadowCallStack
===============

.. contents::
:local:

Introduction
============

ShadowCallStack is an **experimental** instrumentation pass, currently only
implemented for x86_64, that protects programs against return address
overwrites (e.g. 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.

Comparison
----------

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. Similarly, `Return Flow Guard`_ consumes more memory with
shorter function prologs and epilogs than ShadowCallStack but suffers from the
same race conditions (see `Security`_). Intel `Control-flow Enforcement Technology`_
(CET) is a proposed hardware extension that would add native support to
use a shadow stack to store/check return addresses at call/return time. It
would not suffer from race conditions at calls and returns and not incur the
overhead of function instrumentation, but it does require operating system
support.

.. _`Return Flow Guard`: https://xlab.tencent.com/en/2016/11/02/return-flow-guard/
.. _`Control-flow Enforcement Technology`: https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf

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 epilog 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
due to return branch prediction.

The instrumentation makes use of the ``gs`` segment register to reference the
shadow call stack meaning that references to the shadow call stack do not have
to be stored in memory. This makes it possible to implement a runtime that
avoids exposing the address of the shadow call stack to attackers that can read
arbitrary memory. However, attackers could still try to exploit side channels
exposed by the operating system `[1]`_ `[2]`_ or processor `[3]`_ to discover
the address of the shadow call stack.

.. _`[1]`: https://eyalitkin.wordpress.com/2017/09/01/cartography-lighting-up-the-shadows/
.. _`[2]`: https://www.blackhat.com/docs/eu-16/materials/eu-16-Goktas-Bypassing-Clangs-SafeStack.pdf
.. _`[3]`: https://www.vusec.net/projects/anc/

Leaf functions are optimized to store the return address in a free register
and avoid writing to the shadow call stack if a register is available. Very
short leaf functions are uninstrumented if their execution is judged to be
shorter than the race condition window intrinsic to the instrumentation.

Usage
=====

To enable ShadowCallStack, just pass the ``-fsanitize=shadow-call-stack`` flag
to both compile and link command lines.

Low-level API
-------------

``__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.

Example
=======

The following example code:

.. code-block:: c++

int foo() {
return bar() + 1;
}

Generates the following x86_64 assembly when compiled with ``-O2``:

.. code-block:: gas
push %rax
callq foo
add $0x1,%eax
pop %rcx
retq
Adding ``-fsanitize=shadow-call-stack`` would output the following:

.. code-block:: gas
mov (%rsp),%r10
xor %r11,%r11
addq $0x8,%gs:(%r11)
mov %gs:(%r11),%r11
mov %r10,%gs:(%r11)
push %rax
callq foo
add $0x1,%eax
pop %rcx
xor %r11,%r11
mov %gs:(%r11),%r10
mov %gs:(%r10),%r10
subq $0x8,%gs:(%r11)
cmp %r10,(%rsp)
jne trap
retq
trap:
ud2
1 change: 1 addition & 0 deletions clang/docs/index.rst
Expand Up @@ -36,6 +36,7 @@ Using Clang as a Compiler
ControlFlowIntegrity
LTOVisibility
SafeStack
ShadowCallStack
SourceBasedCodeCoverage
Modules
MSVCCompatibility
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/Sanitizers.def
Expand Up @@ -110,6 +110,9 @@ SANITIZER_GROUP("cfi", CFI,
// 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,
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CGDeclCXX.cpp
Expand Up @@ -343,6 +343,10 @@ llvm::Function *CodeGenModule::CreateGlobalInitOrDestructFunction(
!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;
}

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Expand Up @@ -861,6 +861,8 @@ void CodeGenFunction::StartFunction(GlobalDecl GD,
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);

// Apply fuzzing attribute to the function.
if (SanOpts.hasOneOf(SanitizerKind::Fuzzer | SanitizerKind::FuzzerNoLink))
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/Driver/SanitizerArgs.cpp
Expand Up @@ -343,7 +343,10 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
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;
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Driver/ToolChain.cpp
Expand Up @@ -814,6 +814,8 @@ SanitizerMask ToolChain::getSupportedSanitizers() const {
getTriple().getArch() == llvm::Triple::wasm32 ||
getTriple().getArch() == llvm::Triple::wasm64)
Res |= CFIICall;
if (getTriple().getArch() == llvm::Triple::x86_64)
Res |= ShadowCallStack;
return Res;
}

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Lex/PPMacroExpansion.cpp
Expand Up @@ -1275,6 +1275,8 @@ static bool HasFeature(const Preprocessor &PP, StringRef Feature) {
.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);
Expand Down
16 changes: 16 additions & 0 deletions clang/test/CodeGen/shadowcallstack-attr.c
@@ -0,0 +1,16 @@
// RUN: %clang_cc1 -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=UNBLACKLISTED %s

// RUN: %clang_cc1 -D ATTR -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s

// RUN: echo -e "[shadow-call-stack]\nfun:foo" > %t
// RUN: %clang_cc1 -fsanitize-blacklist=%t -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s

#ifdef ATTR
__attribute__((no_sanitize("shadow-call-stack")))
#endif
int foo(int *a) { return *a; }

// CHECK: define i32 @foo(i32* %a)

// BLACKLISTED-NOT: attributes {{.*}}shadowcallstack{{.*}}
// UNBLACKLISTED: attributes {{.*}}shadowcallstack{{.*}}
15 changes: 15 additions & 0 deletions clang/test/Driver/sanitizer-ld.c
Expand Up @@ -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 \
Expand Down

0 comments on commit e55aa03

Please sign in to comment.