[IR] Define "ptrauth" operand bundle.

This introduces a new "ptrauth" operand bundle to be used in
call/invoke. At the IR level, it's semantically equivalent to an
@llvm.ptrauth.auth followed by an indirect call, but it additionally
provides additional hardening, by preventing the intermediate raw
pointer from being exposed.

This mostly adds the IR definition, verifier checks, and support in
a couple of general helper functions. Clang IRGen and backend support
will come separately.

Note that we'll eventually want to support this bundle in indirectbr as
well, for similar reasons.  indirectbr currently doesn't support bundles
at all, and the IR data structures need to be updated to allow that.

Differential Revision: https://reviews.llvm.org/D113685
This commit is contained in:
Ahmed Bougacha 2022-02-08 19:04:47 -08:00
parent aa15274389
commit c703f852c9
11 changed files with 126 additions and 10 deletions

View File

@ -2510,6 +2510,15 @@ void, in which case the operand bundle is ignored.
The operand bundle is needed to ensure the call is immediately followed by the
marker instruction and the ObjC runtime call in the final output.
.. _ob_ptrauth:
Pointer Authentication Operand Bundles
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pointer Authentication operand bundles are characterized by the
``"ptrauth"`` operand bundle tag. They are described in the
`Pointer Authentication <PointerAuth.html#operand-bundle>`_ document.
.. _moduleasm:
Module-Level Inline Assembly

View File

