[llvm] [X86] Match ENDBR immediates in little-endian order (PR #208754)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 25 07:39:33 PDT 2026


https://github.com/BIGSMATER updated https://github.com/llvm/llvm-project/pull/208754

>From 430e72bda3a7a1c036c4e76499013cb8cc404e13 Mon Sep 17 00:00:00 2001
From: BIGSMATER <1025076763 at qq.com>
Date: Fri, 10 Jul 2026 14:09:22 +0800
Subject: [PATCH] [X86] Match ENDBR immediates in little-endian order

The CET immediate scrubber checks constants that would place ENDBR32 or ENDBR64 byte sequences into executable code. Match those sequences in the order emitted for little-endian immediate operands instead of the display-order integer spelling.

Treat unsupported immediate widths as non-matches and assert the ISD::Constant type invariant. Update the X86 CET regression test to cover ENDBR32 and optional-prefix encodings.
---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp       | 65 ++++++++++++-------
 .../test/CodeGen/X86/cet_endbr_imm_enhance.ll | 59 ++++++++---------
 2 files changed, 68 insertions(+), 56 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index c3fc43c2bc6db..5fd070bb3e743 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -933,22 +933,31 @@ static bool isCalleeLoad(SDValue Callee, SDValue &Chain, bool HasCallSeq) {
   }
 }
 
