Skip to content

Commit

Permalink
Implement Control Flow Integrity for virtual calls.
Browse files Browse the repository at this point in the history
This patch introduces the -fsanitize=cfi-vptr flag, which enables a control
flow integrity scheme that checks that virtual calls take place using a vptr of
the correct dynamic type. More details in the new docs/ControlFlowIntegrity.rst
file.

It also introduces the -fsanitize=cfi flag, which is currently a synonym for
-fsanitize=cfi-vptr, but will eventually cover all CFI checks implemented
in Clang.

Differential Revision: http://reviews.llvm.org/D7424

llvm-svn: 230055
  • Loading branch information
pcc committed Feb 20, 2015
1 parent e6909c8 commit a4ccff3
Show file tree
Hide file tree
Showing 20 changed files with 377 additions and 11 deletions.
74 changes: 74 additions & 0 deletions clang/docs/ControlFlowIntegrity.rst
@@ -0,0 +1,74 @@
======================
Control Flow Integrity
======================

.. toctree::
:hidden:

ControlFlowIntegrityDesign

.. contents::
:local:

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

Clang includes an implementation of a number of control flow integrity (CFI)
schemes, which are designed to abort the program upon detecting certain forms
of undefined behavior that can potentially allow attackers to subvert the
program's control flow. These schemes have been optimized for performance,
allowing developers to enable them in release builds.

To enable Clang's available CFI schemes, use the flag ``-fsanitize=cfi``.
As currently implemented, CFI relies on link-time optimization (LTO); the CFI
schemes imply ``-flto``, and the linker used must support LTO, for example
via the `gold plugin`_. To allow the checks to be implemented efficiently,
the program must be structured such that certain object files are compiled
with CFI enabled, and are statically linked into the program. This may
preclude the use of shared libraries in some cases.

Clang currently implements forward-edge CFI for virtual calls. More schemes
are under development.

.. _gold plugin: http://llvm.org/docs/GoldPlugin.html

Forward-Edge CFI for Virtual Calls
----------------------------------

This scheme checks that virtual calls take place using a vptr of the correct
dynamic type; that is, the dynamic type of the called object must be a
derived class of the static type of the object used to make the call.
This CFI scheme can be enabled on its own using ``-fsanitize=cfi-vptr``.

For this scheme to work, all translation units containing the definition
of a virtual member function (whether inline or not) must be compiled
with ``-fsanitize=cfi-vptr`` enabled and be statically linked into the
program. Classes in the C++ standard library (under namespace ``std``) are
exempted from checking, and therefore programs may be linked against a
pre-built standard library, but this may change in the future.

Performance
~~~~~~~~~~~

A performance overhead of less than 1% has been measured by running the
Dromaeo benchmark suite against an instrumented version of the Chromium
web browser. Another good performance benchmark for this mechanism is the
virtual-call-heavy SPEC 2006 xalancbmk.

Note that this scheme has not yet been optimized for binary size; an increase
of up to 15% has been observed for Chromium.

Design
------

Please refer to the :doc:`design document<ControlFlowIntegrityDesign>`.

Publications
------------

`Control-Flow Integrity: Principles, Implementations, and Applications <http://research.microsoft.com/pubs/64250/ccs05.pdf>`_.
Martin Abadi, Mihai Budiu, Úlfar Erlingsson, Jay Ligatti.

`Enforcing Forward-Edge Control-Flow Integrity in GCC & LLVM <http://www.pcc.me.uk/~peter/acad/usenix14.pdf>`_.
Caroline Tice, Tom Roeder, Peter Collingbourne, Stephen Checkoway,
Úlfar Erlingsson, Luis Lozano, Geoff Pike.
59 changes: 59 additions & 0 deletions clang/docs/ControlFlowIntegrityDesign.rst
@@ -0,0 +1,59 @@
===========================================
Control Flow Integrity Design Documentation
===========================================

This page documents the design of the :doc:`ControlFlowIntegrity` schemes
supported by Clang.

Forward-Edge CFI for Virtual Calls
----------------------------------

This scheme works by allocating, for each static type used to make a virtual
call, a region of read-only storage in the object file holding a bit vector
that maps onto to the region of storage used for those virtual tables. Each
set bit in the bit vector corresponds to the `address point`_ for a virtual
table compatible with the static type for which the bit vector is being built.

For example, consider the following three C++ classes:

.. code-block:: c++

struct A {
virtual void f();
};

struct B : A {
virtual void f();
};

struct C : A {
virtual void f();
};