@ -10,8 +10,10 @@ Before the pointer is used, it needs to be authenticated, i.e., have its
signature checked. This prevents pointer values of unknown origin from being
used to replace the signed pointer value.
At the IR level, it is represented using a [set of intrinsics](#intrinsics)
(to sign/authenticate pointers).
At the IR level, it is represented using:
* a [set of intrinsics](#intrinsics) (to sign/authenticate pointers)
* a [call operand bundle](#operand-bundle) (to authenticate called pointers)
The current implementation leverages the
[Armv8.3-A PAuth/Pointer Authentication Code](#armv8-3-a-pauth-pointer-authentication-code)
@ -220,6 +222,46 @@ with a pointer address discriminator, in a way that is specified by the target
implementation.
### Operand Bundle
Function pointers used as indirect call targets can be signed when materialized,
and authenticated before calls. This can be accomplished with the
[``llvm.ptrauth.auth``](#llvm-ptrauth-auth) intrinsic, feeding its result to
an indirect call.
However, that exposes the intermediate, unauthenticated pointer, e.g., if it
gets spilled to the stack. An attacker can then overwrite the pointer in
memory, negating the security benefit provided by pointer authentication.
To prevent that, the ``ptrauth`` operand bundle may be used: it guarantees that
the intermediate call target is kept in a register and never stored to memory.
This hardening benefit is similar to that provided by
[``llvm.ptrauth.resign``](#llvm-ptrauth-resign)).
Concretely:
```llvm
define void @f(void ()* %fp) {
call void %fp() [ "ptrauth"(i32 <key>, i64 <data>) ]
ret void
}
```
is functionally equivalent to:
```llvm
define void @f(void ()* %fp) {
%fp_i = ptrtoint void ()* %fp to i64
%fp_auth = call i64 @llvm.ptrauth.auth(i64 %fp_i, i32 <key>, i64 <data>)
%fp_auth_p = inttoptr i64 %fp_auth to void ()*
call void %fp_auth_p()
ret void
}
```
but with the added guarantee that ``%fp_i``, ``%fp_auth``, and ``%fp_auth_p``
are not stored to (and reloaded from) memory.
## AArch64 Support
AArch64 is currently the only architecture with full support of the pointer

View File

@ -2068,7 +2068,8 @@ public:
bool hasClobberingOperandBundles() const {
for (auto &BOI : bundle_op_infos()) {
if (BOI.Tag->second == LLVMContext::OB_deopt ||
BOI.Tag->second == LLVMContext::OB_funclet)
BOI.Tag->second == LLVMContext::OB_funclet ||
BOI.Tag->second == LLVMContext::OB_ptrauth)
continue;
// This instruction has an operand bundle that is not known to us.

View File

@ -93,6 +93,7 @@ public:
OB_preallocated = 4, // "preallocated"
OB_gc_live = 5, // "gc-live"
OB_clang_arc_attachedcall = 6, // "clang.arc.attachedcall"
OB_ptrauth = 7, // "ptrauth"
};
/// getMDKindID - Return a unique non-zero ID for the specified metadata kind.

View File

@ -482,9 +482,10 @@ CallBase *CallBase::removeOperandBundle(CallBase *CB, uint32_t ID,
bool CallBase::hasReadingOperandBundles() const {
// Implementation note: this is a conservative implementation of operand
// bundle semantics, where *any* non-assume operand bundle forces a callsite
// to be at least readonly.
return hasOperandBundles() && getIntrinsicID() != Intrinsic::assume;
// bundle semantics, where *any* non-assume operand bundle (other than
// ptrauth) forces a callsite to be at least readonly.
return hasOperandBundlesOtherThan(LLVMContext::OB_ptrauth) &&
getIntrinsicID() != Intrinsic::assume;
}
//===----------------------------------------------------------------------===//

View File

@ -82,6 +82,11 @@ LLVMContext::LLVMContext() : pImpl(new LLVMContextImpl(*this)) {
"clang.arc.attachedcall operand bundle id drifted!");
(void)ClangAttachedCall;
auto *PtrauthEntry = pImpl->getOrInsertBundleTag("ptrauth");
assert(PtrauthEntry->second == LLVMContext::OB_ptrauth &&
"ptrauth operand bundle id drifted!");
(void)PtrauthEntry;
SyncScope::ID SingleThreadSSID =
pImpl->getOrInsertSyncScopeID("singlethread");
assert(SingleThreadSSID == SyncScope::SingleThread &&

View File

@ -3285,11 +3285,12 @@ void Verifier::visitCallBase(CallBase &Call) {
visitIntrinsicCall(ID, Call);
// Verify that a callsite has at most one "deopt", at most one "funclet", at
// most one "gc-transition", at most one "cfguardtarget",
// and at most one "preallocated" operand bundle.
// most one "gc-transition", at most one "cfguardtarget", at most one
// "preallocated" operand bundle, and at most one "ptrauth" operand bundle.
bool FoundDeoptBundle = false, FoundFuncletBundle = false,
FoundGCTransitionBundle = false, FoundCFGuardTargetBundle = false,
FoundPreallocatedBundle = false, FoundGCLiveBundle = false,
FoundPtrauthBundle = false,
FoundAttachedCallBundle = false;
for (unsigned i = 0, e = Call.getNumOperandBundles(); i < e; ++i) {
OperandBundleUse BU = Call.getOperandBundleAt(i);
@ -3315,6 +3316,16 @@ void Verifier::visitCallBase(CallBase &Call) {
FoundCFGuardTargetBundle = true;
Assert(BU.Inputs.size() == 1,
"Expected exactly one cfguardtarget bundle operand", Call);
} else if (Tag == LLVMContext::OB_ptrauth) {
Assert(!FoundPtrauthBundle, "Multiple ptrauth operand bundles", Call);
FoundPtrauthBundle = true;
Assert(BU.Inputs.size() == 2,
"Expected exactly two ptrauth bundle operands", Call);
Assert(isa<ConstantInt>(BU.Inputs[0]) &&
BU.Inputs[0]->getType()->isIntegerTy(32),
"Ptrauth bundle key operand must be an i32 constant", Call);
Assert(BU.Inputs[1]->getType()->isIntegerTy(64),
"Ptrauth bundle discriminator operand must be an i64", Call);
} else if (Tag == LLVMContext::OB_preallocated) {
Assert(!FoundPreallocatedBundle, "Multiple preallocated operand bundles",
Call);
@ -3339,6 +3350,10 @@ void Verifier::visitCallBase(CallBase &Call) {
}
}
// Verify that callee and callsite agree on whether to use pointer auth.
Assert(!(Call.getCalledFunction() && FoundPtrauthBundle),
"Direct call cannot have a ptrauth bundle", Call);
// Verify that each inlinable callsite of a debug-info-bearing function in a
// debug-info-bearing function has a debug location attached to it. Failure to
// do so causes assertion failures when the inliner sets up inline scope info.

View File

@ -248,10 +248,10 @@ static bool markTails(Function &F, OptimizationRemarkEmitter *ORE) {
isa<PseudoProbeInst>(&I))
continue;
// Special-case operand bundle "clang.arc.attachedcall".
// Special-case operand bundles "clang.arc.attachedcall" and "ptrauth".
bool IsNoTail =
CI->isNoTailCall() || CI->hasOperandBundlesOtherThan(
LLVMContext::OB_clang_arc_attachedcall);
{LLVMContext::OB_clang_arc_attachedcall, LLVMContext::OB_ptrauth});
if (!IsNoTail && CI->doesNotAccessMemory()) {
// A call to a readnone function whose arguments are all things computed

View File

@ -10,6 +10,7 @@
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
; CHECK-NEXT: </OPERAND_BUNDLE_TAGS_BLOCK
; CHECK: <FUNCTION_BLOCK

View File

@ -0,0 +1,10 @@
; RUN: opt < %s -tailcallelim -verify-dom-info -S | FileCheck %s
; Check that the "ptrauth" operand bundle doesn't prevent tail calls.
define i64 @f_1(i64 %x, i64(i64)* %f_0) {
; CHECK-LABEL: @f_1(
entry:
; CHECK: tail call i64 %f_0(i64 %x) [ "ptrauth"(i32 42, i64 %x) ]
%tmp = call i64 %f_0(i64 %x) [ "ptrauth"(i32 42, i64 %x) ]
ret i64 0
}

View File

@ -0,0 +1,31 @@
; RUN: not opt -verify < %s 2>&1 | FileCheck %s
declare void @g()
define void @test_ptrauth_bundle(i64 %arg0, i32 %arg1, void()* %arg2) {
; CHECK: Multiple ptrauth operand bundles
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i32 42, i64 100), "ptrauth"(i32 42, i64 %arg0) ]
call void %arg2() [ "ptrauth"(i32 42, i64 100), "ptrauth"(i32 42, i64 %arg0) ]
; CHECK: Ptrauth bundle key operand must be an i32 constant
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i32 %arg1, i64 120) ]
call void %arg2() [ "ptrauth"(i32 %arg1, i64 120) ]
; CHECK: Ptrauth bundle key operand must be an i32 constant
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i64 42, i64 120) ]
call void %arg2() [ "ptrauth"(i64 42, i64 120) ]
; CHECK: Ptrauth bundle discriminator operand must be an i64
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i32 42, i32 120) ]
call void %arg2() [ "ptrauth"(i32 42, i32 120) ]
; CHECK: Direct call cannot have a ptrauth bundle
; CHECK-NEXT: call void @g() [ "ptrauth"(i32 42, i64 120) ]
call void @g() [ "ptrauth"(i32 42, i64 120) ]
; CHECK-NOT: call
call void %arg2() [ "ptrauth"(i32 42, i64 120) ] ; OK
call void %arg2() [ "ptrauth"(i32 42, i64 %arg0) ] ; OK
ret void
}