-static bool isEndbrImm64(uint64_t Imm) {
-// There may be some other prefix bytes between 0xF3 and 0x0F1EFA.
-// i.g: 0xF3660F1EFA, 0xF3670F1EFA
-  if ((Imm & 0x00FFFFFF) != 0x0F1EFA)
+static bool isEndbrImm(uint64_t Imm, unsigned BitWidth) {
+  if (BitWidth > 64 || BitWidth % 8 != 0)
     return false;
 
-  uint8_t OptionalPrefixBytes [] = {0x26, 0x2e, 0x36, 0x3e, 0x64,
-                                    0x65, 0x66, 0x67, 0xf0, 0xf2};
-  int i = 24; // 24bit 0x0F1EFA has matched
-  while (i < 64) {
-    uint8_t Byte = (Imm >> i) & 0xFF;
-    if (Byte == 0xF3)
+  const unsigned NumBytes = BitWidth / 8;
+  if (NumBytes < 4)
+    return false;
+
+  const uint8_t OptionalPrefixBytes[] = {0x26, 0x2e, 0x36, 0x3e, 0x64,
+                                         0x65, 0x66, 0x67, 0xf0, 0xf2};
+  uint8_t Bytes[8];
+  for (unsigned I = 0; I != NumBytes; ++I)
+    Bytes[I] = (Imm >> (I * 8)) & 0xFF;
+
+  for (unsigned I = 0; I + 3 < NumBytes; ++I) {
+    if (Bytes[I] != 0xf3)
+      continue;
+
+    unsigned J = I + 1;
+    while (J < NumBytes && llvm::is_contained(OptionalPrefixBytes, Bytes[J]))
+      ++J;
+
+    if (J + 2 < NumBytes && Bytes[J] == 0x0f && Bytes[J + 1] == 0x1e &&
+        (Bytes[J + 2] == 0xfa || Bytes[J + 2] == 0xfb))
       return true;
-    if (!llvm::is_contained(OptionalPrefixBytes, Byte))
-      return false;
-    i += 8;
   }
 
   return false;
@@ -969,28 +978,34 @@ void X86DAGToDAGISel::PreprocessISelDAG() {
     // ENDBR32 and ENDBR64 have specific opcodes:
     // ENDBR32: F3 0F 1E FB
     // ENDBR64: F3 0F 1E FA
-    // And we want that attackers won’t find unintended ENDBR32/64
-    // opcode matches in the binary
-    // Here’s an example:
+    // We want to prevent attackers from finding unintended ENDBR32/64 opcode
+    // matches in executable code. Here's an example:
     // If the compiler had to generate asm for the following code:
-    // a = 0xF30F1EFA
+    // a = 0xFA1E0FF3
     // it could, for example, generate:
-    // mov 0xF30F1EFA, dword ptr[a]
-    // In such a case, the binary would include a gadget that starts
-    // with a fake ENDBR64 opcode. Therefore, we split such generation
-    // into multiple operations, let it not shows in the binary
+    // mov 0xFA1E0FF3, dword ptr[a]
+    // In such a case, the binary would include a gadget that starts with a
+    // fake ENDBR64 opcode. Split such constants into multiple operations so
+    // the byte sequence does not appear in executable code.
     if (N->getOpcode() == ISD::Constant) {
       MVT VT = N->getSimpleValueType(0);
-      int64_t Imm = cast<ConstantSDNode>(N)->getSExtValue();
-      int32_t EndbrImm = Subtarget->is64Bit() ? 0xF30F1EFA : 0xF30F1EFB;
-      if (Imm == EndbrImm || isEndbrImm64(Imm)) {
+      assert(VT.isScalarInteger() &&
+             "ISD::Constant must have a scalar integer type");
+      if (!VT.isScalarInteger() || VT.getSizeInBits() > 64)
+        continue;
+
+      uint64_t Imm = cast<ConstantSDNode>(N)->getZExtValue();
+      if (isEndbrImm(Imm, VT.getSizeInBits())) {
         // Check that the cf-protection-branch is enabled.
         Metadata *CFProtectionBranch =
             MF->getFunction().getParent()->getModuleFlag(
                 "cf-protection-branch");
         if (CFProtectionBranch || IndirectBranchTracking) {
           SDLoc dl(N);
-          SDValue Complement = CurDAG->getConstant(~Imm, dl, VT, false, true);
+          uint64_t ComplementImm =
+              (~Imm) & maskTrailingOnes<uint64_t>(VT.getSizeInBits());
+          SDValue Complement =
+              CurDAG->getConstant(ComplementImm, dl, VT, false, true);
           Complement = CurDAG->getNOT(dl, Complement, VT);
           --I;
           CurDAG->ReplaceAllUsesOfValueWith(SDValue(N, 0), Complement);
diff --git a/llvm/test/CodeGen/X86/cet_endbr_imm_enhance.ll b/llvm/test/CodeGen/X86/cet_endbr_imm_enhance.ll
index 98d315ad14e68..6435f66cf7549 100644
--- a/llvm/test/CodeGen/X86/cet_endbr_imm_enhance.ll
+++ b/llvm/test/CodeGen/X86/cet_endbr_imm_enhance.ll
@@ -6,20 +6,22 @@
 ; ENDBR32 and ENDBR64 have specific opcodes:
 ; ENDBR32: F3 0F 1E FB
 ; ENDBR64: F3 0F 1E FA
-; And we want that attackers won’t find unintended ENDBR32/64
-; opcode matches in the binary
-; Here’s an example:
+; We want to prevent attackers from finding unintended ENDBR32/64 opcode
+; matches in executable code. Here's an example:
 ; If the compiler had to generate asm for the following code:
-; a = 0xF30F1EFA
+; a = 0xFA1E0FF3
 ; it could, for example, generate:
-; mov 0xF30F1EFA, dword ptr[a]
-; In such a case, the binary would include a gadget that starts
-; with a fake ENDBR64 opcode. Therefore, we split such generation
-; into multiple operations, let it not shows in the binary.
+; mov 0xFA1E0FF3, dword ptr[a]
+; In such a case, the binary would include a gadget that starts with a fake
+; ENDBR64 opcode. Split such constants into multiple operations so the byte
+; sequence does not appear in executable code.
 
-; 0xF30F1EFA == -217112838  ~0xF30F1EFA == 217112837 (0xCF0E105)
-; 0x000123F32E0F1EFA == 321002333478650
-; ~0x000123F32E0F1EFA == -321002333478651 (0XFFFEDC0CD1F0E105)
+; 0xFA1E0FF3 == -98693133  ~0xFA1E0FF3 == 98693132 (0x5E1F00C)
+; 0xFB1E0FF3 == -81915917  ~0xFB1E0FF3 == 81915916 (0x4E1F00C)
+; 0x000123FA1E0FF300 == 321032129868544
+; ~0x000123FA1E0FF300 == -321032129868545 (0xFFFEDC05E1F00CFF)
+; 0x0000FA1E0F6766F3 == 275007014397683
+; ~0x0000FA1E0F6766F3 == -275007014397684 (0xFFFF05E1F098990C)
 
 ; test for MOV64ri
 define dso_local i64 @foo(ptr %azx) #0 {
@@ -27,7 +29,7 @@ define dso_local i64 @foo(ptr %azx) #0 {
 ; CHECK:       # %bb.0: # %entry
 ; CHECK-NEXT:    endbr64
 ; CHECK-NEXT:    movq %rdi, -{{[0-9]+}}(%rsp)
-; CHECK-NEXT:    movabsq $-321002333478651, %rax # imm = 0xFFFEDC0CD1F0E105
+; CHECK-NEXT:    movabsq $-321032129868545, %rax # imm = 0xFFFEDC05E1F00CFF
 ; CHECK-NEXT:    notq %rax
 ; CHECK-NEXT:    andq %rax, (%rdi)
 ; CHECK-NEXT:    movq -{{[0-9]+}}(%rsp), %rax
@@ -38,7 +40,7 @@ entry:
   store ptr %azx, ptr %azx.addr, align 8
   %0 = load ptr, ptr %azx.addr, align 8
   %1 = load i64, ptr %0, align 8
-  %and = and i64 %1, 321002333478650
+  %and = and i64 %1, 321032129868544
   %2 = load ptr, ptr %azx.addr, align 8
   store i64 %and, ptr %2, align 8
   %3 = load ptr, ptr %azx.addr, align 8
@@ -46,42 +48,36 @@ entry:
   ret i64 %4
 }
 
- at bzx = dso_local local_unnamed_addr global i32 -217112837, align 4
-
 ; test for AND32ri
-define dso_local i32 @foo2() local_unnamed_addr #0 {
+define dso_local i32 @foo2(i32 %x) local_unnamed_addr #0 {
 ; CHECK-LABEL: foo2:
 ; CHECK:       # %bb.0: # %entry
 ; CHECK-NEXT:    endbr64
-; CHECK-NEXT:    movl bzx(%rip), %ecx
-; CHECK-NEXT:    addl %ecx, %ecx
-; CHECK-NEXT:    movl $217112837, %eax # imm = 0xCF0E105
+; CHECK-NEXT:    movl $98693132, %eax # imm = 0x5E1F00C
 ; CHECK-NEXT:    notl %eax
-; CHECK-NEXT:    andl %ecx, %eax
+; CHECK-NEXT:    andl %edi, %eax
 ; CHECK-NEXT:    retq
 entry:
-  %0 = load i32, ptr @bzx, align 4
-  %mul = shl nsw i32 %0, 1
-  %and = and i32 %mul, -217112838
+  %and = and i32 %x, -98693133
   ret i32 %and
 }
 
 
- at czx = dso_local global i32 -217112837, align 4
+ at czx = dso_local global i32 0, align 4
 
-; test for AND32mi
+; test for ENDBR32 in AND32mi
 define dso_local nonnull ptr @foo3() local_unnamed_addr #0 {
 ; CHECK-LABEL: foo3:
 ; CHECK:       # %bb.0: # %entry
 ; CHECK-NEXT:    endbr64
-; CHECK-NEXT:    movl $217112837, %eax # imm = 0xCF0E105
+; CHECK-NEXT:    movl $81915916, %eax # imm = 0x4E1F00C
 ; CHECK-NEXT:    notl %eax
 ; CHECK-NEXT:    andl %eax, czx(%rip)
 ; CHECK-NEXT:    movl $czx, %eax
 ; CHECK-NEXT:    retq
 entry:
   %0 = load i32, ptr @czx, align 4
-  %and = and i32 %0, -217112838
+  %and = and i32 %0, -81915917
   store i32 %and, ptr @czx, align 4
   ret ptr @czx
 }
@@ -91,28 +87,29 @@ define dso_local i32 @foo4() #0 {
 ; CHECK-LABEL: foo4:
 ; CHECK:       # %bb.0: # %entry
 ; CHECK-NEXT:    endbr64
-; CHECK-NEXT:    movl $217112837, %eax # imm = 0xCF0E105
+; CHECK-NEXT:    movl $98693132, %eax # imm = 0x5E1F00C
 ; CHECK-NEXT:    notl %eax
 ; CHECK-NEXT:    movl %eax, -{{[0-9]+}}(%rsp)
 ; CHECK-NEXT:    retq
 entry:
   %dzx = alloca i32, align 4
-  store i32 -217112838, ptr %dzx, align 4
+  store i32 -98693133, ptr %dzx, align 4
   %0 = load i32, ptr %dzx, align 4
   ret i32 %0
 }
 
+; test for optional prefixes in a 64-bit immediate
 define dso_local i64 @foo5() #0 {
 ; CHECK-LABEL: foo5:
 ; CHECK:       # %bb.0: # %entry
 ; CHECK-NEXT:    endbr64
-; CHECK-NEXT:    movabsq $-4077854459, %rax # imm = 0xFFFFFFFF0CF0E105
+; CHECK-NEXT:    movabsq $-275007014397684, %rax # imm = 0xFFFF05E1F098990C
 ; CHECK-NEXT:    notq %rax
 ; CHECK-NEXT:    movq %rax, -{{[0-9]+}}(%rsp)
 ; CHECK-NEXT:    retq
 entry:
   %ezx = alloca i64, align 8
-  store i64 4077854458, ptr %ezx, align 8
+  store i64 275007014397683, ptr %ezx, align 8
   %0 = load i64, ptr %ezx, align 8
   ret i64 %0
 }



More information about the llvm-commits mailing list