[llvm] c703f85 - [IR] Define "ptrauth" operand bundle.

Ahmed Bougacha via llvm-commits llvm-commits at lists.llvm.org
Mon Feb 14 11:27:41 PST 2022


Author: Ahmed Bougacha
Date: 2022-02-14T11:27:35-08:00
New Revision: c703f852c9dc7ec92b061889f86b531253f86d70

URL: https://github.com/llvm/llvm-project/commit/c703f852c9dc7ec92b061889f86b531253f86d70
DIFF: https://github.com/llvm/llvm-project/commit/c703f852c9dc7ec92b061889f86b531253f86d70.diff

LOG: [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

Added: 
    llvm/test/Transforms/TailCallElim/ptrauth-bundle.ll
    llvm/test/Verifier/ptrauth-operand-bundles.ll

Modified: 
    llvm/docs/LangRef.rst
    llvm/docs/PointerAuth.md
    llvm/include/llvm/IR/InstrTypes.h
    llvm/include/llvm/IR/LLVMContext.h
    llvm/lib/IR/Instructions.cpp
    llvm/lib/IR/LLVMContext.cpp
    llvm/lib/IR/Verifier.cpp
    llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
    llvm/test/Bitcode/operand-bundles-bc-analyzer.ll

Removed: 
    


################################################################################
diff  --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 1a212c6615976..0b96c0c9a3bf4 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -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

diff  --git a/llvm/docs/PointerAuth.md b/llvm/docs/PointerAuth.md
index d62d051eca3d2..b606e84541481 100644
--- a/llvm/docs/PointerAuth.md
+++ b/llvm/docs/PointerAuth.md
@@ -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

diff  --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h
index 35c1ef5514b29..b50a02c5dde52 100644
--- a/llvm/include/llvm/IR/InstrTypes.h
+++ b/llvm/include/llvm/IR/InstrTypes.h
@@ -2068,7 +2068,8 @@ class CallBase : public Instruction {
   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
index 446bcecf1c642..e804ce90ff9f5 100644
--- a/llvm/include/llvm/IR/LLVMContext.h
+++ b/llvm/include/llvm/IR/LLVMContext.h
@@ -93,6 +93,7 @@ class LLVMContext {
     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
index 7798af3b19b99..1cfab627becae 100644
--- a/llvm/lib/IR/Instructions.cpp
+++ b/llvm/lib/IR/Instructions.cpp
@@ -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;
 }
 
 //===----------------------------------------------------------------------===//

diff  --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp
index e19ead98a6166..4b39599555a65 100644
--- a/llvm/lib/IR/LLVMContext.cpp
+++ b/llvm/lib/IR/LLVMContext.cpp
@@ -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 &&

diff  --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index e613589068371..4eae6a5fe4a2a 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -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.

diff  --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 3bcf92e28a21c..e40d9f4c48cf4 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -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

diff  --git a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll
index cb7a3ee7eb714..1504f5b525dba 100644
--- a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll
+++ b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll
@@ -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

diff  --git a/llvm/test/Transforms/TailCallElim/ptrauth-bundle.ll b/llvm/test/Transforms/TailCallElim/ptrauth-bundle.ll
new file mode 100644
index 0000000000000..f07ba93043580
--- /dev/null
+++ b/llvm/test/Transforms/TailCallElim/ptrauth-bundle.ll
@@ -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
+}

diff  --git a/llvm/test/Verifier/ptrauth-operand-bundles.ll b/llvm/test/Verifier/ptrauth-operand-bundles.ll
new file mode 100644
index 0000000000000..813a210004ae6
--- /dev/null
+++ b/llvm/test/Verifier/ptrauth-operand-bundles.ll
@@ -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
+}


        


More information about the llvm-commits mailing list