forked from OSchip/llvm-project
[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:
parent
aa15274389
commit
c703f852c9
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue