Skip to content

Commit c703f85

Browse files
committed
[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
1 parent aa15274 commit c703f85

File tree

11 files changed

+126
-10
lines changed

11 files changed

+126
-10
lines changed

llvm/docs/LangRef.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2510,6 +2510,15 @@ void, in which case the operand bundle is ignored.
25102510
The operand bundle is needed to ensure the call is immediately followed by the
25112511
marker instruction and the ObjC runtime call in the final output.
25122512

2513+
.. _ob_ptrauth:
2514+
2515+
Pointer Authentication Operand Bundles
2516+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2517+
2518+
Pointer Authentication operand bundles are characterized by the
2519+
``"ptrauth"`` operand bundle tag. They are described in the
2520+
`Pointer Authentication <PointerAuth.html#operand-bundle>`_ document.
2521+
25132522
.. _moduleasm:
25142523

25152524
Module-Level Inline Assembly

llvm/docs/PointerAuth.md

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ Before the pointer is used, it needs to be authenticated, i.e., have its
1010
signature checked. This prevents pointer values of unknown origin from being
1111
used to replace the signed pointer value.
1212

13-
At the IR level, it is represented using a [set of intrinsics](#intrinsics)
14-
(to sign/authenticate pointers).
13+
At the IR level, it is represented using:
14+
15+
* a [set of intrinsics](#intrinsics) (to sign/authenticate pointers)
16+
* a [call operand bundle](#operand-bundle) (to authenticate called pointers)
1517

1618
The current implementation leverages the
1719
[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
220222
implementation.
221223

222224

225+
### Operand Bundle
226+
227+
Function pointers used as indirect call targets can be signed when materialized,
228+
and authenticated before calls. This can be accomplished with the
229+
[``llvm.ptrauth.auth``](#llvm-ptrauth-auth) intrinsic, feeding its result to
230+
an indirect call.
231+
232+
However, that exposes the intermediate, unauthenticated pointer, e.g., if it
233+
gets spilled to the stack. An attacker can then overwrite the pointer in
234+
memory, negating the security benefit provided by pointer authentication.
235+
To prevent that, the ``ptrauth`` operand bundle may be used: it guarantees that
236+
the intermediate call target is kept in a register and never stored to memory.
237+
This hardening benefit is similar to that provided by
238+
[``llvm.ptrauth.resign``](#llvm-ptrauth-resign)).
239+
240+
Concretely:
241+
242+
```llvm
243+
define void @f(void ()* %fp) {
244+
call void %fp() [ "ptrauth"(i32 <key>, i64 <data>) ]
245+
ret void
246+
}
247+
```
248+
249+
is functionally equivalent to:
250+
251+
```llvm
252+
define void @f(void ()* %fp) {
253+
%fp_i = ptrtoint void ()* %fp to i64
254+
%fp_auth = call i64 @llvm.ptrauth.auth(i64 %fp_i, i32 <key>, i64 <data>)
255+
%fp_auth_p = inttoptr i64 %fp_auth to void ()*
256+
call void %fp_auth_p()
257+
ret void
258+
}
259+
```
260+
261+
but with the added guarantee that ``%fp_i``, ``%fp_auth``, and ``%fp_auth_p``
262+
are not stored to (and reloaded from) memory.
263+
264+
223265
## AArch64 Support
224266

225267
AArch64 is currently the only architecture with full support of the pointer

llvm/include/llvm/IR/InstrTypes.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2068,7 +2068,8 @@ class CallBase : public Instruction {
20682068
bool hasClobberingOperandBundles() const {
20692069
for (auto &BOI : bundle_op_infos()) {
20702070
if (BOI.Tag->second == LLVMContext::OB_deopt ||
2071-
BOI.Tag->second == LLVMContext::OB_funclet)
2071+
BOI.Tag->second == LLVMContext::OB_funclet ||
2072+
BOI.Tag->second == LLVMContext::OB_ptrauth)
20722073
continue;
20732074

20742075
// This instruction has an operand bundle that is not known to us.

llvm/include/llvm/IR/LLVMContext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class LLVMContext {
9393
OB_preallocated = 4, // "preallocated"
9494
OB_gc_live = 5, // "gc-live"
9595
OB_clang_arc_attachedcall = 6, // "clang.arc.attachedcall"
96+
OB_ptrauth = 7, // "ptrauth"
9697
};
9798

9899
/// getMDKindID - Return a unique non-zero ID for the specified metadata kind.

llvm/lib/IR/Instructions.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,9 +482,10 @@ CallBase *CallBase::removeOperandBundle(CallBase *CB, uint32_t ID,
482482

483483
bool CallBase::hasReadingOperandBundles() const {
484484
// Implementation note: this is a conservative implementation of operand
485-
// bundle semantics, where *any* non-assume operand bundle forces a callsite
486-
// to be at least readonly.
487-
return hasOperandBundles() && getIntrinsicID() != Intrinsic::assume;
485+
// bundle semantics, where *any* non-assume operand bundle (other than
486+
// ptrauth) forces a callsite to be at least readonly.
487+
return hasOperandBundlesOtherThan(LLVMContext::OB_ptrauth) &&
488+
getIntrinsicID() != Intrinsic::assume;
488489
}
489490

490491
//===----------------------------------------------------------------------===//

llvm/lib/IR/LLVMContext.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ LLVMContext::LLVMContext() : pImpl(new LLVMContextImpl(*this)) {
8282
"clang.arc.attachedcall operand bundle id drifted!");
8383
(void)ClangAttachedCall;
8484

85+
auto *PtrauthEntry = pImpl->getOrInsertBundleTag("ptrauth");
86+
assert(PtrauthEntry->second == LLVMContext::OB_ptrauth &&
87+
"ptrauth operand bundle id drifted!");
88+
(void)PtrauthEntry;
89+
8590
SyncScope::ID SingleThreadSSID =
8691
pImpl->getOrInsertSyncScopeID("singlethread");
8792
assert(SingleThreadSSID == SyncScope::SingleThread &&

llvm/lib/IR/Verifier.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3285,11 +3285,12 @@ void Verifier::visitCallBase(CallBase &Call) {
32853285
visitIntrinsicCall(ID, Call);
32863286

32873287
// Verify that a callsite has at most one "deopt", at most one "funclet", at
3288-
// most one "gc-transition", at most one "cfguardtarget",
3289-
// and at most one "preallocated" operand bundle.
3288+
// most one "gc-transition", at most one "cfguardtarget", at most one
3289+
// "preallocated" operand bundle, and at most one "ptrauth" operand bundle.
32903290
bool FoundDeoptBundle = false, FoundFuncletBundle = false,
32913291
FoundGCTransitionBundle = false, FoundCFGuardTargetBundle = false,
32923292
FoundPreallocatedBundle = false, FoundGCLiveBundle = false,
3293+
FoundPtrauthBundle = false,
32933294
FoundAttachedCallBundle = false;
32943295
for (unsigned i = 0, e = Call.getNumOperandBundles(); i < e; ++i) {
32953296
OperandBundleUse BU = Call.getOperandBundleAt(i);
@@ -3315,6 +3316,16 @@ void Verifier::visitCallBase(CallBase &Call) {
33153316
FoundCFGuardTargetBundle = true;
33163317
Assert(BU.Inputs.size() == 1,
33173318
"Expected exactly one cfguardtarget bundle operand", Call);
3319+
} else if (Tag == LLVMContext::OB_ptrauth) {
3320+
Assert(!FoundPtrauthBundle, "Multiple ptrauth operand bundles", Call);
3321+
FoundPtrauthBundle = true;
3322+
Assert(BU.Inputs.size() == 2,
3323+
"Expected exactly two ptrauth bundle operands", Call);
3324+
Assert(isa<ConstantInt>(BU.Inputs[0]) &&
3325+
BU.Inputs[0]->getType()->isIntegerTy(32),
3326+
"Ptrauth bundle key operand must be an i32 constant", Call);
3327+
Assert(BU.Inputs[1]->getType()->isIntegerTy(64),
3328+
"Ptrauth bundle discriminator operand must be an i64", Call);
33183329
} else if (Tag == LLVMContext::OB_preallocated) {
33193330
Assert(!FoundPreallocatedBundle, "Multiple preallocated operand bundles",
33203331
Call);
@@ -3339,6 +3350,10 @@ void Verifier::visitCallBase(CallBase &Call) {
33393350
}
33403351
}
33413352

3353+
// Verify that callee and callsite agree on whether to use pointer auth.
3354+
Assert(!(Call.getCalledFunction() && FoundPtrauthBundle),
3355+
"Direct call cannot have a ptrauth bundle", Call);
3356+
33423357
// Verify that each inlinable callsite of a debug-info-bearing function in a
33433358
// debug-info-bearing function has a debug location attached to it. Failure to
33443359
// do so causes assertion failures when the inliner sets up inline scope info.

llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,10 @@ static bool markTails(Function &F, OptimizationRemarkEmitter *ORE) {
248248
isa<PseudoProbeInst>(&I))
249249
continue;
250250

251-
// Special-case operand bundle "clang.arc.attachedcall".
251+
// Special-case operand bundles "clang.arc.attachedcall" and "ptrauth".
252252
bool IsNoTail =
253253
CI->isNoTailCall() || CI->hasOperandBundlesOtherThan(
254-
LLVMContext::OB_clang_arc_attachedcall);
254+
{LLVMContext::OB_clang_arc_attachedcall, LLVMContext::OB_ptrauth});
255255

256256
if (!IsNoTail && CI->doesNotAccessMemory()) {
257257
// A call to a readnone function whose arguments are all things computed

llvm/test/Bitcode/operand-bundles-bc-analyzer.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
1111
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
1212
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
13+
; CHECK-NEXT: <OPERAND_BUNDLE_TAG
1314
; CHECK-NEXT: </OPERAND_BUNDLE_TAGS_BLOCK
1415

1516
; CHECK: <FUNCTION_BLOCK
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
; RUN: opt < %s -tailcallelim -verify-dom-info -S | FileCheck %s
2+
; Check that the "ptrauth" operand bundle doesn't prevent tail calls.
3+
4+
define i64 @f_1(i64 %x, i64(i64)* %f_0) {
5+
; CHECK-LABEL: @f_1(
6+
entry:
7+
; CHECK: tail call i64 %f_0(i64 %x) [ "ptrauth"(i32 42, i64 %x) ]
8+
%tmp = call i64 %f_0(i64 %x) [ "ptrauth"(i32 42, i64 %x) ]
9+
ret i64 0
10+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
; RUN: not opt -verify < %s 2>&1 | FileCheck %s
2+
3+
declare void @g()
4+
5+
define void @test_ptrauth_bundle(i64 %arg0, i32 %arg1, void()* %arg2) {
6+
7+
; CHECK: Multiple ptrauth operand bundles
8+
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i32 42, i64 100), "ptrauth"(i32 42, i64 %arg0) ]
9+
call void %arg2() [ "ptrauth"(i32 42, i64 100), "ptrauth"(i32 42, i64 %arg0) ]
10+
11+
; CHECK: Ptrauth bundle key operand must be an i32 constant
12+
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i32 %arg1, i64 120) ]
13+
call void %arg2() [ "ptrauth"(i32 %arg1, i64 120) ]
14+
15+
; CHECK: Ptrauth bundle key operand must be an i32 constant
16+
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i64 42, i64 120) ]
17+
call void %arg2() [ "ptrauth"(i64 42, i64 120) ]
18+
19+
; CHECK: Ptrauth bundle discriminator operand must be an i64
20+
; CHECK-NEXT: call void %arg2() [ "ptrauth"(i32 42, i32 120) ]
21+
call void %arg2() [ "ptrauth"(i32 42, i32 120) ]
22+
23+
; CHECK: Direct call cannot have a ptrauth bundle
24+
; CHECK-NEXT: call void @g() [ "ptrauth"(i32 42, i64 120) ]
25+
call void @g() [ "ptrauth"(i32 42, i64 120) ]
26+
27+
; CHECK-NOT: call
28+
call void %arg2() [ "ptrauth"(i32 42, i64 120) ] ; OK
29+
call void %arg2() [ "ptrauth"(i32 42, i64 %arg0) ] ; OK
30+
ret void
31+
}

0 commit comments

Comments
 (0)