The scheme will cause the virtual tables for A, B and C to be laid out
consecutively:

.. csv-table:: Virtual Table Layout for A, B, C
:header: 0, 1, 2, 3, 4, 5, 6, 7, 8

A::offset-to-top, &A::rtti, &A::f, B::offset-to-top, &B::rtti, &B::f, C::offset-to-top, &C::rtti, &C::f

The bit vector for static types A, B and C will look like this:

.. csv-table:: Bit Vectors for A, B, C
:header: Class, 0, 1, 2, 3, 4, 5, 6, 7, 8

A, 0, 0, 1, 0, 0, 1, 0, 0, 1
B, 0, 0, 0, 0, 0, 1, 0, 0, 0
C, 0, 0, 0, 0, 0, 0, 0, 0, 1

To emit a virtual call, the compiler will assemble code that checks that
the object's virtual table pointer is in-bounds and aligned and that the
relevant bit is set in the bit vector.

The compiler relies on co-operation from the linker in order to assemble
the bit vector for the whole program. It currently does this using LLVM's
`bit sets`_ mechanism together with link-time optimization.

.. _address point: https://mentorembedded.github.io/cxx-abi/abi.html#vtable-general
.. _bit sets: http://llvm.org/docs/BitSets.html
4 changes: 4 additions & 0 deletions clang/docs/UsersManual.rst
Expand Up @@ -957,6 +957,8 @@ are listed below.
``unsigned-integer-overflow`` and ``vptr``.
- ``-fsanitize=dataflow``: :doc:`DataFlowSanitizer`, a general data
flow analysis.
- ``-fsanitize=cfi``: :doc:`control flow integrity <ControlFlowIntegrity>`
checks. Implies ``-flto``.

The following more fine-grained checks are also available:

Expand All @@ -966,6 +968,8 @@ are listed below.
``true`` nor ``false``.
- ``-fsanitize=bounds``: Out of bounds array indexing, in cases
where the array bound can be statically determined.
- ``-fsanitize=cfi-vptr``: Use of an object whose vptr is of the
wrong dynamic type. Implies ``-flto``.
- ``-fsanitize=enum``: Load of a value of an enumerated type which
is not in the range of representable values for that enumerated
type.
Expand Down
1 change: 1 addition & 0 deletions clang/docs/index.rst
Expand Up @@ -27,6 +27,7 @@ Using Clang as a Compiler
DataFlowSanitizer
LeakSanitizer
SanitizerSpecialCaseList
ControlFlowIntegrity
Modules
MSVCCompatibility
FAQ
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/AST/Mangle.h
Expand Up @@ -141,6 +141,9 @@ class MangleContext {
/// across translation units so it can be used with LTO.
virtual void mangleTypeName(QualType T, raw_ostream &) = 0;

virtual void mangleCXXVTableBitSet(const CXXRecordDecl *RD,
raw_ostream &) = 0;

/// @}
};

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/Sanitizers.def
Expand Up @@ -76,6 +76,10 @@ SANITIZER("unsigned-integer-overflow", UnsignedIntegerOverflow)
// DataFlowSanitizer
SANITIZER("dataflow", DataFlow)

// Control Flow Integrity
SANITIZER("cfi-vptr", CFIVptr)
SANITIZER_GROUP("cfi", CFI, CFIVptr)

