Skip to content

Commit acbf077

Browse files
committed
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 Pull Request: llvm#151647
1 parent d3b5bfd commit acbf077

File tree

4 files changed

+305
-0
lines changed

4 files changed

+305
-0
lines changed

llvm/docs/LangRef.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31161,3 +31161,57 @@ This intrinsic is assumed to execute in the default :ref:`floating-point
3116131161
environment <floatenv>` *except* for the rounding mode.
3116231162
This intrinsic is not supported on all targets. Some targets may not support
3116331163
all rounding modes.
31164+
31165+
'``llvm.protected.field.ptr``' Intrinsic
31166+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31167+
31168+
Syntax:
31169+
"""""""
31170+
31171+
::
31172+
31173+
declare ptr @llvm.protected.field.ptr(ptr ptr, i64 disc, i1 use_hw_encoding)
31174+
31175+
Overview:
31176+
"""""""""
31177+
31178+
The '``llvm.protected.field.ptr``' intrinsic returns a pointer to the
31179+
storage location of a pointer that has special properties as described
31180+
below.
31181+
31182+
Arguments:
31183+
""""""""""
31184+
31185+
The first argument is the pointer specifying the location to store the
31186+
pointer. The second argument is the discriminator, which is used as an
31187+
input for the pointer encoding. The third argument specifies whether to
31188+
use a target-specific mechanism to encode the pointer.
31189+
31190+
Semantics:
31191+
""""""""""
31192+
31193+
This intrinsic returns a pointer which may be used to store a
31194+
pointer at the specified address that is encoded using the specified
31195+
discriminator. Stores via the pointer will cause the stored pointer to be
31196+
blended with the second argument before being stored. The blend operation
31197+
shall be either a weak but cheap and target-independent operation (if
31198+
the third argument is 0) or a stronger target-specific operation (if the
31199+
third argument is 1). When loading from the pointer, the inverse operation
31200+
is done on the loaded pointer after it is loaded. Specifically, when the
31201+
third argument is 1, the pointer is signed (using pointer authentication
31202+
instructions or emulated PAC if not supported by the hardware) using
31203+
the struct address before being stored, and authenticated after being
31204+
loaded. Note that it is currently unsupported to have the third argument
31205+
be 1 on targets other than AArch64. When the third argument is 0, it is
31206+
rotated left by 16 bits and the discriminator is subtracted before being
31207+
stored, and the discriminator is added and the pointer is rotated right
31208+
by 16 bits after being loaded.
31209+
31210+
If the pointer is used otherwise than for loading or storing (e.g. its
31211+
address escapes), that will disable all blending operations using
31212+
the deactivation symbol specified in the intrinsic's operand bundle.
31213+
The deactivation symbol operand bundle is copied onto any sign and auth
31214+
intrinsics that this intrinsic is lowered into. The intent is that the
31215+
deactivation symbol represents a field identifier.
31216+
31217+
This intrinsic is used to implement structure protection.

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2850,6 +2850,12 @@ def int_experimental_convergence_anchor
28502850
def int_experimental_convergence_loop
28512851
: DefaultAttrsIntrinsic<[llvm_token_ty], [], [IntrNoMem, IntrConvergent]>;
28522852

2853+
//===----------------- Structure Protection Intrinsics --------------------===//
2854+
2855+
def int_protected_field_ptr :
2856+
DefaultAttrsIntrinsic<[llvm_ptr_ty], [llvm_ptr_ty, llvm_i64_ty, llvm_i1_ty],
2857+
[IntrNoMem, ImmArg<ArgIndex<2>>]>;
2858+
28532859
//===----------------------------------------------------------------------===//
28542860
// Target-specific intrinsics
28552861
//===----------------------------------------------------------------------===//

llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
#include "llvm/CodeGen/TargetLowering.h"
2222
#include "llvm/CodeGen/TargetPassConfig.h"
2323
#include "llvm/IR/Function.h"
24+
#include "llvm/IR/GlobalValue.h"
2425
#include "llvm/IR/IRBuilder.h"
2526
#include "llvm/IR/Instructions.h"
2627
#include "llvm/IR/IntrinsicInst.h"
28+
#include "llvm/IR/Metadata.h"
2729
#include "llvm/IR/Module.h"
2830
#include "llvm/IR/RuntimeLibcalls.h"
2931
#include "llvm/IR/Type.h"
@@ -37,6 +39,8 @@
3739
#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
3840
#include "llvm/Transforms/Utils/LowerVectorIntrinsics.h"
3941

42+
#include <set>
43+
4044
using namespace llvm;
4145

4246
/// Threshold to leave statically sized memory intrinsic calls. Calls of known
@@ -461,6 +465,198 @@ bool PreISelIntrinsicLowering::expandMemIntrinsicUses(
461465
return Changed;
462466
}
463467

468+
namespace {
469+
470+
enum class PointerEncoding {
471+
Rotate,
472+
PACCopyable,
473+
PACNonCopyable,
474+
};
475+
476+
bool expandProtectedFieldPtr(Function &Intr) {
477+
Module &M = *Intr.getParent();
478+
479+
std::set<GlobalValue *> DSsToDeactivate;
480+
std::set<Instruction *> LoadsStores;
481+
482+
Type *Int8Ty = Type::getInt8Ty(M.getContext());
483+
Type *Int64Ty = Type::getInt64Ty(M.getContext());
484+
PointerType *PtrTy = PointerType::get(M.getContext(), 0);
485+
486+
Function *SignIntr =
487+
Intrinsic::getOrInsertDeclaration(&M, Intrinsic::ptrauth_sign, {});
488+
Function *AuthIntr =
489+
Intrinsic::getOrInsertDeclaration(&M, Intrinsic::ptrauth_auth, {});
490+
491+
auto *EmuFnTy = FunctionType::get(Int64Ty, {Int64Ty, Int64Ty}, false);
492+
FunctionCallee EmuSignIntr = M.getOrInsertFunction("__emupac_pacda", EmuFnTy);
493+
FunctionCallee EmuAuthIntr = M.getOrInsertFunction("__emupac_autda", EmuFnTy);
494+
495+
auto CreateSign = [&](IRBuilder<> &B, Value *Val, Value *Disc,
496+
OperandBundleDef DSBundle) {
497+
Function *F = B.GetInsertBlock()->getParent();
498+
Attribute FSAttr = F->getFnAttribute("target-features");
499+
if (FSAttr.isValid() && FSAttr.getValueAsString().contains("+pauth"))
500+
return B.CreateCall(SignIntr, {Val, B.getInt32(2), Disc}, DSBundle);
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(AuthIntr, {Val, B.getInt32(2), Disc}, DSBundle);
510+
return B.CreateCall(EmuAuthIntr, {Val, Disc}, DSBundle);
511+
};
512+
513+
auto GetDeactivationSymbol = [&](CallInst *Call) -> GlobalValue * {
514+
if (auto Bundle =
515+
Call->getOperandBundle(LLVMContext::OB_deactivation_symbol))
516+
return cast<GlobalValue>(Bundle->Inputs[0]);
517+
return nullptr;
518+
};
519+
520+
for (User *U : Intr.users()) {
521+
auto *Call = cast<CallInst>(U);
522+
auto *DS = GetDeactivationSymbol(Call);
523+
std::set<PHINode *> VisitedPhis;
524+
525+
std::function<void(Instruction *)> FindLoadsStores;
526+
FindLoadsStores = [&](Instruction *I) {
527+
for (Use &U : I->uses()) {
528+
if (auto *LI = dyn_cast<LoadInst>(U.getUser())) {
529+
if (isa<PointerType>(LI->getType())) {
530+
LoadsStores.insert(LI);
531+
continue;
532+
}
533+
}
534+
if (auto *SI = dyn_cast<StoreInst>(U.getUser())) {
535+
if (U.getOperandNo() == 1 &&
536+
isa<PointerType>(SI->getValueOperand()->getType())) {
537+
LoadsStores.insert(SI);
538+
continue;
539+
}
540+
}
541+
if (auto *P = dyn_cast<PHINode>(U.getUser())) {
542+
if (VisitedPhis.insert(P).second)
543+
FindLoadsStores(P);
544+
continue;
545+
}
546+
// Comparisons against null cannot be used to recover the original
547+
// pointer so we allow them.
548+
if (auto *CI = dyn_cast<ICmpInst>(U.getUser())) {
549+
if (auto *Op = dyn_cast<Constant>(CI->getOperand(0)))
550+
if (Op->isNullValue())
551+
continue;
552+
if (auto *Op = dyn_cast<Constant>(CI->getOperand(1)))
553+
if (Op->isNullValue())
554+
continue;
555+
}
556+
if (DS)
557+
DSsToDeactivate.insert(DS);
558+
}
559+
};
560+
561+
FindLoadsStores(Call);
562+
}
563+
564+
for (Instruction *I : LoadsStores) {
565+
std::set<Value *> Pointers;
566+
std::set<Value *> Discs;
567+
std::set<GlobalValue *> DSs;
568+
std::set<PHINode *> VisitedPhis;
569+
bool UseHWEncoding = false;
570+
571+
std::function<void(Value *)> FindFields;
572+
FindFields = [&](Value *V) {
573+
if (auto *Call = dyn_cast<CallInst>(V)) {
574+
if (Call->getCalledOperand() == &Intr) {
575+
Pointers.insert(Call->getArgOperand(0));
576+
Discs.insert(Call->getArgOperand(1));
577+
if (cast<ConstantInt>(Call->getArgOperand(2))->getZExtValue())
578+
UseHWEncoding = true;
579+
DSs.insert(GetDeactivationSymbol(Call));
580+
return;
581+
}
582+
}
583+
if (auto *P = dyn_cast<PHINode>(V)) {
584+
if (VisitedPhis.insert(P).second)
585+
for (Value *V : P->incoming_values())
586+
FindFields(V);
587+
return;
588+
}
589+
Pointers.insert(nullptr);
590+
};
591+
FindFields(isa<StoreInst>(I) ? cast<StoreInst>(I)->getPointerOperand()
592+
: cast<LoadInst>(I)->getPointerOperand());
593+
if (Pointers.size() != 1 || Discs.size() != 1 || DSs.size() != 1) {
594+
for (GlobalValue *DS : DSs)
595+
if (DS)
596+
DSsToDeactivate.insert(DS);
597+
continue;
598+
}
599+
600+
GlobalValue *DS = *DSs.begin();
601+
OperandBundleDef DSBundle("deactivation-symbol", DS);
602+
603+
if (auto *LI = dyn_cast<LoadInst>(I)) {
604+
IRBuilder<> B(LI->getNextNode());
605+
auto *LIInt = cast<Instruction>(B.CreatePtrToInt(LI, B.getInt64Ty()));
606+
Value *Auth;
607+
if (UseHWEncoding) {
608+
Auth = CreateAuth(B, LIInt, *Discs.begin(), DSBundle);
609+
} else {
610+
Auth = B.CreateAdd(LIInt, *Discs.begin());
611+
Auth = B.CreateIntrinsic(
612+
Auth->getType(), Intrinsic::fshr,
613+
{Auth, Auth, ConstantInt::get(Auth->getType(), 16)});
614+
}
615+
LI->replaceAllUsesWith(B.CreateIntToPtr(Auth, B.getPtrTy()));
616+
LIInt->setOperand(0, LI);
617+
} else if (auto *SI = dyn_cast<StoreInst>(I)) {
618+
IRBuilder<> B(SI);
619+
auto *SIValInt =
620+
B.CreatePtrToInt(SI->getValueOperand(), B.getInt64Ty());
621+
Value *Sign;
622+
if (UseHWEncoding) {
623+
Sign = CreateSign(B, SIValInt, *Discs.begin(), DSBundle);
624+
} else {
625+
Sign = B.CreateIntrinsic(
626+
SIValInt->getType(), Intrinsic::fshl,
627+
{SIValInt, SIValInt, ConstantInt::get(SIValInt->getType(), 16)});
628+
}
629+
SI->setOperand(0, B.CreateIntToPtr(Sign, B.getPtrTy()));
630+
}
631+
}
632+
633+
for (User *U : llvm::make_early_inc_range(Intr.users())) {
634+
auto *Call = cast<CallInst>(U);
635+
auto *Pointer = Call->getArgOperand(0);
636+
637+
Call->replaceAllUsesWith(Pointer);
638+
Call->eraseFromParent();
639+
}
640+
641+
if (!DSsToDeactivate.empty()) {
642+
Constant *Nop =
643+
ConstantExpr::getIntToPtr(ConstantInt::get(Int64Ty, 0xd503201f), PtrTy);
644+
for (GlobalValue *OldDS : DSsToDeactivate) {
645+
GlobalValue *DS = GlobalAlias::create(
646+
Int8Ty, 0, GlobalValue::ExternalLinkage, OldDS->getName(), Nop, &M);
647+
DS->setVisibility(GlobalValue::HiddenVisibility);
648+
if (OldDS) {
649+
DS->takeName(OldDS);
650+
OldDS->replaceAllUsesWith(DS);
651+
OldDS->eraseFromParent();
652+
}
653+
}
654+
}
655+
return true;
656+
}
657+
658+
}
659+
464660
bool PreISelIntrinsicLowering::lowerIntrinsics(Module &M) const {
465661
// Map unique constants to globals.
466662
DenseMap<Constant *, GlobalVariable *> CMap;
@@ -598,6 +794,9 @@ bool PreISelIntrinsicLowering::lowerIntrinsics(Module &M) const {
598794
return lowerUnaryVectorIntrinsicAsLoop(M, CI);
599795
});
600796
break;
797+
case Intrinsic::protected_field_ptr:
798+
Changed |= expandProtectedFieldPtr(F);
799+
break;
601800
}
602801
}
603802
return Changed;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
; RUN: opt -passes=pre-isel-intrinsic-lowering -S < %s | FileCheck --check-prefixes=CHECK,NOPAUTH %s
2+
; RUN: opt -passes=pre-isel-intrinsic-lowering -mattr=+pauth -S < %s | FileCheck --check-prefixes=CHECK,PAUTH %s
3+
4+
target triple = "aarch64-unknown-linux-gnu"
5+
6+
; CHECK: @ds1 = external global i8
7+
@ds1 = external global i8
8+
; CHECK: @ds2 = external global i8
9+
@ds2 = external global i8
10+
; CHECK: @ds3 = hidden alias i8, inttoptr (i64 3573751839 to ptr)
11+
@ds3 = external global i8
12+
13+
; CHECK: define ptr @f1
14+
define ptr @f1(ptr %ptrptr) {
15+
; CHECK: %ptr = load ptr, ptr %ptrptr, align 8
16+
; CHECK: %1 = ptrtoint ptr %ptr to i64
17+
; NOPAUTH: %2 = call i64 @__emupac_autda(i64 %1, i64 1) [ "deactivation-symbol"(ptr @ds1) ]
18+
; PAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 1) [ "deactivation-symbol"(ptr @ds1) ]
19+
; CHECK: %3 = inttoptr i64 %2 to ptr
20+
; CHECK: ret ptr %3
21+
%protptrptr = call ptr @llvm.protected.field.ptr(ptr %ptrptr, i64 1, i1 true) [ "deactivation-symbol"(ptr @ds1) ]
22+
%ptr = load ptr, ptr %protptrptr
23+
ret ptr %ptr
24+
}
25+
26+
; CHECK: define void @f2
27+
define void @f2(ptr %ptrptr, ptr %ptr) {
28+
; CHECK: %1 = ptrtoint ptr %ptr to i64
29+
; NOPAUTH: %2 = call i64 @__emupac_pacda(i64 %1, i64 2) [ "deactivation-symbol"(ptr @ds2) ]
30+
; PAUTH: %2 = call i64 @llvm.ptrauth.sign(i64 %1, i32 2, i64 2) [ "deactivation-symbol"(ptr @ds2) ]
31+
; CHECK: %3 = inttoptr i64 %2 to ptr
32+
; CHECK: store ptr %3, ptr %ptrptr, align 8
33+
; CHECK: ret void
34+
%protptrptr = call ptr @llvm.protected.field.ptr(ptr %ptrptr, i64 2, i1 true) [ "deactivation-symbol"(ptr @ds2) ]
35+
store ptr %ptr, ptr %protptrptr
36+
ret void
37+
}
38+
39+
; CHECK: define ptr @f3
40+
define ptr @f3(ptr %ptrptr) {
41+
; CHECK: ret ptr %ptrptr
42+
%protptrptr = call ptr @llvm.protected.field.ptr(ptr %ptrptr, i64 3, i1 true) [ "deactivation-symbol"(ptr @ds3) ]
43+
ret ptr %protptrptr
44+
}
45+
46+
declare ptr @llvm.protected.field.ptr(ptr, i64, i1 immarg)

0 commit comments

Comments
 (0)