[llvm] [PAC][InstCombine] Replace auth+sign with resign (PR #130807)

Anatoly Trosinenko via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 22 04:16:06 PDT 2025


https://github.com/atrosinenko updated https://github.com/llvm/llvm-project/pull/130807

>From 7852c6b710e94d7f33ab2c26ec1ff82bf0002956 Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Mon, 10 Mar 2025 15:13:05 +0300
Subject: [PATCH 1/3] [PAC][InstCombine] Precommit tests for replacing
 auth+sign with resign

---
 .../InstCombine/ptrauth-intrinsics.ll         | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll b/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll
index 208e162ac9416..fcb6221a8ebd5 100644
--- a/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll
+++ b/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll
@@ -160,6 +160,68 @@ define i64 @test_ptrauth_resign_ptrauth_constant(ptr %p) {
   ret i64 %authed
 }
 
+define i64 @test_ptrauth_auth_sign_same_schema(ptr %p) {
+; CHECK-LABEL: @test_ptrauth_auth_sign_same_schema(
+; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
+; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 1, i64 1234)
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 1234)
+; CHECK-NEXT:    ret i64 [[RESIGNED]]
+;
+  %p.int = ptrtoint ptr %p to i64
+  %authed = call i64 @llvm.ptrauth.auth(i64 %p.int, i32 1, i64 1234)
+  %resigned = call i64 @llvm.ptrauth.sign(i64 %authed, i32 1, i64 1234)
+  ret i64 %resigned
+}
+
+define i64 @test_ptrauth_auth_sign_opaque_disc_same_schema(ptr %p, i64 %disc) {
+; CHECK-LABEL: @test_ptrauth_auth_sign_opaque_disc_same_schema(
+; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
+; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 1, i64 [[DISC:%.*]])
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 [[DISC]])
+; CHECK-NEXT:    ret i64 [[RESIGNED]]
+;
+  %p.int = ptrtoint ptr %p to i64
+  %authed = call i64 @llvm.ptrauth.auth(i64 %p.int, i32 1, i64 %disc)
+  %resigned = call i64 @llvm.ptrauth.sign(i64 %authed, i32 1, i64 %disc)
+  ret i64 %resigned
+}
+
+define i64 @test_ptrauth_auth_sign_different_disc(ptr %p, i64 %disc) {
+; CHECK-LABEL: @test_ptrauth_auth_sign_different_disc(
+; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
+; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 1, i64 [[DISC:%.*]])
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 1234)
+; CHECK-NEXT:    ret i64 [[RESIGNED]]
+;
+  %p.int = ptrtoint ptr %p to i64
+  %authed = call i64 @llvm.ptrauth.auth(i64 %p.int, i32 1, i64 %disc)
+  %resigned = call i64 @llvm.ptrauth.sign(i64 %authed, i32 1, i64 1234)
+  ret i64 %resigned
+}
+
+define i64 @test_ptrauth_auth_sign_different_key(ptr %p) {
+; CHECK-LABEL: @test_ptrauth_auth_sign_different_key(
+; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
+; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 0, i64 1234)
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 1234)
+; CHECK-NEXT:    ret i64 [[RESIGNED]]
+;
+  %p.int = ptrtoint ptr %p to i64
+  %authed = call i64 @llvm.ptrauth.auth(i64 %p.int, i32 0, i64 1234)
+  %resigned = call i64 @llvm.ptrauth.sign(i64 %authed, i32 1, i64 1234)
+  ret i64 %resigned
+}
+
+define i64 @test_ptrauth_sign_nonauth_nonconst_disc(i64 %disc) {
+; CHECK-LABEL: @test_ptrauth_sign_nonauth_nonconst_disc(
+; CHECK-NEXT:    [[SIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr @foo to i64), i32 1, i64 [[DISC:%.*]])
+; CHECK-NEXT:    ret i64 [[SIGNED]]
+;
+  %foo.int = ptrtoint ptr @foo to i64
+  %signed = call i64 @llvm.ptrauth.sign(i64 %foo.int, i32 1, i64 %disc)
+  ret i64 %signed
+}
+
 declare i64 @llvm.ptrauth.auth(i64, i32, i64)
 declare i64 @llvm.ptrauth.sign(i64, i32, i64)
 declare i64 @llvm.ptrauth.resign(i64, i32, i64, i32, i64)

>From afece664d9b2241d7f70cc6d6ef2ff0d0d1fc3c1 Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Mon, 10 Mar 2025 15:13:05 +0300
Subject: [PATCH 2/3] [PAC][InstCombine] Replace auth+sign with resign

Prevent introducing signing oracles by increasing the chances that
pointer resign pattern is detected and kept as a single operation until
it is expanded in AsmPrinter.

With separate auth and sign intrinsics, it is possible that intermediate
authenticated pointer is spilled to stack and reloaded before it is
signed again, thus making it possible for the attacker to trick the PAC*
instruction into signing an arbitrary pointer.
---
 .../InstCombine/InstCombineCalls.cpp          | 23 +++++++++++++++++++
 .../InstCombine/ptrauth-intrinsics.ll         | 12 ++++------
 2 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 38519d81fce8d..cda9db0db16d4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -2992,6 +2992,29 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
         Intrinsic::getOrInsertDeclaration(II->getModule(), NewIntrin);
     return CallInst::Create(NewFn, CallArgs);
   }
