Skip to content

Commit 4afc256

Browse files
authored
Add llvm.protected.field.ptr intrinsic and pre-ISel lowering.
This intrinsic is used to implement pointer field protection. For more information, see the included LangRef update and the RFC: https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555 Reviewers: nikic, fmayer, ahmedbougacha Reviewed By: nikic, fmayer Pull Request: #151647
1 parent c2409b4 commit 4afc256

File tree

5 files changed

+424
-0
lines changed

5 files changed

+424
-0
lines changed

llvm/docs/LangRef.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31721,3 +31721,55 @@ Semantics:
3172131721

3172231722
The '``llvm.preserve.struct.access.index``' intrinsic produces the same result
3172331723
as a getelementptr with base ``base`` and access operands ``{0, gep_index}``.
31724+
31725+
'``llvm.protected.field.ptr``' Intrinsic
31726+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31727+
31728+
Syntax:
31729+
"""""""
31730+
31731+
::
31732+
31733+
declare ptr @llvm.protected.field.ptr(ptr ptr, i64 disc, i1 use_hw_encoding)
31734+
31735+
Overview:
31736+
"""""""""
31737+
31738+
The '``llvm.protected.field.ptr``' intrinsic returns a pointer to the
31739+
storage location of a pointer that has special properties as described
31740+
below.
31741+
31742+
Arguments:
31743+
""""""""""
31744+
31745+
The first argument is the pointer specifying the location to store the
31746+
pointer. The second argument is the discriminator, which is used as an
31747+
input for the pointer encoding. The third argument specifies whether to
31748+
use a target-specific mechanism to encode the pointer.
31749+
31750+
Semantics:
31751+
""""""""""
31752+
31753+
This intrinsic returns a pointer which may be used to store a
31754+
pointer at the specified address that is encoded using the specified
31755+
discriminator. Stores via the pointer will cause the stored pointer to be
31756+
blended with the second argument before being stored. The blend operation
31757+
shall be either a weak but cheap and target-independent operation (if
31758+
the third argument is 0) or a stronger target-specific operation (if the
31759+
third argument is 1). When loading from the pointer, the inverse operation
31760+
is done on the loaded pointer after it is loaded. Specifically, when the
31761+
third argument is 1, the pointer is signed (using pointer authentication
31762+
instructions or emulated PAC if not supported by the hardware) using
31763+
the discriminator before being stored, and authenticated after being
31764+
loaded. Note that it is currently unsupported to have the third argument
31765+
be 1 on targets other than AArch64, and it is also currently unsupported
31766+
to have the third argument be 0 at all.
31767+
31768+
If the pointer is used other than for loading or storing (e.g. its
31769+
address escapes), that will disable all blending operations using
31770+
the deactivation symbol specified in the intrinsic's operand bundle.
31771+
The deactivation symbol operand bundle is copied onto any sign and auth
31772+
intrinsics that this intrinsic is lowered into. The intent is that the
31773+
deactivation symbol represents a field identifier.
31774+
31775+
This intrinsic is used to implement structure protection.

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2925,6 +2925,13 @@ def int_experimental_convergence_anchor
29252925
def int_experimental_convergence_loop
29262926
: DefaultAttrsIntrinsic<[llvm_token_ty], [], [IntrNoMem, IntrConvergent]>;
29272927

2928+
//===----------------- Structure Protection Intrinsics --------------------===//
2929+
2930+
def int_protected_field_ptr :
2931+
DefaultAttrsIntrinsic<[llvm_anyptr_ty],
2932+
[LLVMMatchType<0>, llvm_i64_ty, llvm_i1_ty],
2933+
[IntrNoMem, ImmArg<ArgIndex<2>>]>;
2934+
29282935
//===----------------------------------------------------------------------===//
29292936
// Target-specific intrinsics
29302937
//===----------------------------------------------------------------------===//

llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
#include "llvm/CodeGen/TargetLowering.h"
2323
#include "llvm/CodeGen/TargetPassConfig.h"
2424
#include "llvm/IR/Function.h"
25+
#include "llvm/IR/GlobalValue.h"
2526
#include "llvm/IR/IRBuilder.h"
2627
#include "llvm/IR/Instructions.h"
2728
#include "llvm/IR/IntrinsicInst.h"
29+
#include "llvm/IR/Metadata.h"
2830
#include "llvm/IR/Module.h"
2931
#include "llvm/IR/RuntimeLibcalls.h"
3032
#include "llvm/IR/Type.h"
@@ -471,6 +473,144 @@ bool PreISelIntrinsicLowering::expandMemIntrinsicUses(
471473
return Changed;
472474
}
473475

476+
static bool expandProtectedFieldPtr(Function &Intr) {
477+
Module &M = *Intr.getParent();
478+
479+
SmallPtrSet<GlobalValue *, 2> DSsToDeactivate;
480+
481+
Type *Int8Ty = Type::getInt8Ty(M.getContext());
482+
Type *Int64Ty = Type::getInt64Ty(M.getContext());
483+
PointerType *PtrTy = PointerType::get(M.getContext(), 0);
484+
485+
Function *SignIntr =
486+
Intrinsic::getOrInsertDeclaration(&M, Intrinsic::ptrauth_sign, {});
487+
Function *AuthIntr =
488+
Intrinsic::getOrInsertDeclaration(&M, Intrinsic::ptrauth_auth, {});
489+
490+
auto *EmuFnTy = FunctionType::get(Int64Ty, {Int64Ty, Int64Ty}, false);
491+
492+
auto CreateSign = [&](IRBuilder<> &B, Value *Val, Value *Disc,
493+
OperandBundleDef DSBundle) {
494+
Function *F = B.GetInsertBlock()->getParent();
495+
Attribute FSAttr = F->getFnAttribute("target-features");
496+
if (FSAttr.isValid() && FSAttr.getValueAsString().contains("+pauth"))
497+
return B.CreateCall(
498+
SignIntr, {Val, B.getInt32(/*AArch64PACKey::DA*/ 2), Disc}, DSBundle);
499+
FunctionCallee EmuSignIntr =
500+
M.getOrInsertFunction("__emupac_pacda", EmuFnTy);
501+
return B.CreateCall(EmuSignIntr, {Val, Disc}, DSBundle);
502+
};
503+
504+
auto CreateAuth = [&](IRBuilder<> &B, Value *Val, Value *Disc,
505+
OperandBundleDef DSBundle) {
506+
Function *F = B.GetInsertBlock()->getParent();
507+
Attribute FSAttr = F->getFnAttribute("target-features");
508+
if (FSAttr.isValid() && FSAttr.getValueAsString().contains("+pauth"))
509+
return B.CreateCall(
510+
AuthIntr, {Val, B.getInt32(/*AArch64PACKey::DA*/ 2), Disc}, DSBundle);
511+
FunctionCallee EmuAuthIntr =
512+
M.getOrInsertFunction("__emupac_autda", EmuFnTy);
513+
return B.CreateCall(EmuAuthIntr, {Val, Disc}, DSBundle);
514+
};
515+
516+
auto GetDeactivationSymbol = [&](CallInst *Call) -> GlobalValue * {
517+
if (auto Bundle =
518+
Call->getOperandBundle(LLVMContext::OB_deactivation_symbol))
519+
return cast<GlobalValue>(Bundle->Inputs[0]);
520+
return nullptr;
521+
};
522+
523+
for (User *U : llvm::make_early_inc_range(Intr.users())) {
524+
auto *Call = cast<CallInst>(U);
525+
526+
auto *Pointer = Call->getArgOperand(0);
527+
auto *Disc = Call->getArgOperand(1);
528+
bool UseHWEncoding =
529+
cast<ConstantInt>(Call->getArgOperand(2))->getZExtValue();
530+
if (!UseHWEncoding)
531+
reportFatalUsageError("software encoding currently unsupported");
532+
533+
auto *DS = GetDeactivationSymbol(Call);
534+
OperandBundleDef DSBundle("deactivation-symbol", DS);
535+
536+
for (Use &U : llvm::make_early_inc_range(Call->uses())) {
537+
// Insert code to encode each pointer stored to the pointer returned by
538+
// the intrinsic.
539+
if (auto *SI = dyn_cast<StoreInst>(U.getUser())) {
540+
if (U.getOperandNo() == 1 &&
541+
isa<PointerType>(SI->getValueOperand()->getType())) {
542+
IRBuilder<> B(SI);
543+
auto *SIValInt =
544+
B.CreatePtrToInt(SI->getValueOperand(), B.getInt64Ty());
545+
Value *Sign = CreateSign(B, SIValInt, Disc, DSBundle);
546+
SI->setOperand(0, B.CreateIntToPtr(Sign, B.getPtrTy()));
547+
SI->setOperand(1, Pointer);
548+
continue;
549+
}
550+
}
551+
552+
// Insert code to decode each pointer loaded from the pointer returned by
553+
// the intrinsic. This is the inverse of the encode operation implemented
554+
// above.
555+
if (auto *LI = dyn_cast<LoadInst>(U.getUser())) {
556+
if (isa<PointerType>(LI->getType())) {
557+
IRBuilder<> B(LI);
558+
auto *NewLI = cast<LoadInst>(LI->clone());
559+
NewLI->setOperand(0, Pointer);
560+
B.Insert(NewLI);
561+
auto *LIInt = B.CreatePtrToInt(NewLI, B.getInt64Ty());
562+
Value *Auth = CreateAuth(B, LIInt, Disc, DSBundle);
563+
LI->replaceAllUsesWith(B.CreateIntToPtr(Auth, B.getPtrTy()));
564+
LI->eraseFromParent();
565+
continue;
566+
}
567+
}
568+
// Comparisons against null cannot be used to recover the original
569+
// pointer so we replace them with comparisons against the original
570+
// pointer.
571+
if (auto *CI = dyn_cast<ICmpInst>(U.getUser())) {
572+
if (auto *Op = dyn_cast<Constant>(CI->getOperand(0))) {
573+
if (Op->isNullValue()) {
574+
CI->setOperand(1, Pointer);
575+
continue;
576+
}
577+
}
578+
if (auto *Op = dyn_cast<Constant>(CI->getOperand(1))) {
579+
if (Op->isNullValue()) {
580+
CI->setOperand(0, Pointer);
581+
continue;
582+
}
583+
}
584+
}
585+
586+
// We couldn't rewrite away this use of the intrinsic. Replace it with the
587+
// pointer operand, and arrange to define a deactivation symbol.
588+
U.set(Pointer);
589+
if (DS)
590+
DSsToDeactivate.insert(DS);
591+
}
592+
593+
Call->eraseFromParent();
594+
}
595+
596+
if (!DSsToDeactivate.empty()) {
597+
// This is an AArch64 NOP instruction. When the deactivation symbol support
598+
// is expanded to more architectures, there will likely need to be an API
599+
// for retrieving this constant.
600+
Constant *Nop =
601+
ConstantExpr::getIntToPtr(ConstantInt::get(Int64Ty, 0xd503201f), PtrTy);
602+
for (GlobalValue *OldDS : DSsToDeactivate) {
603+
GlobalValue *DS = GlobalAlias::create(
604+
Int8Ty, 0, GlobalValue::ExternalLinkage, OldDS->getName(), Nop, &M);
605+
DS->setVisibility(GlobalValue::HiddenVisibility);
606+
DS->takeName(OldDS);
607+
OldDS->replaceAllUsesWith(DS);
608+
OldDS->eraseFromParent();
609+
}
610+
}
611+
return true;
612+
}
613+
474614
bool PreISelIntrinsicLowering::lowerIntrinsics(Module &M) const {
475615
// Map unique constants to globals.
476616
DenseMap<Constant *, GlobalVariable *> CMap;
@@ -613,6 +753,9 @@ bool PreISelIntrinsicLowering::lowerIntrinsics(Module &M) const {
613753
return lowerUnaryVectorIntrinsicAsLoop(M, CI);
614754
});
615755
break;
756+
case Intrinsic::protected_field_ptr:
757+
Changed |= expandProtectedFieldPtr(F);
758+
break;
616759
}
617760
}
618761
return Changed;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
2+
; RUN: opt -passes=pre-isel-intrinsic-lowering -S < %s | FileCheck --check-prefix=NOPAUTH %s
3+
; RUN: opt -passes=pre-isel-intrinsic-lowering -mattr=+pauth -S < %s | FileCheck --check-prefix=PAUTH %s
4+
5+
target triple = "aarch64-unknown-linux-gnu"
6+
7+
@ds1 = external global i8
8+
@ds2 = external global i8
9+
@ds3 = external global i8
10+
@ds4 = external global i8
11+
12+
;.
13+
; NOPAUTH: @ds1 = external global i8
14+
; NOPAUTH: @ds2 = external global i8
15+
; NOPAUTH: @ds3 = external global i8
16+
; NOPAUTH: @ds4 = hidden alias i8, inttoptr (i64 3573751839 to ptr)
17+
;.
18+
; PAUTH: @ds1 = external global i8
19+
; PAUTH: @ds2 = external global i8
20+
; PAUTH: @ds3 = external global i8
21+
; PAUTH: @ds4 = hidden alias i8, inttoptr (i64 3573751839 to ptr)
22+
;.
23+
define ptr @load_hw(ptr addrspace(1) %ptrptr) {
24+
; NOPAUTH-LABEL: define ptr @load_hw(
25+
; NOPAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]]) {
26+
; NOPAUTH-NEXT: [[PTR:%.*]] = load ptr, ptr addrspace(1) [[PTRPTR]], align 8
27+
; NOPAUTH-NEXT: [[TMP1:%.*]] = ptrtoint ptr [[PTR]] to i64
28+
; NOPAUTH-NEXT: [[TMP2:%.*]] = call i64 @__emupac_autda(i64 [[TMP1]], i64 1) [ "deactivation-symbol"(ptr @ds1) ]
29+
; NOPAUTH-NEXT: [[TMP3:%.*]] = inttoptr i64 [[TMP2]] to ptr
30+
; NOPAUTH-NEXT: ret ptr [[TMP3]]
31+
;
32+
; PAUTH-LABEL: define ptr @load_hw(
33+
; PAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]]) #[[ATTR0:[0-9]+]] {
34+
; PAUTH-NEXT: [[PTR:%.*]] = load ptr, ptr addrspace(1) [[PTRPTR]], align 8
35+
; PAUTH-NEXT: [[TMP1:%.*]] = ptrtoint ptr [[PTR]] to i64
36+
; PAUTH-NEXT: [[TMP2:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[TMP1]], i32 2, i64 1) [ "deactivation-symbol"(ptr @ds1) ]
37+
; PAUTH-NEXT: [[TMP3:%.*]] = inttoptr i64 [[TMP2]] to ptr
38+
; PAUTH-NEXT: ret ptr [[TMP3]]
39+
;
40+
%protptrptr = call ptr addrspace(1) @llvm.protected.field.ptr.p1(ptr addrspace(1) %ptrptr, i64 1, i1 true) [ "deactivation-symbol"(ptr @ds1) ]
41+
%ptr = load ptr, ptr addrspace(1) %protptrptr
42+
ret ptr %ptr
43+
}
44+
45+
define void @store_hw(ptr addrspace(1) %ptrptr, ptr %ptr) {
46+
; NOPAUTH-LABEL: define void @store_hw(
47+
; NOPAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]], ptr [[PTR:%.*]]) {
48+
; NOPAUTH-NEXT: [[TMP1:%.*]] = ptrtoint ptr [[PTR]] to i64
49+
; NOPAUTH-NEXT: [[TMP2:%.*]] = call i64 @__emupac_pacda(i64 [[TMP1]], i64 2) [ "deactivation-symbol"(ptr @ds2) ]
50+
; NOPAUTH-NEXT: [[TMP3:%.*]] = inttoptr i64 [[TMP2]] to ptr
51+
; NOPAUTH-NEXT: store ptr [[TMP3]], ptr addrspace(1) [[PTRPTR]], align 8
52+
; NOPAUTH-NEXT: ret void
53+
;
54+
; PAUTH-LABEL: define void @store_hw(
55+
; PAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]], ptr [[PTR:%.*]]) #[[ATTR0]] {
56+
; PAUTH-NEXT: [[TMP1:%.*]] = ptrtoint ptr [[PTR]] to i64
57+
; PAUTH-NEXT: [[TMP2:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[TMP1]], i32 2, i64 2) [ "deactivation-symbol"(ptr @ds2) ]
58+
; PAUTH-NEXT: [[TMP3:%.*]] = inttoptr i64 [[TMP2]] to ptr
59+
; PAUTH-NEXT: store ptr [[TMP3]], ptr addrspace(1) [[PTRPTR]], align 8
60+
; PAUTH-NEXT: ret void
61+
;
62+
%protptrptr = call ptr addrspace(1) @llvm.protected.field.ptr.p1(ptr addrspace(1) %ptrptr, i64 2, i1 true) [ "deactivation-symbol"(ptr @ds2) ]
63+
store ptr %ptr, ptr addrspace(1) %protptrptr
64+
ret void
65+
}
66+
67+
define i1 @compare(ptr addrspace(1) %ptrptr) {
68+
; NOPAUTH-LABEL: define i1 @compare(
69+
; NOPAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]]) {
70+
; NOPAUTH-NEXT: [[CMP1:%.*]] = icmp eq ptr addrspace(1) [[PTRPTR]], null
71+
; NOPAUTH-NEXT: [[CMP2:%.*]] = icmp eq ptr addrspace(1) null, [[PTRPTR]]
72+
; NOPAUTH-NEXT: [[CMP:%.*]] = or i1 [[CMP1]], [[CMP2]]
73+
; NOPAUTH-NEXT: ret i1 [[CMP]]
74+
;
75+
; PAUTH-LABEL: define i1 @compare(
76+
; PAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]]) #[[ATTR0]] {
77+
; PAUTH-NEXT: [[CMP1:%.*]] = icmp eq ptr addrspace(1) [[PTRPTR]], null
78+
; PAUTH-NEXT: [[CMP2:%.*]] = icmp eq ptr addrspace(1) null, [[PTRPTR]]
79+
; PAUTH-NEXT: [[CMP:%.*]] = or i1 [[CMP1]], [[CMP2]]
80+
; PAUTH-NEXT: ret i1 [[CMP]]
81+
;
82+
%protptrptr = call ptr addrspace(1) @llvm.protected.field.ptr.p1(ptr addrspace(1) %ptrptr, i64 3, i1 true) [ "deactivation-symbol"(ptr @ds3) ]
83+
%cmp1 = icmp eq ptr addrspace(1) %protptrptr, null
84+
%cmp2 = icmp eq ptr addrspace(1) null, %protptrptr
85+
%cmp = or i1 %cmp1, %cmp2
86+
ret i1 %cmp
87+
}
88+
89+
define ptr addrspace(1) @escape(ptr addrspace(1) %ptrptr) {
90+
; NOPAUTH-LABEL: define ptr addrspace(1) @escape(
91+
; NOPAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]]) {
92+
; NOPAUTH-NEXT: ret ptr addrspace(1) [[PTRPTR]]
93+
;
94+
; PAUTH-LABEL: define ptr addrspace(1) @escape(
95+
; PAUTH-SAME: ptr addrspace(1) [[PTRPTR:%.*]]) #[[ATTR0]] {
96+
; PAUTH-NEXT: ret ptr addrspace(1) [[PTRPTR]]
97+
;
98+
%protptrptr = call ptr addrspace(1) @llvm.protected.field.ptr.p1(ptr addrspace(1) %ptrptr, i64 3, i1 true) [ "deactivation-symbol"(ptr @ds4) ]
99+
ret ptr addrspace(1) %protptrptr
100+
}
101+
102+
declare ptr addrspace(1) @llvm.protected.field.ptr.p1(ptr addrspace(1), i64, i1 immarg)
103+
;.
104+
; NOPAUTH: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(none) }
105+
; NOPAUTH: attributes #[[ATTR1:[0-9]+]] = { nounwind memory(none) }
106+
;.
107+
; PAUTH: attributes #[[ATTR0]] = { "target-features"="+pauth" }
108+
; PAUTH: attributes #[[ATTR1:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(none) "target-features"="+pauth" }
109+
; PAUTH: attributes #[[ATTR2:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(none) }
110+
; PAUTH: attributes #[[ATTR3:[0-9]+]] = { nounwind memory(none) }
111+
;.

0 commit comments

Comments
 (0)