// -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
6 changes: 3 additions & 3 deletions clang/include/clang/Driver/Driver.h
Expand Up @@ -357,8 +357,8 @@ class Driver {
/// \p Phase on the \p Input, taking in to account arguments
/// like -fsyntax-only or --analyze.
std::unique_ptr<Action>
ConstructPhaseAction(const llvm::opt::ArgList &Args, phases::ID Phase,
std::unique_ptr<Action> Input) const;
ConstructPhaseAction(const ToolChain &TC, const llvm::opt::ArgList &Args,
phases::ID Phase, std::unique_ptr<Action> Input) const;

/// BuildJobsForAction - Construct the jobs to perform for the
/// action \p A.
Expand Down Expand Up @@ -402,7 +402,7 @@ class Driver {
/// handle this action.
bool ShouldUseClangCompiler(const JobAction &JA) const;

bool IsUsingLTO(const llvm::opt::ArgList &Args) const;
bool IsUsingLTO(const ToolChain &TC, const llvm::opt::ArgList &Args) const;

private:
/// \brief Retrieves a ToolChain for a particular target triple.
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Driver/SanitizerArgs.h
Expand Up @@ -51,6 +51,7 @@ class SanitizerArgs {

bool requiresPIE() const;
bool needsUnwindTables() const;
bool needsLTO() const;
bool linkCXXRuntimes() const { return LinkCXXRuntimes; }
void addArgs(const llvm::opt::ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) const;
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/AST/ItaniumMangle.cpp
Expand Up @@ -172,6 +172,8 @@ class ItaniumMangleContextImpl : public ItaniumMangleContext {

void mangleStringLiteral(const StringLiteral *, raw_ostream &) override;

void mangleCXXVTableBitSet(const CXXRecordDecl *RD, raw_ostream &) override;

bool getNextDiscriminator(const NamedDecl *ND, unsigned &disc) {
// Lambda closure types are already numbered.
if (isLambda(ND))
Expand Down Expand Up @@ -4040,6 +4042,22 @@ void ItaniumMangleContextImpl::mangleTypeName(QualType Ty, raw_ostream &Out) {
mangleCXXRTTIName(Ty, Out);
}

void ItaniumMangleContextImpl::mangleCXXVTableBitSet(const CXXRecordDecl *RD,
raw_ostream &Out) {
Linkage L = RD->getLinkageInternal();
if (L == InternalLinkage || L == UniqueExternalLinkage) {
// This part of the identifier needs to be unique across all translation
// units in the linked program. The scheme fails if multiple translation
// units are compiled using the same relative source file path, or if
// multiple translation units are built from the same source file.
SourceManager &SM = getASTContext().getSourceManager();
Out << "[" << SM.getFileEntryForID(SM.getMainFileID())->getName() << "]";
}

CXXNameMangler Mangler(*this, Out);
Mangler.mangleType(QualType(RD->getTypeForDecl(), 0));
}

void ItaniumMangleContextImpl::mangleStringLiteral(const StringLiteral *, raw_ostream &) {
llvm_unreachable("Can't mangle string literals");
}
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/AST/MicrosoftMangle.cpp
Expand Up @@ -138,6 +138,8 @@ class MicrosoftMangleContextImpl : public MicrosoftMangleContext {
void mangleSEHFilterExpression(const NamedDecl *EnclosingDecl,
raw_ostream &Out) override;
void mangleStringLiteral(const StringLiteral *SL, raw_ostream &Out) override;
void mangleCXXVTableBitSet(const CXXRecordDecl *RD,
raw_ostream &Out) override;
bool getNextDiscriminator(const NamedDecl *ND, unsigned &disc) {
// Lambda closure types are already numbered.
if (isLambda(ND))
Expand Down Expand Up @@ -2567,6 +2569,11 @@ void MicrosoftMangleContextImpl::mangleStringLiteral(const StringLiteral *SL,
Mangler.getStream() << '@';
}

void MicrosoftMangleContextImpl::mangleCXXVTableBitSet(const CXXRecordDecl *RD,
raw_ostream &Out) {
llvm::report_fatal_error("Cannot mangle bitsets yet");
}

MicrosoftMangleContext *
MicrosoftMangleContext::create(ASTContext &Context, DiagnosticsEngine &Diags) {
return new MicrosoftMangleContextImpl(Context, Diags);
Expand Down
33 changes: 33 additions & 0 deletions clang/lib/CodeGen/CGClass.cpp
Expand Up @@ -24,6 +24,7 @@
#include "clang/Basic/TargetBuiltins.h"
#include "clang/CodeGen/CGFunctionInfo.h"
#include "clang/Frontend/CodeGenOptions.h"
#include "llvm/IR/Intrinsics.h"

using namespace clang;
using namespace CodeGen;
Expand Down Expand Up @@ -2087,6 +2088,38 @@ llvm::Value *CodeGenFunction::GetVTablePtr(llvm::Value *This,
return VTable;
}

void CodeGenFunction::EmitVTablePtrCheckForCall(const CXXMethodDecl *MD,
llvm::Value *VTable) {
if (!SanOpts.has(SanitizerKind::CFIVptr))
return;

const CXXRecordDecl *RD = MD->getParent();
// FIXME: Add blacklisting scheme.
if (RD->isInStdNamespace())
return;

std::string OutName;
llvm::raw_string_ostream Out(OutName);
CGM.getCXXABI().getMangleContext().mangleCXXVTableBitSet(RD, Out);

llvm::Value *BitSetName = llvm::MetadataAsValue::get(
getLLVMContext(), llvm::MDString::get(getLLVMContext(), Out.str()));

llvm::Value *BitSetTest = Builder.CreateCall2(
CGM.getIntrinsic(llvm::Intrinsic::bitset_test),
Builder.CreateBitCast(VTable, CGM.Int8PtrTy), BitSetName);

llvm::BasicBlock *ContBlock = createBasicBlock("vtable.check.cont");
llvm::BasicBlock *TrapBlock = createBasicBlock("vtable.check.trap");

Builder.CreateCondBr(BitSetTest, ContBlock, TrapBlock);

EmitBlock(TrapBlock);
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::trap));
Builder.CreateUnreachable();

EmitBlock(ContBlock);
}

// FIXME: Ideally Expr::IgnoreParenNoopCasts should do this, but it doesn't do
// quite what we want.
Expand Down
61 changes: 61 additions & 0 deletions clang/lib/CodeGen/CGVTables.cpp
Expand Up @@ -669,6 +669,8 @@ CodeGenVTables::GenerateConstructionVTable(const CXXRecordDecl *RD,
VTLayout->getNumVTableThunks(), RTTI);
VTable->setInitializer(Init);

CGM.EmitVTableBitSetEntries(VTable, *VTLayout.get());

return VTable;
}

Expand Down Expand Up @@ -837,3 +839,62 @@ void CodeGenModule::EmitDeferredVTables() {
"deferred extra v-tables during v-table emission?");
DeferredVTables.clear();
}

void CodeGenModule::EmitVTableBitSetEntries(llvm::GlobalVariable *VTable,
const VTableLayout &VTLayout) {
if (!LangOpts.Sanitize.has(SanitizerKind::CFIVptr))
return;

llvm::Metadata *VTableMD = llvm::ConstantAsMetadata::get(VTable);

std::vector<llvm::MDTuple *> BitsetEntries;
// Create a bit set entry for each address point.
for (auto &&AP : VTLayout.getAddressPoints()) {
// FIXME: Add blacklisting scheme.
if (AP.first.getBase()->isInStdNamespace())
continue;

std::string OutName;
llvm::raw_string_ostream Out(OutName);
getCXXABI().getMangleContext().mangleCXXVTableBitSet(AP.first.getBase(),
Out);

CharUnits PointerWidth =
Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0));
uint64_t AddrPointOffset = AP.second * PointerWidth.getQuantity();

llvm::Metadata *BitsetOps[] = {
llvm::MDString::get(getLLVMContext(), Out.str()),
VTableMD,
llvm::ConstantAsMetadata::get(
llvm::ConstantInt::get(Int64Ty, AddrPointOffset))};
llvm::MDTuple *BitsetEntry =
llvm::MDTuple::get(getLLVMContext(), BitsetOps);
BitsetEntries.push_back(BitsetEntry);
}

// Sort the bit set entries for determinism.
std::sort(BitsetEntries.begin(), BitsetEntries.end(), [](llvm::MDTuple *T1,
llvm::MDTuple *T2) {
StringRef S1 = cast<llvm::MDString>(T1->getOperand(0))->getString();
StringRef S2 = cast<llvm::MDString>(T2->getOperand(0))->getString();
if (S1 < S2)
return true;
if (S1 != S2)
return false;

uint64_t Offset1 = cast<llvm::ConstantInt>(
cast<llvm::ConstantAsMetadata>(T1->getOperand(2))
->getValue())->getZExtValue();
uint64_t Offset2 = cast<llvm::ConstantInt>(
cast<llvm::ConstantAsMetadata>(T2->getOperand(2))
->getValue())->getZExtValue();
assert(Offset1 != Offset2);
return Offset1 < Offset2;
});

llvm::NamedMDNode *BitsetsMD =
getModule().getOrInsertNamedMetadata("llvm.bitsets");
for (auto BitsetEntry : BitsetEntries)
BitsetsMD->addOperand(BitsetEntry);
}
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Expand Up @@ -1346,6 +1346,9 @@ class CodeGenFunction : public CodeGenTypeCache {
/// to by This.
llvm::Value *GetVTablePtr(llvm::Value *This, llvm::Type *Ty);

/// EmitVTablePtrCheckForCall - Virtual method MD is being called via VTable.
/// If vptr CFI is enabled, emit a check that VTable is valid.
void EmitVTablePtrCheckForCall(const CXXMethodDecl *MD, llvm::Value *VTable);

/// CanDevirtualizeMemberFunctionCalls - Checks whether virtual calls on given
/// expr can be devirtualized.
Expand Down

0 comments on commit a4ccff3

Please sign in to comment.