From 985a2552f125c464937f309e4a08b695c4d6a801 Mon Sep 17 00:00:00 2001 From: Swapnil Gaikwad Date: Wed, 11 Mar 2026 16:37:48 +0000 Subject: [PATCH 1/5] Arm64: [PAC-RET] Add Pointer Authentication support for Arm64 (Part 1/3: JIT changes) As suggested in [comment](https://github.com/dotnet/runtime/pull/125436#issuecomment-4379363931), this PR covers subset of changes from #125436 related to the JIT. This PR adds support for Pointer Authentication (PAC) on Arm64. Pointer Authentication (PAC) is an Armv8.3+ security feature designed to mitigate Return-Oriented Programming (ROP) attacks by cryptographically signing return addresses. While using PAC, we store a signed return address, instead of the plain address, on the stack and later authenticate it before returning from a function. It ensures control flow returns to the intended caller. More details on PAC and its role in software security can be found ([here](https://llsoftsec.github.io/llsoftsecbook/#sec:pointer-authentication)). - The current implementation of PAC is turned off by default, but can be turned on by setting DOTNET_JitPacEnabled=1. - PAC protects link register (LR) by signing it in the prolog (using `paciasp`) before it is split, using the current SP as the modifier. It then authenticates the LR in the epilog (using `autiasp`) before the function returns. If the signature is invalid, the execution fails with `SIGILL`. - Changes are limited to JIT. --- src/coreclr/inc/cfi.h | 3 ++- src/coreclr/inc/clrconfigvalues.h | 1 + src/coreclr/jit/codegenarm64.cpp | 33 +++++++++++++++++++++++++++++- src/coreclr/jit/codegenarmarch.cpp | 5 +++++ src/coreclr/jit/compiler.h | 1 + src/coreclr/jit/emit.h | 5 +++++ src/coreclr/jit/emitarm64.cpp | 26 +++++++++++++++++++++++ src/coreclr/jit/jitconfigvalues.h | 1 + src/coreclr/jit/unwind.cpp | 5 +++++ src/coreclr/jit/unwindarm64.cpp | 33 ++++++++++++++++++++++++++++++ 10 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/coreclr/inc/cfi.h b/src/coreclr/inc/cfi.h index 3d7ec0f4cc11f8..3c8e3d05c04980 100644 --- a/src/coreclr/inc/cfi.h +++ b/src/coreclr/inc/cfi.h @@ -9,7 +9,8 @@ enum CFI_OPCODE { CFI_ADJUST_CFA_OFFSET, // Offset is adjusted relative to the current one. CFI_DEF_CFA_REGISTER, // New register is used to compute CFA - CFI_REL_OFFSET // Register is saved at offset from the current CFA + CFI_REL_OFFSET, // Register is saved at offset from the current CFA + CFI_NEGATE_RA_STATE, // Sign the return address in lr with paciasp }; struct CFI_CODE diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index c9dd7c485c99c0..86875b43594b31 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -710,6 +710,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Sm4, W("EnableArm64Sm RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveAes, W("EnableArm64SveAes"), 1, "Allows Arm64 SveAes+ hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveSha3, W("EnableArm64SveSha3"), 1, "Allows Arm64 SveSha3+ hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveSm4, W("EnableArm64SveSm4"), 1, "Allows Arm64 SveSm4+ hardware intrinsics to be disabled") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_JitPacEnabled, W("JitPacEnabled"), 0, "Allows Arm64 Pointer Authentication (PAC) to be disabled") #elif defined(TARGET_RISCV64) RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zba, W("EnableRiscV64Zba"), 1, "Allows RiscV64 Zba hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64Zbb"), 1, "Allows RiscV64 Zbb hardware intrinsics to be disabled") diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 47ecfbea7dc7de..72d96f9365a9e6 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -251,6 +251,11 @@ void CodeGen::genPopCalleeSavedRegistersAndFreeLclFrame(bool jmpEpilog) } } + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInEpilog(); + } + // For OSR, we must also adjust the SP to remove the Tier0 frame. // if (m_compiler->opts.IsOSR()) @@ -487,9 +492,10 @@ void CodeGen::genPrologSaveRegPair(regNumber reg1, if ((spOffset == 0) && (spDelta >= -512)) { - // We can use pre-indexed addressing. + // We can use pre-indexed addressing when the stack adjustment fits in the instruction. // stp REG, REG + 1, [SP, #spDelta]! // 64-bit STP offset range: -512 to 504, multiple of 8. + assert(reg1 != REG_LR); GetEmitter()->emitIns_R_R_R_I(INS_stp, EA_PTRSIZE, reg1, reg2, REG_SPBASE, spDelta, INS_OPTS_PRE_INDEX); m_compiler->unwindSaveRegPairPreindexed(reg1, reg2, spDelta); @@ -511,6 +517,8 @@ void CodeGen::genPrologSaveRegPair(regNumber reg1, // 64-bit STP offset range: -512 to 504, multiple of 8. assert(spOffset <= 504); assert((spOffset % 8) == 0); + assert(reg1 != REG_LR); + GetEmitter()->emitIns_R_R_R_I(INS_stp, EA_PTRSIZE, reg1, reg2, REG_SPBASE, spOffset); if (TargetOS::IsUnix && m_compiler->generateCFIUnwindCodes()) @@ -622,6 +630,7 @@ void CodeGen::genRestoreRegPair(regNumber reg1, assert((spDelta % 16) == 0); // SP changes must be 16-byte aligned assert(genIsValidFloatReg(reg1) == genIsValidFloatReg(reg2)); // registers must be both general-purpose, or both // FP/SIMD + assert(reg1 != REG_LR); if (spDelta != 0) { @@ -1384,6 +1393,11 @@ void CodeGen::genFuncletProlog(BasicBlock* block) m_compiler->unwindBegProlog(); + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInProlog(); + } + regMaskTP maskSaveRegsFloat = genFuncletInfo.fiSaveRegs & RBM_ALLFLOAT; regMaskTP maskSaveRegsInt = genFuncletInfo.fiSaveRegs & ~maskSaveRegsFloat; @@ -1669,6 +1683,11 @@ void CodeGen::genFuncletEpilog(BasicBlock* /* block */) } } + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInEpilog(); + } + inst_RV(INS_ret, REG_LR, TYP_I_IMPL); m_compiler->unwindReturn(REG_LR); @@ -5675,6 +5694,18 @@ void CodeGen::genOSRHandleTier0CalleeSavedRegistersAndFrame() genRestoreRegPair(REG_FP, REG_LR, REG_FP, 0, 0, false, REG_IP1, nullptr, /* reportUnwindData */ false); + if (JitConfig.JitPacEnabled() != 0) + { + // Tier0 signed LR with the Tier0 caller SP before allocating its frame. + // Recreate that SP from the current Tier0 body SP so we can authenticate + // LR before the OSR prolog later re-signs it with the OSR SP via PACIASP. + genInstrWithConstant(INS_add, EA_PTRSIZE, REG_IP0, REG_SPBASE, patchpointInfo->TotalFrameSize(), REG_IP0, + /* inUnwindRegion */ false); + GetEmitter()->emitIns_Mov(INS_mov, EA_PTRSIZE, REG_IP1, REG_LR, /* canSkip */ false); + GetEmitter()->emitIns(INS_autia1716); + GetEmitter()->emitIns_Mov(INS_mov, EA_PTRSIZE, REG_LR, REG_IP1, /* canSkip */ false); + } + // Emit phantom unwind data for the tier0 frame. m_compiler->unwindAllocStack(patchpointInfo->TotalFrameSize()); // Emit nops to make the prolog 1:1 in unwind codes to instructions. This diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 74d8148f720653..192c305b9e2583 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -4476,6 +4476,11 @@ void CodeGen::genPushCalleeSavedRegisters(regNumber initReg, bool* pInitRegZeroe } #endif // DEBUG + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInProlog(); + } + // The frameType number is arbitrary, is defined below, and corresponds to one of the frame styles we // generate based on various sizes. int frameType = 0; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 7f6426b6b7bf31..5dff6f76db6109 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9563,6 +9563,7 @@ class Compiler void unwindSaveRegPair(regNumber reg1, regNumber reg2, int offset); // stp reg1, reg2, [sp, #offset] void unwindSaveRegPairPreindexed(regNumber reg1, regNumber reg2, int offset); // stp reg1, reg2, [sp, #offset]! void unwindSaveNext(); // unwind code: save_next + void unwindPacSignLR(); // unwind code: pac_sign_lr void unwindReturn(regNumber reg); // ret lr #endif // defined(TARGET_ARM64) diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index d871a91cd8a516..f2960afeda6e1e 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -3347,6 +3347,11 @@ class emitter instrDescAlign* emitNewInstrAlign(); #endif +#if defined(TARGET_ARM64) + void emitPacInProlog(); + void emitPacInEpilog(); +#endif + instrDesc* emitNewInstrSmall(emitAttr attr); instrDesc* emitNewInstr(emitAttr attr = EA_4BYTE); instrDesc* emitNewInstrSC(emitAttr attr, cnsval_ssize_t cns); diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 250af9a94d9213..ff2ebd7a7fb126 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -1431,6 +1431,32 @@ static const char * const bRegNames[] = // clang-format on +//------------------------------------------------------------------------ +// emitPacInProlog: Sign LR as part of Pointer Authentication (PAC) support +// +void emitter::emitPacInProlog() +{ + if (JitConfig.JitPacEnabled() == 0) + { + return; + } + emitIns(INS_paciasp); + m_compiler->unwindPacSignLR(); +} + +//------------------------------------------------------------------------ +// emitPacInEpilog: unsign LR as part of Pointer Authentication (PAC) support +// +void emitter::emitPacInEpilog() +{ + if (JitConfig.JitPacEnabled() == 0) + { + return; + } + emitIns(INS_autiasp); + m_compiler->unwindPacSignLR(); +} + //------------------------------------------------------------------------ // emitRegName: Returns a general-purpose register name or SIMD and floating-point scalar register name. // diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index a87b20f8491c78..1c2531e4834413 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -126,6 +126,7 @@ CONFIG_STRING(JitInlineMethodsWithEHRange, "JitInlineMethodsWithEHRange") CONFIG_INTEGER(JitLongAddress, "JitLongAddress", 0) // Force using the large pseudo instruction form for long address CONFIG_INTEGER(JitMaxUncheckedOffset, "JitMaxUncheckedOffset", 8) +RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 0) // Enable devirtualization for generic virtual methods RELEASE_CONFIG_INTEGER(JitEnableGenericVirtualDevirtualization, "JitEnableGenericVirtualDevirtualization", 1) diff --git a/src/coreclr/jit/unwind.cpp b/src/coreclr/jit/unwind.cpp index 1b4d454e142326..97d819e04cb379 100644 --- a/src/coreclr/jit/unwind.cpp +++ b/src/coreclr/jit/unwind.cpp @@ -398,6 +398,11 @@ void Compiler::DumpCfiInfo(bool isHotCode, assert(dwarfReg == DWARF_REG_ILLEGAL); printf(" CodeOffset: 0x%02X Op: AdjustCfaOffset Offset:0x%X\n", codeOffset, offset); break; + case CFI_NEGATE_RA_STATE: + assert(dwarfReg == DWARF_REG_ILLEGAL); + assert(offset == 0); + printf(" CodeOffset: 0x%02X Op: NegateRAState\n", codeOffset); + break; default: printf(" Unrecognized CFI_CODE: 0x%llX\n", *(UINT64*)pCode); break; diff --git a/src/coreclr/jit/unwindarm64.cpp b/src/coreclr/jit/unwindarm64.cpp index f842737171c0b4..f5696b1aa841b4 100644 --- a/src/coreclr/jit/unwindarm64.cpp +++ b/src/coreclr/jit/unwindarm64.cpp @@ -635,6 +635,33 @@ void Compiler::unwindSaveNext() pu->AddCode(0xE6); } +void Compiler::unwindPacSignLR() +{ + if (JitConfig.JitPacEnabled() == 0) + { + return; + } +#if defined(FEATURE_CFI_SUPPORT) + if (generateCFIUnwindCodes()) + { + // Emit NEGATE_RA_STATE opcode in prologs. + if (!compGeneratingProlog) + { + return; + } + FuncInfoDsc* func = funCurrentFunc(); + UNATIVE_OFFSET cbProlog = unwindGetCurrentOffset(func); + // Maps to DW_CFA_AARCH64_negate_ra_state + createCfiCode(func, cbProlog, CFI_NEGATE_RA_STATE, DWARF_REG_ILLEGAL); + + return; + } +#endif // FEATURE_CFI_SUPPORT + + // pac_sign_lr: 11111100: sign the return address in lr with paciasp + funCurrentFunc()->uwi.AddCode(0xFC); +} + void Compiler::unwindReturn(regNumber reg) { // Nothing to do; we will always have at least one trailing "end" opcode in our padding. @@ -1081,6 +1108,12 @@ void DumpUnwindInfo(Compiler* comp, printf(" %02X save_next\n", b1); } + else if (b1 == 0xFC) + { + // pac_sign_lr: 11111100 : sign the return address in lr with paciasp. + + printf(" %02X pac_sign_lr\n", b1); + } else { // Unknown / reserved unwind code From 045973a40c0ba3e6df9609307de21389db7e097d Mon Sep 17 00:00:00 2001 From: Swapnil Gaikwad Date: Fri, 8 May 2026 11:13:50 +0100 Subject: [PATCH 2/5] Use B key on Windows and A key otherwise for PAC --- src/coreclr/inc/clrconfigvalues.h | 1 - src/coreclr/jit/codegenarm64.cpp | 4 ++-- src/coreclr/jit/codegenarm64test.cpp | 5 +++++ src/coreclr/jit/emitarm64.cpp | 12 +++++++++--- src/coreclr/jit/emitfmtsarm64.h | 2 +- src/coreclr/jit/instrsarm64.h | 15 +++++++++++++++ src/coreclr/jit/unwindarm64.cpp | 4 ++-- 7 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 86875b43594b31..c9dd7c485c99c0 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -710,7 +710,6 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Sm4, W("EnableArm64Sm RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveAes, W("EnableArm64SveAes"), 1, "Allows Arm64 SveAes+ hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveSha3, W("EnableArm64SveSha3"), 1, "Allows Arm64 SveSha3+ hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveSm4, W("EnableArm64SveSm4"), 1, "Allows Arm64 SveSm4+ hardware intrinsics to be disabled") -RETAIL_CONFIG_DWORD_INFO(EXTERNAL_JitPacEnabled, W("JitPacEnabled"), 0, "Allows Arm64 Pointer Authentication (PAC) to be disabled") #elif defined(TARGET_RISCV64) RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zba, W("EnableRiscV64Zba"), 1, "Allows RiscV64 Zba hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64Zbb"), 1, "Allows RiscV64 Zbb hardware intrinsics to be disabled") diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 72d96f9365a9e6..5d952cdf186916 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -5698,11 +5698,11 @@ void CodeGen::genOSRHandleTier0CalleeSavedRegistersAndFrame() { // Tier0 signed LR with the Tier0 caller SP before allocating its frame. // Recreate that SP from the current Tier0 body SP so we can authenticate - // LR before the OSR prolog later re-signs it with the OSR SP via PACIASP. + // LR before the OSR prolog later re-signs it with the OSR SP. genInstrWithConstant(INS_add, EA_PTRSIZE, REG_IP0, REG_SPBASE, patchpointInfo->TotalFrameSize(), REG_IP0, /* inUnwindRegion */ false); GetEmitter()->emitIns_Mov(INS_mov, EA_PTRSIZE, REG_IP1, REG_LR, /* canSkip */ false); - GetEmitter()->emitIns(INS_autia1716); + GetEmitter()->emitIns(TargetOS::IsWindows ? INS_autib1716 : INS_autia1716); GetEmitter()->emitIns_Mov(INS_mov, EA_PTRSIZE, REG_LR, REG_IP1, /* canSkip */ false); } diff --git a/src/coreclr/jit/codegenarm64test.cpp b/src/coreclr/jit/codegenarm64test.cpp index 10b39a53c10e60..3ca2b4b13e1ecb 100644 --- a/src/coreclr/jit/codegenarm64test.cpp +++ b/src/coreclr/jit/codegenarm64test.cpp @@ -9143,10 +9143,15 @@ void CodeGen::genArm64EmitterUnitTestsPac() // IF_PC_0A theEmitter->emitIns(INS_autia1716); // AUTIA1716 theEmitter->emitIns(INS_autiasp); // AUTIASP + theEmitter->emitIns(INS_autib1716); // AUTIB1716 + theEmitter->emitIns(INS_autibsp); // AUTIBSP theEmitter->emitIns(INS_autiaz); // AUTIAZ theEmitter->emitIns(INS_pacia1716); // PACIA1716 theEmitter->emitIns(INS_paciasp); // PACIASP theEmitter->emitIns(INS_paciaz); // PACIAZ + theEmitter->emitIns(INS_pacib1716); // PACIB1716 + theEmitter->emitIns(INS_pacibsp); // PACIBSP + theEmitter->emitIns(INS_pacibz); // PACIBZ theEmitter->emitIns(INS_xpaclri); // XPACLRI // IF_PC_1A diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 5c882d026535d3..630f15e7040089 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -1440,7 +1440,7 @@ void emitter::emitPacInProlog() { return; } - emitIns(INS_paciasp); + emitIns(TargetOS::IsWindows ? INS_pacibsp : INS_paciasp); m_compiler->unwindPacSignLR(); } @@ -1453,7 +1453,7 @@ void emitter::emitPacInEpilog() { return; } - emitIns(INS_autiasp); + emitIns(TargetOS::IsWindows ? INS_autibsp : INS_autiasp); m_compiler->unwindPacSignLR(); } @@ -3756,9 +3756,14 @@ void emitter::emitIns(instruction ins) case INS_autia1716: case INS_autiasp: case INS_autiaz: + case INS_autib1716: + case INS_autibsp: case INS_pacia1716: case INS_paciasp: case INS_paciaz: + case INS_pacib1716: + case INS_pacibsp: + case INS_pacibz: case INS_xpaclri: assert(fmt == IF_PC_0A); break; @@ -16229,7 +16234,8 @@ emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(ins } break; - case IF_PC_0A: // autia1716, autiasp, autiaz, pacia1716, paciasp, paciaz, xpaclri + case IF_PC_0A: // autia1716, autiasp, autib1716, autibsp, autiaz, pacia1716, paciasp, pacib1716, pacibsp, + // pacibz, paciaz, xpaclri case IF_PC_1A: // autiza, paciza, xpacd, xpaci case IF_PC_2A: // autia, pacia switch (ins) diff --git a/src/coreclr/jit/emitfmtsarm64.h b/src/coreclr/jit/emitfmtsarm64.h index b710230609a8df..1a042b461ee72a 100644 --- a/src/coreclr/jit/emitfmtsarm64.h +++ b/src/coreclr/jit/emitfmtsarm64.h @@ -232,7 +232,7 @@ IF_DEF(SI_0A, IS_NONE, NONE) // SI_0A ...........iiiii iiiiiiiiiii..... IF_DEF(SI_0B, IS_NONE, NONE) // SI_0B ................ ....bbbb........ imm4 - barrier // Pointer Authentication (PAC) groups -IF_DEF(PC_0A, IS_NONE, NONE) // PC_0A ................ ................ (autia1716, autiasp, autiaz, pacia1716, paciasp, paciaz, xpaclri) +IF_DEF(PC_0A, IS_NONE, NONE) // PC_0A ................ ................ (autia1716, autiasp, autib1716, autibsp, autiaz, pacia1716, paciasp, pacib1716, pacibsp, pacibz, paciaz, xpaclri) IF_DEF(PC_1A, IS_NONE, NONE) // PC_1A ................ ...........ddddd Rd (autiza, paciza, xpacd, xpaci) IF_DEF(PC_2A, IS_NONE, NONE) // PC_2A X........X...... ......nnnnnddddd Rd Rn (autia, pacia) diff --git a/src/coreclr/jit/instrsarm64.h b/src/coreclr/jit/instrsarm64.h index 4f94424065bf05..fccdadc676364e 100644 --- a/src/coreclr/jit/instrsarm64.h +++ b/src/coreclr/jit/instrsarm64.h @@ -1593,6 +1593,12 @@ INST1(autia1716, "autia1716", 0, IF_PC_0A, 0xD503219F) INST1(autiasp, "autiasp", 0, IF_PC_0A, 0xD50323BF) // autiasp PC_0A 1101010100000011 0010001110111111 D503 23BF +INST1(autib1716, "autib1716", 0, IF_PC_0A, 0xD50321DF) + // autib1716 PC_0A 1101010100000011 0010000111011111 D503 21DF + +INST1(autibsp, "autibsp", 0, IF_PC_0A, 0xD50323FF) + // autibsp PC_0A 1101010100000011 0010001111111111 D503 23FF + INST1(autiaz, "autiaz", 0, IF_PC_0A, 0xD503239F) // autiaz PC_0A 1101010100000011 0010001110011111 D503 239F @@ -1602,6 +1608,15 @@ INST1(pacia1716, "pacia1716 ", 0, IF_PC_0A, 0xD503211F) INST1(paciasp, "paciasp", 0, IF_PC_0A, 0xD503233F) // paciasp PC_0A 1101010100000011 0010001100111111 D503 233F +INST1(pacib1716, "pacib1716 ", 0, IF_PC_0A, 0xD503215F) + // pacib1716 PC_0A 1101010100000011 0010000101011111 D503 215F + +INST1(pacibsp, "pacibsp", 0, IF_PC_0A, 0xD503237F) + // pacibsp PC_0A 1101010100000011 0010001101111111 D503 237F + +INST1(pacibz, "pacibz", 0, IF_PC_0A, 0xD503235F) + // pacibz PC_0A 1101010100000011 0010001101011111 D503 235F + INST1(paciaz, "paciaz", 0, IF_PC_0A, 0xD503231F) // paciaz PC_0A 1101010100000011 0010001100011111 D503 231F diff --git a/src/coreclr/jit/unwindarm64.cpp b/src/coreclr/jit/unwindarm64.cpp index f5696b1aa841b4..620438ec644e5a 100644 --- a/src/coreclr/jit/unwindarm64.cpp +++ b/src/coreclr/jit/unwindarm64.cpp @@ -658,7 +658,7 @@ void Compiler::unwindPacSignLR() } #endif // FEATURE_CFI_SUPPORT - // pac_sign_lr: 11111100: sign the return address in lr with paciasp + // pac_sign_lr: 11111100: sign the return address in lr with the platform PAC key funCurrentFunc()->uwi.AddCode(0xFC); } @@ -1110,7 +1110,7 @@ void DumpUnwindInfo(Compiler* comp, } else if (b1 == 0xFC) { - // pac_sign_lr: 11111100 : sign the return address in lr with paciasp. + // pac_sign_lr: 11111100 : sign the return address in lr with the platform PAC key. printf(" %02X pac_sign_lr\n", b1); } From 0196a181b896774fdde7efd25016f643c34be993 Mon Sep 17 00:00:00 2001 From: Swapnil Gaikwad Date: Wed, 13 May 2026 12:00:58 +0100 Subject: [PATCH 3/5] Add todo to improve OSR handling with PAC --- src/coreclr/jit/codegenarm64.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 5d952cdf186916..b06de35e3a3d37 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -5699,6 +5699,8 @@ void CodeGen::genOSRHandleTier0CalleeSavedRegistersAndFrame() // Tier0 signed LR with the Tier0 caller SP before allocating its frame. // Recreate that SP from the current Tier0 body SP so we can authenticate // LR before the OSR prolog later re-signs it with the OSR SP. + // TODO-PAC: Avoid authenticating and re-signing so the signing SP will point to the start of the frame. It may + // require adding a phantom pac_sign_lr unwind code. genInstrWithConstant(INS_add, EA_PTRSIZE, REG_IP0, REG_SPBASE, patchpointInfo->TotalFrameSize(), REG_IP0, /* inUnwindRegion */ false); GetEmitter()->emitIns_Mov(INS_mov, EA_PTRSIZE, REG_IP1, REG_LR, /* canSkip */ false); From 91d56e3bfea7fa4ca831d05bce856876109c0159 Mon Sep 17 00:00:00 2001 From: Swapnil Gaikwad Date: Thu, 14 May 2026 15:23:46 +0100 Subject: [PATCH 4/5] Make DOTNET_JitPacEnabled flag Arm64 only --- src/coreclr/jit/codegenarmarch.cpp | 2 ++ src/coreclr/jit/jitconfigvalues.h | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 20d41c662be1e2..8f518b11c3cc91 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -4484,10 +4484,12 @@ void CodeGen::genPushCalleeSavedRegisters(regNumber initReg, bool* pInitRegZeroe } #endif // DEBUG +#if defined(TARGET_ARM64) if (JitConfig.JitPacEnabled() != 0) { GetEmitter()->emitPacInProlog(); } +#endif // TARGET_ARM64 // The frameType number is arbitrary, is defined below, and corresponds to one of the frame styles we // generate based on various sizes. diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 75bff3741f4da8..290515d40a04a3 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -126,7 +126,9 @@ CONFIG_STRING(JitInlineMethodsWithEHRange, "JitInlineMethodsWithEHRange") CONFIG_INTEGER(JitLongAddress, "JitLongAddress", 0) // Force using the large pseudo instruction form for long address CONFIG_INTEGER(JitMaxUncheckedOffset, "JitMaxUncheckedOffset", 8) -RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 0) +#if defined(TARGET_ARM64) +RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 1) +#endif // Enable devirtualization for generic virtual methods RELEASE_CONFIG_INTEGER(JitEnableGenericVirtualDevirtualization, "JitEnableGenericVirtualDevirtualization", 1) From 19f5ded08401b76481b45adfa08dd5a2295e036c Mon Sep 17 00:00:00 2001 From: Swapnil Gaikwad Date: Fri, 15 May 2026 10:30:17 +0100 Subject: [PATCH 5/5] Disable PAC by default --- src/coreclr/jit/jitconfigvalues.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 290515d40a04a3..6e36a55a67ae02 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -127,7 +127,7 @@ CONFIG_STRING(JitInlineMethodsWithEHRange, "JitInlineMethodsWithEHRange") CONFIG_INTEGER(JitLongAddress, "JitLongAddress", 0) // Force using the large pseudo instruction form for long address CONFIG_INTEGER(JitMaxUncheckedOffset, "JitMaxUncheckedOffset", 8) #if defined(TARGET_ARM64) -RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 1) +RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 0) #endif // Enable devirtualization for generic virtual methods