diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -2510,6 +2510,15 @@ 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 `_ document. + .. _moduleasm: Module-Level Inline Assembly diff --git a/llvm/docs/PointerAuth.md b/llvm/docs/PointerAuth.md --- a/llvm/docs/PointerAuth.md +++ b/llvm/docs/PointerAuth.md @@ -10,8 +10,10 @@ 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 @@ 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 , i64 ) ] + 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 , i64 ) + %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 diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h --- a/llvm/include/llvm/IR/InstrTypes.h +++ b/llvm/include/llvm/IR/InstrTypes.h @@ -2068,7 +2068,8 @@ 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. diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h --- a/llvm/include/llvm/IR/LLVMContext.h +++ b/llvm/include/llvm/IR/LLVMContext.h @@ -93,6 +93,7 @@ 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. diff --git a/llvm/lib/IR/Instructions.cpp b/llvm/lib/IR/Instructions.cpp --- a/llvm/lib/IR/Instructions.cpp +++ b/llvm/lib/IR/Instructions.cpp @@ -482,9 +482,10 @@ 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; } //===----------------------------------------------------------------------===// diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp --- a/llvm/lib/IR/LLVMContext.cpp +++ b/llvm/lib/IR/LLVMContext.cpp @@ -82,6 +82,11 @@ "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 && diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -3285,11 +3285,12 @@ 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 @@ 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(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 @@ } } + // 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. diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp --- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp +++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp @@ -248,10 +248,10 @@ isa(&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 diff --git a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll --- a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll +++ b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll @@ -10,6 +10,7 @@ ; CHECK-NEXT: &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 +}