+  case Intrinsic::ptrauth_sign: {
+    // auth + sign can be replaced with resign, which prevents unsafe
+    // spills and reloads of intermediate authenticated value.
+    Value *Ptr = II->getArgOperand(0);
+    Value *SignKey = II->getArgOperand(1);
+    Value *SignDisc = II->getArgOperand(2);
+
+    const auto *CI = dyn_cast<CallBase>(Ptr);
+    if (!CI || CI->getIntrinsicID() != Intrinsic::ptrauth_auth)
+      break;
+
+    Value *BasePtr = CI->getOperand(0);
+    Value *AuthKey = CI->getArgOperand(1);
+    Value *AuthDisc = CI->getArgOperand(2);
+
+    // Not replacing auth+sign using the same schema with nop, as auth+sign
+    // pair traps on authentication failure.
+
+    Function *NewFn = Intrinsic::getOrInsertDeclaration(
+        II->getModule(), Intrinsic::ptrauth_resign);
+    return CallInst::Create(NewFn,
+                            {BasePtr, AuthKey, AuthDisc, SignKey, SignDisc});
+  }
   case Intrinsic::arm_neon_vtbl1:
   case Intrinsic::aarch64_neon_tbl1:
     if (Value *V = simplifyNeonTbl1(*II, Builder))
diff --git a/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll b/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll
index fcb6221a8ebd5..15dd8bff19f2c 100644
--- a/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll
+++ b/llvm/test/Transforms/InstCombine/ptrauth-intrinsics.ll
@@ -163,8 +163,7 @@ define i64 @test_ptrauth_resign_ptrauth_constant(ptr %p) {
 define i64 @test_ptrauth_auth_sign_same_schema(ptr %p) {
 ; CHECK-LABEL: @test_ptrauth_auth_sign_same_schema(
 ; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
-; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 1, i64 1234)
-; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 1234)
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[P_INT]], i32 1, i64 1234, i32 1, i64 1234)
 ; CHECK-NEXT:    ret i64 [[RESIGNED]]
 ;
   %p.int = ptrtoint ptr %p to i64
@@ -176,8 +175,7 @@ define i64 @test_ptrauth_auth_sign_same_schema(ptr %p) {
 define i64 @test_ptrauth_auth_sign_opaque_disc_same_schema(ptr %p, i64 %disc) {
 ; CHECK-LABEL: @test_ptrauth_auth_sign_opaque_disc_same_schema(
 ; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
-; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 1, i64 [[DISC:%.*]])
-; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 [[DISC]])
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[P_INT]], i32 1, i64 [[DISC:%.*]], i32 1, i64 [[DISC]])
 ; CHECK-NEXT:    ret i64 [[RESIGNED]]
 ;
   %p.int = ptrtoint ptr %p to i64
@@ -189,8 +187,7 @@ define i64 @test_ptrauth_auth_sign_opaque_disc_same_schema(ptr %p, i64 %disc) {
 define i64 @test_ptrauth_auth_sign_different_disc(ptr %p, i64 %disc) {
 ; CHECK-LABEL: @test_ptrauth_auth_sign_different_disc(
 ; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
-; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 1, i64 [[DISC:%.*]])
-; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 1234)
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[P_INT]], i32 1, i64 [[DISC:%.*]], i32 1, i64 1234)
 ; CHECK-NEXT:    ret i64 [[RESIGNED]]
 ;
   %p.int = ptrtoint ptr %p to i64
@@ -202,8 +199,7 @@ define i64 @test_ptrauth_auth_sign_different_disc(ptr %p, i64 %disc) {
 define i64 @test_ptrauth_auth_sign_different_key(ptr %p) {
 ; CHECK-LABEL: @test_ptrauth_auth_sign_different_key(
 ; CHECK-NEXT:    [[P_INT:%.*]] = ptrtoint ptr [[P:%.*]] to i64
-; CHECK-NEXT:    [[AUTHED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[P_INT]], i32 0, i64 1234)
-; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[AUTHED]], i32 1, i64 1234)
+; CHECK-NEXT:    [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[P_INT]], i32 0, i64 1234, i32 1, i64 1234)
 ; CHECK-NEXT:    ret i64 [[RESIGNED]]
 ;
   %p.int = ptrtoint ptr %p to i64

>From 06d0e148f90afd420140c9e32ba8130845b9257a Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Sat, 15 Mar 2025 12:55:06 +0300
Subject: [PATCH 3/3] Comment: explain the reason to combine auth+sign

---
 llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index cda9db0db16d4..d5f9b02e2a065 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -2993,8 +2993,13 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     return CallInst::Create(NewFn, CallArgs);
   }
   case Intrinsic::ptrauth_sign: {
-    // auth + sign can be replaced with resign, which prevents unsafe
-    // spills and reloads of intermediate authenticated value.
+    // Replace auth+sign with a single resign intrinsic.
+    // When auth and sign operations are performed separately, later compiler
+    // passes may spill intermediate result to memory as a raw, unprotected
+    // pointer, which makes it possible for an attacker to replace it under
+    // PAuth threat model. On the other hand, resign intrinsic is not expanded
+    // until AsmPrinter, when it is emitted as a contiguous, non-attackable
+    // sequence of instructions.
     Value *Ptr = II->getArgOperand(0);
     Value *SignKey = II->getArgOperand(1);
     Value *SignDisc = II->getArgOperand(2);



More information about the llvm-commits mailing list