https://github.com/vhscampos updated https://github.com/llvm/llvm-project/pull/176171
>From 4f775ae42fbc2729697b32ed5b5310a86f9cbfc4 Mon Sep 17 00:00:00 2001 From: Victor Campos <[email protected]> Date: Tue, 13 Jan 2026 20:39:02 +0000 Subject: [PATCH 1/3] [Clang][AArch64] Command-line options for A-profile's Sign Return Address Hardening This patch introduces a new command-line option to enable the AArch64 A-profile's Sign Return Address Hardening. It also introduces a new function attribute with the same naming as the new command-line option. At the time of this patch, this new option enables the hardening against the PACMAN attack [1] using a load of the return address [2]. The new option, -mharden-pac-ret, can take one of two values: - none: disable hardening. (The default if the option is absent) - load-return-address: enables hardening using the mitigation based on load of return address. The corresponding function attribute takes the option and its possible values using the same naming. 1: https://pacmanattack.com 2: https://developer.arm.com/documentation/101754/0624/armclang-Reference/armclang-Command-line-Options/-mharden-pac-ret --- .../clang/Basic/DiagnosticDriverKinds.td | 2 + .../clang/Basic/DiagnosticSemaKinds.td | 5 +++ clang/include/clang/Basic/LangOptions.def | 2 + clang/include/clang/Basic/LangOptions.h | 12 ++++++ clang/include/clang/Basic/TargetInfo.h | 22 ++++++++++ clang/include/clang/Options/Options.td | 9 ++++ clang/lib/Basic/Targets/AArch64.cpp | 16 +++++++ clang/lib/Basic/Targets/AArch64.h | 2 + clang/lib/CodeGen/TargetInfo.cpp | 4 ++ clang/lib/CodeGen/Targets/AArch64.cpp | 3 ++ clang/lib/Driver/ToolChains/Clang.cpp | 22 +++++++++- clang/lib/Sema/SemaDeclAttr.cpp | 18 +++++++- .../aarch64-sign-return-address-harden.c | 13 ++++++ clang/test/Driver/aarch64-security-options.c | 30 +++++++++++++ clang/test/Driver/arm-security-options.c | 19 ++++++++ .../Frontend/aarch64-harden-pac-ret-err.c | 3 ++ .../aarch64-harden-pac-ret-attr-err-warn.c | 21 +++++++++ clang/test/Sema/aarch64-harden-pac-ret-attr.c | 43 +++++++++++++++++++ 18 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 clang/test/CodeGen/aarch64-sign-return-address-harden.c create mode 100644 clang/test/Frontend/aarch64-harden-pac-ret-err.c create mode 100644 clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c create mode 100644 clang/test/Sema/aarch64-harden-pac-ret-attr.c diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index db0f521b73544..d16fbd45f104e 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -544,6 +544,8 @@ def warn_ignoring_verify_debuginfo_preserve_export : Warning< InGroup<UnusedCommandLineArgument>; def warn_unsupported_branch_protection: Warning < "invalid branch protection option '%0' in '%1'">, InGroup<BranchProtection>; +def warn_harden_pac_ret_requires_pac_ret: Warning< + "ignoring '-mharden-pac-ret' as it requires return address signing">, InGroup<UnusedCommandLineArgument>; def err_sls_hardening_arm_not_supported : Error< "-mharden-sls is only supported on armv7-a or later">; def warn_drv_large_data_threshold_invalid_code_model: Warning< diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5cbbc7d130c99..fdfe7792441b9 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3384,6 +3384,11 @@ def err_invalid_branch_protection_spec : Error< "invalid or misplaced branch protection specification '%0'">; def warn_unsupported_branch_protection_spec : Warning< "unsupported branch protection specification '%0'">, InGroup<BranchProtection>; +def warn_attribute_harden_pac_ret_requires_pac_ret: Warning< + "'harden-pac-ret' attribute requires 'branch-protection=pac-ret'; 'target' attribute ignored">, + InGroup<IgnoredAttributes>; +def err_invalid_harden_pac_ret_spec : Error< + "invalid or misplaced pac-ret hardening specification '%0'">; def err_attribute_invalid_atomic_argument : Error< "invalid argument '%0' to atomic attribute; valid options are: " "'remote_memory', 'fine_grained_memory', 'ignore_denormal_mode' (optionally " diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 8cba1dbaee24e..f3c5613d3dab7 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -454,6 +454,8 @@ ENUM_LANGOPT(SignReturnAddressScope, SignReturnAddressScopeKind, 2, SignReturnAd "Scope of return address signing") ENUM_LANGOPT(SignReturnAddressKey, SignReturnAddressKeyKind, 1, SignReturnAddressKeyKind::AKey, NotCompatible, "Key used for return address signing") +ENUM_LANGOPT(SignReturnAddressHardening, SignReturnAddressHardeningKind, 1, + SignReturnAddressHardeningKind::None, NotCompatible, "Hardening of return address signing") LANGOPT(BranchTargetEnforcement, 1, 0, NotCompatible, "Branch-target enforcement enabled") LANGOPT(BranchProtectionPAuthLR, 1, 0, NotCompatible, "Use PC as a diversifier using PAuthLR NOP instructions.") LANGOPT(GuardedControlStack, 1, 0, NotCompatible, "Guarded control stack enabled") diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index ebd0436fa154b..c0e82b3edd2cb 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -299,6 +299,13 @@ class LangOptionsBase { BKey }; + enum class SignReturnAddressHardeningKind { + /// Regular return address signing. + None, + /// Hardened return address signing with load of return address. + LoadReturnAddress + }; + enum class ThreadModelKind { /// POSIX Threads. POSIX, @@ -728,6 +735,11 @@ class LangOptions : public LangOptionsBase { return getSignReturnAddressScope() == SignReturnAddressScopeKind::All; } + bool hasSignReturnAddressHardening() const { + return getSignReturnAddressHardening() != + SignReturnAddressHardeningKind::None; + } + bool isSYCL() const { return SYCLIsDevice || SYCLIsHost; } bool hasDefaultVisibilityExportMapping() const { diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h index 83467b8e93b6a..10779518275b4 100644 --- a/clang/include/clang/Basic/TargetInfo.h +++ b/clang/include/clang/Basic/TargetInfo.h @@ -62,10 +62,12 @@ struct ParsedTargetAttr { StringRef CPU; StringRef Tune; StringRef BranchProtection; + StringRef SignReturnAddrHardening; StringRef Duplicate; bool operator ==(const ParsedTargetAttr &Other) const { return Duplicate == Other.Duplicate && CPU == Other.CPU && Tune == Other.Tune && BranchProtection == Other.BranchProtection && + SignReturnAddrHardening == Other.SignReturnAddrHardening && Features == Other.Features; } }; @@ -1471,6 +1473,7 @@ class TargetInfo : public TransferrableTargetInfo, public: LangOptions::SignReturnAddressScopeKind SignReturnAddr; LangOptions::SignReturnAddressKeyKind SignKey; + LangOptions::SignReturnAddressHardeningKind SignReturnAddressHardening; bool BranchTargetEnforcement; bool BranchProtectionPAuthLR; bool GuardedControlStack; @@ -1497,9 +1500,21 @@ class TargetInfo : public TransferrableTargetInfo, llvm_unreachable("Unexpected SignReturnAddressKeyKind"); } + const char *getSignReturnAddressHardeningStr() const { + switch (SignReturnAddressHardening) { + case LangOptions::SignReturnAddressHardeningKind::None: + return "none"; + case LangOptions::SignReturnAddressHardeningKind::LoadReturnAddress: + return "load-return-address"; + } + llvm_unreachable("Unexpected SignReturnAddressHardeningKind"); + } + BranchProtectionInfo() : SignReturnAddr(LangOptions::SignReturnAddressScopeKind::None), SignKey(LangOptions::SignReturnAddressKeyKind::AKey), + SignReturnAddressHardening( + LangOptions::SignReturnAddressHardeningKind::None), BranchTargetEnforcement(false), BranchProtectionPAuthLR(false), GuardedControlStack(false) {} @@ -1513,6 +1528,7 @@ class TargetInfo : public TransferrableTargetInfo, SignKey = LangOpts.isSignReturnAddressWithAKey() ? LangOptions::SignReturnAddressKeyKind::AKey : LangOptions::SignReturnAddressKeyKind::BKey; + SignReturnAddressHardening = LangOpts.getSignReturnAddressHardening(); BranchTargetEnforcement = LangOpts.BranchTargetEnforcement; BranchProtectionPAuthLR = LangOpts.BranchProtectionPAuthLR; GuardedControlStack = LangOpts.GuardedControlStack; @@ -1535,6 +1551,12 @@ class TargetInfo : public TransferrableTargetInfo, return false; } + /// Validate the Return Address Signing Hardening specification + virtual std::optional<LangOptions::SignReturnAddressHardeningKind> + validateSignReturnAddressHardening(StringRef Spec) const { + return std::nullopt; + } + /// Perform initialization based on the user configured /// set of features (e.g., +sse4). /// diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 2f57a5b13b917..3ac001d05be82 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -5521,6 +5521,15 @@ def msign_return_address_EQ : Joined<["-"], "msign-return-address=">, Visibility<[ClangOption, CC1Option]>, Group<m_Group>, Values<"none,all,non-leaf">, HelpText<"Select return address signing scope">; + +def mharden_pac_ret_EQ : Joined<["-"], "mharden-pac-ret=">, + Visibility<[ClangOption, CC1Option]>, + Flags<[TargetSpecific]>, Group<m_Group>, + HelpText<"Select the return address signing hardening scheme. <arg> must be: none, load-return-address">, + Values<"none,load-return-address">, NormalizedValues<["None", "LoadReturnAddress"]>, + NormalizedValuesScope<"LangOptions::SignReturnAddressHardeningKind">, + MarshallingInfoEnum<LangOpts<"SignReturnAddressHardening">, "None">; + let Flags = [TargetSpecific] in { def mbranch_protection_EQ : Joined<["-"], "mbranch-protection=">, Group<m_Group>, diff --git a/clang/lib/Basic/Targets/AArch64.cpp b/clang/lib/Basic/Targets/AArch64.cpp index fe407e9fc1789..639540fc13d87 100644 --- a/clang/lib/Basic/Targets/AArch64.cpp +++ b/clang/lib/Basic/Targets/AArch64.cpp @@ -276,6 +276,17 @@ bool AArch64TargetInfo::validateBranchProtection(StringRef Spec, StringRef, return true; } +std::optional<LangOptions::SignReturnAddressHardeningKind> +AArch64TargetInfo::validateSignReturnAddressHardening(StringRef Spec) const { + assert(!Spec.empty() && "Spec must not be empty"); + return llvm::StringSwitch< + std::optional<LangOptions::SignReturnAddressHardeningKind>>(Spec) + .Case("load-return-address", + LangOptions::SignReturnAddressHardeningKind::LoadReturnAddress) + .Case("none", LangOptions::SignReturnAddressHardeningKind::None) + .Default(std::nullopt); +} + bool AArch64TargetInfo::isValidCPUName(StringRef Name) const { return llvm::AArch64::parseCpu(Name).has_value(); } @@ -1290,6 +1301,11 @@ ParsedTargetAttr AArch64TargetInfo::parseTargetAttr(StringRef Features) const { continue; } + if (Feature.starts_with("harden-pac-ret=")) { + Ret.SignReturnAddrHardening = Feature.split('=').second.trim(); + continue; + } + if (Feature.starts_with("arch=")) { if (FoundArch) Ret.Duplicate = "arch="; diff --git a/clang/lib/Basic/Targets/AArch64.h b/clang/lib/Basic/Targets/AArch64.h index 2d3b8d2a8d950..c0e52319ba628 100644 --- a/clang/lib/Basic/Targets/AArch64.h +++ b/clang/lib/Basic/Targets/AArch64.h @@ -147,6 +147,8 @@ class LLVM_LIBRARY_VISIBILITY AArch64TargetInfo : public TargetInfo { BranchProtectionInfo &BPI, const LangOptions &LO, StringRef &Err) const override; + std::optional<LangOptions::SignReturnAddressHardeningKind> + validateSignReturnAddressHardening(StringRef Spec) const override; bool isValidCPUName(StringRef Name) const override; void fillValidCPUList(SmallVectorImpl<StringRef> &Values) const override; diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp index 342a3af0ac1ee..530304f9530ad 100644 --- a/clang/lib/CodeGen/TargetInfo.cpp +++ b/clang/lib/CodeGen/TargetInfo.cpp @@ -233,6 +233,10 @@ void TargetCodeGenInfo::setBranchProtectionFnAttributes( if (BPI.SignReturnAddr != LangOptions::SignReturnAddressScopeKind::None) { F.addFnAttr("sign-return-address", BPI.getSignReturnAddrStr()); F.addFnAttr("sign-return-address-key", BPI.getSignKeyStr()); + if (BPI.SignReturnAddressHardening != + LangOptions::SignReturnAddressHardeningKind::None) + F.addFnAttr("sign-return-address-harden", + BPI.getSignReturnAddressHardeningStr()); } else { if (F.hasFnAttribute("sign-return-address")) F.removeFnAttr("sign-return-address"); diff --git a/clang/lib/CodeGen/Targets/AArch64.cpp b/clang/lib/CodeGen/Targets/AArch64.cpp index 963b74927036a..2d197e27dc115 100644 --- a/clang/lib/CodeGen/Targets/AArch64.cpp +++ b/clang/lib/CodeGen/Targets/AArch64.cpp @@ -160,6 +160,9 @@ class AArch64TargetCodeGenInfo : public TargetCodeGenInfo { Attr.BranchProtection, Attr.CPU, BPI, CGM.getLangOpts(), Error); assert(Error.empty()); } + if (!Attr.SignReturnAddrHardening.empty()) + Fn->addFnAttr("sign-return-address-harden", + Attr.SignReturnAddrHardening); } setBranchProtectionFnAttributes(BPI, *Fn); setPointerAuthFnAttributes(CGM.getCodeGenOpts().PointerAuth, *Fn); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 4ca98600d6e93..f91e1fd367043 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -1378,6 +1378,19 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, ? Args.getLastArg(options::OPT_msign_return_address_EQ, options::OPT_mbranch_protection_EQ) : Args.getLastArg(options::OPT_mbranch_protection_EQ); + const Arg *HardenPACRetArg = Args.getLastArg(options::OPT_mharden_pac_ret_EQ); + const Driver &D = TC.getDriver(); + + if (HardenPACRetArg) { + if (!isAArch64) { + D.Diag(diag::err_drv_unsupported_opt_for_target) + << HardenPACRetArg->getSpelling() << TC.getTriple().str(); + return; + } + if (!A) + D.Diag(diag::warn_harden_pac_ret_requires_pac_ret); + } + if (!A) { if (Triple.isOSOpenBSD() && isAArch64) { CmdArgs.push_back("-msign-return-address=non-leaf"); @@ -1387,7 +1400,6 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, return; } - const Driver &D = TC.getDriver(); if (!(isAArch64 || (Triple.isArmT32() && Triple.isArmMClass()))) D.Diag(diag::warn_incompatible_branch_protection_option) << Triple.getArchName(); @@ -1466,6 +1478,14 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, if (GuardedControlStack) CmdArgs.push_back("-mguarded-control-stack"); + + if (HardenPACRetArg) { + if (Scope == "none") + D.Diag(diag::warn_harden_pac_ret_requires_pac_ret); + else + CmdArgs.push_back(Args.MakeArgString(Twine("-mharden-pac-ret=") + + HardenPACRetArg->getValue())); + } } void Clang::AddARMTargetArgs(const llvm::Triple &Triple, const ArgList &Args, diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index d762bcd789bf5..a514be7fd217f 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -3428,8 +3428,11 @@ bool Sema::checkTargetAttr(SourceLocation LiteralLoc, StringRef AttrStr) { TargetInfo::BranchProtectionInfo BPI{}; StringRef DiagMsg; - if (ParsedAttrs.BranchProtection.empty()) + if (ParsedAttrs.BranchProtection.empty()) { + if (!ParsedAttrs.SignReturnAddrHardening.empty()) + Diag(LiteralLoc, diag::warn_attribute_harden_pac_ret_requires_pac_ret); return false; + } if (!Context.getTargetInfo().validateBranchProtection( ParsedAttrs.BranchProtection, ParsedAttrs.CPU, BPI, Context.getLangOpts(), DiagMsg)) { @@ -3442,6 +3445,19 @@ bool Sema::checkTargetAttr(SourceLocation LiteralLoc, StringRef AttrStr) { if (!DiagMsg.empty()) Diag(LiteralLoc, diag::warn_unsupported_branch_protection_spec) << DiagMsg; + if (!ParsedAttrs.SignReturnAddrHardening.empty()) { + auto SignReturnAddrOption = + Context.getTargetInfo().validateSignReturnAddressHardening( + ParsedAttrs.SignReturnAddrHardening); + if (!SignReturnAddrOption) + return Diag(LiteralLoc, diag::err_invalid_harden_pac_ret_spec) + << ParsedAttrs.SignReturnAddrHardening; + + if (BPI.SignReturnAddr == LangOptions::SignReturnAddressScopeKind::None) + return Diag(LiteralLoc, + diag::warn_attribute_harden_pac_ret_requires_pac_ret); + } + return false; } diff --git a/clang/test/CodeGen/aarch64-sign-return-address-harden.c b/clang/test/CodeGen/aarch64-sign-return-address-harden.c new file mode 100644 index 0000000000000..6d4db64c5efde --- /dev/null +++ b/clang/test/CodeGen/aarch64-sign-return-address-harden.c @@ -0,0 +1,13 @@ +// RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=none \ +// RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN +// RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=pac-ret \ +// RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN +// RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=pac-ret -mharden-pac-ret=none \ +// RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN +// RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=pac-ret -mharden-pac-ret=load-return-address \ +// RUN: | FileCheck %s --check-prefixes=CHECK-HARDEN + +void foo() {} + +// CHECK-NO-HARDEN-NOT: attributes #0 = {{.*}}"sign-return-address-harden" +// CHECK-HARDEN: attributes #0 = {{.*}}"sign-return-address-harden"="load-return-address" \ No newline at end of file diff --git a/clang/test/Driver/aarch64-security-options.c b/clang/test/Driver/aarch64-security-options.c index 146add2d1cf70..fbcf3d75071c6 100644 --- a/clang/test/Driver/aarch64-security-options.c +++ b/clang/test/Driver/aarch64-security-options.c @@ -32,6 +32,36 @@ // WARN-NOT: warning: ignoring '-mbranch-protection=' option because the 'aarch64' architecture does not support it [-Wbranch-protection] +// RUN: %clang -target aarch64 -c %s -### -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN + +// RUN: %clang -target aarch64 -c %s -### -mharden-pac-ret=load-return-address 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN + +// RUN: %clang -target aarch64 -c %s -### -mbranch-protection=none -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN + +// RUN: %clang -target aarch64 -c %s -### -mbranch-protection=none -mharden-pac-ret=load-return-address 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN + +// RUN: %clang -target aarch64 -c %s -### -mbranch-protection=pac-ret -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-HARDEN-NONE + +// RUN: %clang -target aarch64 -c %s -### -mbranch-protection=pac-ret -mharden-pac-ret=load-return-address 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-HARDEN-LRA + +// RUN: %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-HARDEN-NONE + +// RUN: %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=load-return-address 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-HARDEN-LRA + +// NO-RA-HARDEN: ignoring '-mharden-pac-ret' as it requires return address signing +// NO-RA-HARDEN-NOT: "-mharden-pac-ret" +// NO-RA-HARDEN-LRA: ignoring '-mharden-pac-ret' as it requires return address signing +// RA-HARDEN-NONE: "-mharden-pac-ret=none" +// RA-HARDEN-LRA: "-mharden-pac-ret=load-return-address" + // RA-OFF: "-msign-return-address=none" // RA-NON-LEAF: "-msign-return-address=non-leaf" // RA-ALL: "-msign-return-address=all" diff --git a/clang/test/Driver/arm-security-options.c b/clang/test/Driver/arm-security-options.c index 613945c24eede..db1888bec52db 100644 --- a/clang/test/Driver/arm-security-options.c +++ b/clang/test/Driver/arm-security-options.c @@ -75,6 +75,23 @@ // RUN: %clang -target arm-arm-none-eabi -march=armv7-r -c %s -### -mbranch-protection=bti 2>&1 | \ // RUN: FileCheck %s --check-prefix=INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8m.main -c %s -### -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8.1-m.main -c %s -### -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8.1-m.main -c %s -### -mbranch-protection=bti -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8.1-m.main -c %s -### -mbranch-protection=pac-ret -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8.1-m.main -c %s -### -mbranch-protection=pac-ret+bti -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8.1-m.main -c %s -### -mbranch-protection=pac-ret+leaf -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8.1-m.main -c %s -### -msign-return-address=all -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH +// RUN: not %clang -target arm-arm-none-eabi -march=armv8.1-m.main -c %s -### -msign-return-address=non-leaf -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefix=RA-HARDEN-INCOMPATIBLE-ARCH + // RA-OFF: "-msign-return-address=none" // RA-NON-LEAF: "-msign-return-address=non-leaf" // RA-ALL: "-msign-return-address=all" @@ -91,3 +108,5 @@ // BAD-LEAF-COMBINATION: unsupported argument 'leaf' to option '-mbranch-protection=' // INCOMPATIBLE-ARCH: '-mbranch-protection=' option is incompatible with the '{{.*}}' architecture + +// RA-HARDEN-INCOMPATIBLE-ARCH: unsupported option '-mharden-pac-ret=' for target 'arm-arm-none-eabi' diff --git a/clang/test/Frontend/aarch64-harden-pac-ret-err.c b/clang/test/Frontend/aarch64-harden-pac-ret-err.c new file mode 100644 index 0000000000000..9fc2cba7e8205 --- /dev/null +++ b/clang/test/Frontend/aarch64-harden-pac-ret-err.c @@ -0,0 +1,3 @@ +// RUN: not %clang_cc1 -fsyntax-only -triple aarch64 %s -mharden-pac-ret=foo 2>&1 | FileCheck %s + +// CHECK: invalid value 'foo' in '-mharden-pac-ret=foo' diff --git a/clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c b/clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c new file mode 100644 index 0000000000000..3e218253137d6 --- /dev/null +++ b/clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple aarch64 -verify -fsyntax-only %s + +__attribute__((target("harden-pac-ret=none"))) // expected-warning {{'harden-pac-ret' attribute requires 'branch-protection=pac-ret'; 'target' attribute ignored}} +void +badvalue0(void) {} + +__attribute__((target("harden-pac-ret=load-return-address"))) // expected-warning {{'harden-pac-ret' attribute requires 'branch-protection=pac-ret'; 'target' attribute ignored}} +void +badvalue1(void) {} + +__attribute__((target("branch-protection=bti,harden-pac-ret=none"))) // expected-warning {{'harden-pac-ret' attribute requires 'branch-protection=pac-ret'; 'target' attribute ignored}} +void +badvalue2(void) {} + +__attribute__((target("branch-protection=bti,harden-pac-ret=load-return-address"))) // expected-warning {{'harden-pac-ret' attribute requires 'branch-protection=pac-ret'; 'target' attribute ignored}} +void +badvalue3(void) {} + +__attribute__((target("branch-protection=bti,harden-pac-ret=inexistent"))) // expected-error {{invalid or misplaced pac-ret hardening specification 'inexistent'}} +void +badvalue4(void) {} diff --git a/clang/test/Sema/aarch64-harden-pac-ret-attr.c b/clang/test/Sema/aarch64-harden-pac-ret-attr.c new file mode 100644 index 0000000000000..c05e33ee28055 --- /dev/null +++ b/clang/test/Sema/aarch64-harden-pac-ret-attr.c @@ -0,0 +1,43 @@ +// RUN: %clang_cc1 -triple aarch64 -emit-llvm -target-cpu generic -target-feature +v8.5a %s -o - \ +// RUN: | FileCheck %s --check-prefix=CHECK + +__attribute__ ((target("branch-protection=pac-ret,harden-pac-ret=none"))) +void f1() {} +// CHECK: define{{.*}} void @f1() #[[#F1:]] + +__attribute__ ((target("branch-protection=pac-ret,harden-pac-ret=load-return-address"))) +void f2() {} +// CHECK: define{{.*}} void @f2() #[[#F2:]] + +__attribute__ ((target("branch-protection=pac-ret+leaf,harden-pac-ret=none"))) +void f3() {} +// CHECK: define{{.*}} void @f3() #[[#F3:]] + +__attribute__ ((target("branch-protection=pac-ret+leaf,harden-pac-ret=load-return-address"))) +void f4() {} +// CHECK: define{{.*}} void @f4() #[[#F4:]] + +__attribute__ ((target("branch-protection=pac-ret+b-key,harden-pac-ret=none"))) +void f5() {} +// CHECK: define{{.*}} void @f5() #[[#F5:]] + +__attribute__ ((target("branch-protection=pac-ret+b-key,harden-pac-ret=load-return-address"))) +void f6() {} +// CHECK: define{{.*}} void @f6() #[[#F6:]] + +__attribute__ ((target("branch-protection=pac-ret+leaf+b-key,harden-pac-ret=none"))) +void f7() {} +// CHECK: define{{.*}} void @f7() #[[#F7:]] + +__attribute__ ((target("branch-protection=pac-ret+leaf+b-key,harden-pac-ret=load-return-address"))) +void f8() {} +// CHECK: define{{.*}} void @f8() #[[#F8:]] + +// CHECK-DAG: attributes #[[#F1]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="none" +// CHECK-DAG: attributes #[[#F2]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" +// CHECK-DAG: attributes #[[#F3]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="none" +// CHECK-DAG: attributes #[[#F4]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="load-return-address" +// CHECK-DAG: attributes #[[#F5]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="none" "sign-return-address-key"="b_key" +// CHECK-DAG: attributes #[[#F6]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" +// CHECK-DAG: attributes #[[#F7]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="none" "sign-return-address-key"="b_key" +// CHECK-DAG: attributes #[[#F8]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" >From 97c8d752b6115d3a2e7d6e4c84c35ea0baa641bc Mon Sep 17 00:00:00 2001 From: Victor Campos <[email protected]> Date: Mon, 13 Apr 2026 11:39:18 +0100 Subject: [PATCH 2/3] Address code reviews --- .../clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/include/clang/Basic/LangOptions.h | 5 - clang/include/clang/Basic/TargetInfo.h | 4 +- clang/lib/Basic/Targets/AArch64.cpp | 3 +- clang/lib/Basic/Targets/AArch64.h | 2 +- clang/lib/CodeGen/TargetInfo.cpp | 13 ++ clang/lib/CodeGen/Targets/AArch64.cpp | 13 +- clang/lib/Driver/ToolChains/Clang.cpp | 127 +++++++++--------- clang/lib/Sema/SemaDeclAttr.cpp | 12 +- .../aarch64-sign-return-address-harden.c | 8 ++ clang/test/Driver/aarch64-security-options.c | 17 ++- .../aarch64-harden-pac-ret-attr-err-warn.c | 2 +- clang/test/Sema/aarch64-harden-pac-ret-attr.c | 31 +++-- 13 files changed, 144 insertions(+), 95 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index fdfe7792441b9..e963eb7d1399b 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3388,7 +3388,7 @@ def warn_attribute_harden_pac_ret_requires_pac_ret: Warning< "'harden-pac-ret' attribute requires 'branch-protection=pac-ret'; 'target' attribute ignored">, InGroup<IgnoredAttributes>; def err_invalid_harden_pac_ret_spec : Error< - "invalid or misplaced pac-ret hardening specification '%0'">; + "invalid or misspelled pac-ret hardening specification '%0'">; def err_attribute_invalid_atomic_argument : Error< "invalid argument '%0' to atomic attribute; valid options are: " "'remote_memory', 'fine_grained_memory', 'ignore_denormal_mode' (optionally " diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index c0e82b3edd2cb..bc7deae9c5122 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -735,11 +735,6 @@ class LangOptions : public LangOptionsBase { return getSignReturnAddressScope() == SignReturnAddressScopeKind::All; } - bool hasSignReturnAddressHardening() const { - return getSignReturnAddressHardening() != - SignReturnAddressHardeningKind::None; - } - bool isSYCL() const { return SYCLIsDevice || SYCLIsHost; } bool hasDefaultVisibilityExportMapping() const { diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h index 10779518275b4..7d64a9d7bd4bb 100644 --- a/clang/include/clang/Basic/TargetInfo.h +++ b/clang/include/clang/Basic/TargetInfo.h @@ -1551,9 +1551,9 @@ class TargetInfo : public TransferrableTargetInfo, return false; } - /// Validate the Return Address Signing Hardening specification + /// Parse the Return Address Signing Hardening specification. virtual std::optional<LangOptions::SignReturnAddressHardeningKind> - validateSignReturnAddressHardening(StringRef Spec) const { + parseSignReturnAddressHardening(StringRef Spec) const { return std::nullopt; } diff --git a/clang/lib/Basic/Targets/AArch64.cpp b/clang/lib/Basic/Targets/AArch64.cpp index 639540fc13d87..da10cd384b963 100644 --- a/clang/lib/Basic/Targets/AArch64.cpp +++ b/clang/lib/Basic/Targets/AArch64.cpp @@ -277,8 +277,7 @@ bool AArch64TargetInfo::validateBranchProtection(StringRef Spec, StringRef, } std::optional<LangOptions::SignReturnAddressHardeningKind> -AArch64TargetInfo::validateSignReturnAddressHardening(StringRef Spec) const { - assert(!Spec.empty() && "Spec must not be empty"); +AArch64TargetInfo::parseSignReturnAddressHardening(StringRef Spec) const { return llvm::StringSwitch< std::optional<LangOptions::SignReturnAddressHardeningKind>>(Spec) .Case("load-return-address", diff --git a/clang/lib/Basic/Targets/AArch64.h b/clang/lib/Basic/Targets/AArch64.h index c0e52319ba628..9312b785dd20b 100644 --- a/clang/lib/Basic/Targets/AArch64.h +++ b/clang/lib/Basic/Targets/AArch64.h @@ -148,7 +148,7 @@ class LLVM_LIBRARY_VISIBILITY AArch64TargetInfo : public TargetInfo { const LangOptions &LO, StringRef &Err) const override; std::optional<LangOptions::SignReturnAddressHardeningKind> - validateSignReturnAddressHardening(StringRef Spec) const override; + parseSignReturnAddressHardening(StringRef Spec) const override; bool isValidCPUName(StringRef Name) const override; void fillValidCPUList(SmallVectorImpl<StringRef> &Values) const override; diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp index 530304f9530ad..9e0b7bb709cc9 100644 --- a/clang/lib/CodeGen/TargetInfo.cpp +++ b/clang/lib/CodeGen/TargetInfo.cpp @@ -230,6 +230,7 @@ void TargetCodeGenInfo::setBranchProtectionFnAttributes( // Called on already created and initialized function where attributes already // set from command line attributes but some might need to be removed as the // actual BPI is different. + if (BPI.SignReturnAddr != LangOptions::SignReturnAddressScopeKind::None) { F.addFnAttr("sign-return-address", BPI.getSignReturnAddrStr()); F.addFnAttr("sign-return-address-key", BPI.getSignKeyStr()); @@ -244,6 +245,14 @@ void TargetCodeGenInfo::setBranchProtectionFnAttributes( F.removeFnAttr("sign-return-address-key"); } + if (BPI.SignReturnAddressHardening == + LangOptions::SignReturnAddressHardeningKind::None) { + F.removeFnAttr("sign-return-address-harden"); + } else { + F.addFnAttr("sign-return-address-harden", + BPI.getSignReturnAddressHardeningStr()); + } + auto AddRemoveAttributeAsSet = [&](bool Set, const StringRef &ModAttr) { if (Set) F.addFnAttr(ModAttr); @@ -266,6 +275,10 @@ void TargetCodeGenInfo::initBranchProtectionFnAttributes( FuncAttrs.addAttribute("sign-return-address", BPI.getSignReturnAddrStr()); FuncAttrs.addAttribute("sign-return-address-key", BPI.getSignKeyStr()); } + if (BPI.SignReturnAddressHardening != + LangOptions::SignReturnAddressHardeningKind::None) + FuncAttrs.addAttribute("sign-return-address-harden", + BPI.getSignReturnAddressHardeningStr()); if (BPI.BranchTargetEnforcement) FuncAttrs.addAttribute("branch-target-enforcement"); if (BPI.BranchProtectionPAuthLR) diff --git a/clang/lib/CodeGen/Targets/AArch64.cpp b/clang/lib/CodeGen/Targets/AArch64.cpp index 2d197e27dc115..87219df5cbc83 100644 --- a/clang/lib/CodeGen/Targets/AArch64.cpp +++ b/clang/lib/CodeGen/Targets/AArch64.cpp @@ -159,10 +159,17 @@ class AArch64TargetCodeGenInfo : public TargetCodeGenInfo { (void)CGM.getTarget().validateBranchProtection( Attr.BranchProtection, Attr.CPU, BPI, CGM.getLangOpts(), Error); assert(Error.empty()); + + // Hardening is only accepted in the target attribute if PAC-RET is also + // present there. Invalid combinations are handled in Sema. + if (BPI.SignReturnAddr != + LangOptions::SignReturnAddressScopeKind::None) { + if (auto Hardening = CGM.getTarget().parseSignReturnAddressHardening( + Attr.SignReturnAddrHardening)) { + BPI.SignReturnAddressHardening = *Hardening; + } + } } - if (!Attr.SignReturnAddrHardening.empty()) - Fn->addFnAttr("sign-return-address-harden", - Attr.SignReturnAddrHardening); } setBranchProtectionFnAttributes(BPI, *Fn); setPointerAuthFnAttributes(CGM.getCodeGenOpts().PointerAuth, *Fn); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index f91e1fd367043..ed200b6ec3ec9 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -1381,79 +1381,85 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, const Arg *HardenPACRetArg = Args.getLastArg(options::OPT_mharden_pac_ret_EQ); const Driver &D = TC.getDriver(); + // Check CmdArgs because some toolchains bypass the driver args and add to + // the frontend args directly. + bool HasPtrauthReturns = llvm::is_contained(CmdArgs, "-fptrauth-returns") || + Args.hasArgNoClaim(options::OPT_fno_ptrauth_returns, + options::OPT_fptrauth_returns); + if (HardenPACRetArg) { if (!isAArch64) { D.Diag(diag::err_drv_unsupported_opt_for_target) << HardenPACRetArg->getSpelling() << TC.getTriple().str(); return; } - if (!A) - D.Diag(diag::warn_harden_pac_ret_requires_pac_ret); + StringRef ArgValue = HardenPACRetArg->getValue(); + if (ArgValue != "none" && ArgValue != "load-return-address") { + D.Diag(diag::err_drv_unsupported_option_argument) + << HardenPACRetArg->getSpelling() << ArgValue; + return; + } } - if (!A) { - if (Triple.isOSOpenBSD() && isAArch64) { - CmdArgs.push_back("-msign-return-address=non-leaf"); - CmdArgs.push_back("-msign-return-address-key=a_key"); - CmdArgs.push_back("-mbranch-target-enforce"); - } + if (!A && Triple.isOSOpenBSD() && isAArch64) { + CmdArgs.push_back("-msign-return-address=non-leaf"); + CmdArgs.push_back("-msign-return-address-key=a_key"); + CmdArgs.push_back("-mbranch-target-enforce"); return; } - if (!(isAArch64 || (Triple.isArmT32() && Triple.isArmMClass()))) + if (A && !(isAArch64 || (Triple.isArmT32() && Triple.isArmMClass()))) D.Diag(diag::warn_incompatible_branch_protection_option) << Triple.getArchName(); - StringRef Scope, Key; - bool IndirectBranches, BranchProtectionPAuthLR, GuardedControlStack; - - if (A->getOption().matches(options::OPT_msign_return_address_EQ)) { - Scope = A->getValue(); - if (Scope != "none" && Scope != "non-leaf" && Scope != "all") - D.Diag(diag::err_drv_unsupported_option_argument) - << A->getSpelling() << Scope; - Key = "a_key"; - IndirectBranches = Triple.isOSOpenBSD() && isAArch64; - BranchProtectionPAuthLR = false; - GuardedControlStack = false; - } else { - StringRef DiagMsg; - llvm::ARM::ParsedBranchProtection PBP; - bool EnablePAuthLR = false; - - // To know if we need to enable PAuth-LR As part of the standard branch - // protection option, it needs to be determined if the feature has been - // activated in the `march` argument. This information is stored within the - // CmdArgs variable and can be found using a search. - if (isAArch64) { - auto isPAuthLR = [](const char *member) { - llvm::AArch64::ExtensionInfo pauthlr_extension = - llvm::AArch64::getExtensionByID(llvm::AArch64::AEK_PAUTHLR); - return pauthlr_extension.PosTargetFeature == member; - }; + StringRef Scope = "none", Key; + bool IndirectBranches = false, BranchProtectionPAuthLR = false, + GuardedControlStack = false; - if (llvm::any_of(CmdArgs, isPAuthLR)) - EnablePAuthLR = true; + if (A) { + if (A->getOption().matches(options::OPT_msign_return_address_EQ)) { + Scope = A->getValue(); + if (Scope != "none" && Scope != "non-leaf" && Scope != "all") + D.Diag(diag::err_drv_unsupported_option_argument) + << A->getSpelling() << Scope; + Key = "a_key"; + IndirectBranches = Triple.isOSOpenBSD() && isAArch64; + BranchProtectionPAuthLR = false; + GuardedControlStack = false; + } else { + StringRef DiagMsg; + llvm::ARM::ParsedBranchProtection PBP; + bool EnablePAuthLR = false; + + // To know if we need to enable PAuth-LR As part of the standard branch + // protection option, it needs to be determined if the feature has been + // activated in the `march` argument. This information is stored within + // the CmdArgs variable and can be found using a search. + if (isAArch64) { + auto isPAuthLR = [](const char *member) { + llvm::AArch64::ExtensionInfo pauthlr_extension = + llvm::AArch64::getExtensionByID(llvm::AArch64::AEK_PAUTHLR); + return pauthlr_extension.PosTargetFeature == member; + }; + + if (llvm::any_of(CmdArgs, isPAuthLR)) + EnablePAuthLR = true; + } + if (!llvm::ARM::parseBranchProtection(A->getValue(), PBP, DiagMsg, + EnablePAuthLR)) + D.Diag(diag::err_drv_unsupported_option_argument) + << A->getSpelling() << DiagMsg; + if (!isAArch64 && PBP.Key == "b_key") + D.Diag(diag::warn_unsupported_branch_protection) + << "b-key" << A->getAsString(Args); + Scope = PBP.Scope; + Key = PBP.Key; + BranchProtectionPAuthLR = PBP.BranchProtectionPAuthLR; + IndirectBranches = PBP.BranchTargetEnforcement; + GuardedControlStack = PBP.GuardedControlStack; } - if (!llvm::ARM::parseBranchProtection(A->getValue(), PBP, DiagMsg, - EnablePAuthLR)) - D.Diag(diag::err_drv_unsupported_option_argument) - << A->getSpelling() << DiagMsg; - if (!isAArch64 && PBP.Key == "b_key") - D.Diag(diag::warn_unsupported_branch_protection) - << "b-key" << A->getAsString(Args); - Scope = PBP.Scope; - Key = PBP.Key; - BranchProtectionPAuthLR = PBP.BranchProtectionPAuthLR; - IndirectBranches = PBP.BranchTargetEnforcement; - GuardedControlStack = PBP.GuardedControlStack; - } - - Arg *PtrauthReturnsArg = Args.getLastArg(options::OPT_fptrauth_returns, - options::OPT_fno_ptrauth_returns); - bool HasPtrauthReturns = - PtrauthReturnsArg && - PtrauthReturnsArg->getOption().matches(options::OPT_fptrauth_returns); + } + // GCS is currently untested with ptrauth-returns, but enabling this could be // allowed in future after testing with a suitable system. if (Scope != "none" || BranchProtectionPAuthLR || GuardedControlStack) { @@ -1465,8 +1471,9 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, << A->getAsString(Args) << "-fptrauth-returns"; } - CmdArgs.push_back( - Args.MakeArgString(Twine("-msign-return-address=") + Scope)); + if (A) + CmdArgs.push_back( + Args.MakeArgString(Twine("-msign-return-address=") + Scope)); if (Scope != "none") CmdArgs.push_back( Args.MakeArgString(Twine("-msign-return-address-key=") + Key)); @@ -1480,7 +1487,7 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, CmdArgs.push_back("-mguarded-control-stack"); if (HardenPACRetArg) { - if (Scope == "none") + if (Scope == "none" && !HasPtrauthReturns) D.Diag(diag::warn_harden_pac_ret_requires_pac_ret); else CmdArgs.push_back(Args.MakeArgString(Twine("-mharden-pac-ret=") + diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index a514be7fd217f..9118f7bef778e 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -3426,13 +3426,15 @@ bool Sema::checkTargetAttr(SourceLocation LiteralLoc, StringRef AttrStr) { << Unsupported << None << CurFeature << Target; } - TargetInfo::BranchProtectionInfo BPI{}; - StringRef DiagMsg; if (ParsedAttrs.BranchProtection.empty()) { if (!ParsedAttrs.SignReturnAddrHardening.empty()) Diag(LiteralLoc, diag::warn_attribute_harden_pac_ret_requires_pac_ret); return false; } + + TargetInfo::BranchProtectionInfo BPI{}; + StringRef DiagMsg; + if (!Context.getTargetInfo().validateBranchProtection( ParsedAttrs.BranchProtection, ParsedAttrs.CPU, BPI, Context.getLangOpts(), DiagMsg)) { @@ -3446,10 +3448,10 @@ bool Sema::checkTargetAttr(SourceLocation LiteralLoc, StringRef AttrStr) { Diag(LiteralLoc, diag::warn_unsupported_branch_protection_spec) << DiagMsg; if (!ParsedAttrs.SignReturnAddrHardening.empty()) { - auto SignReturnAddrOption = - Context.getTargetInfo().validateSignReturnAddressHardening( + auto SignReturnAddrHardenOpt = + Context.getTargetInfo().parseSignReturnAddressHardening( ParsedAttrs.SignReturnAddrHardening); - if (!SignReturnAddrOption) + if (!SignReturnAddrHardenOpt) return Diag(LiteralLoc, diag::err_invalid_harden_pac_ret_spec) << ParsedAttrs.SignReturnAddrHardening; diff --git a/clang/test/CodeGen/aarch64-sign-return-address-harden.c b/clang/test/CodeGen/aarch64-sign-return-address-harden.c index 6d4db64c5efde..4b078bbc367b3 100644 --- a/clang/test/CodeGen/aarch64-sign-return-address-harden.c +++ b/clang/test/CodeGen/aarch64-sign-return-address-harden.c @@ -1,11 +1,19 @@ // RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=none \ // RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN +// RUN: %clang -target aarch64-linux-pauthtest -S -emit-llvm -o - %s -fno-ptrauth-returns \ +// RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN // RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=pac-ret \ // RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN +// RUN: %clang -target aarch64-linux-pauthtest -S -emit-llvm -o - %s -fptrauth-returns \ +// RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN // RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=pac-ret -mharden-pac-ret=none \ // RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN +// RUN: %clang -target aarch64-linux-pauthtest -S -emit-llvm -o - %s -fptrauth-returns -mharden-pac-ret=none \ +// RUN: | FileCheck %s --check-prefixes=CHECK-NO-HARDEN // RUN: %clang -target aarch64 -S -emit-llvm -o - %s -mbranch-protection=pac-ret -mharden-pac-ret=load-return-address \ // RUN: | FileCheck %s --check-prefixes=CHECK-HARDEN +// RUN: %clang -target aarch64-linux-pauthtest -S -emit-llvm -o - %s -fptrauth-returns -mharden-pac-ret=load-return-address \ +// RUN: | FileCheck %s --check-prefixes=CHECK-HARDEN void foo() {} diff --git a/clang/test/Driver/aarch64-security-options.c b/clang/test/Driver/aarch64-security-options.c index fbcf3d75071c6..321205abd8e1a 100644 --- a/clang/test/Driver/aarch64-security-options.c +++ b/clang/test/Driver/aarch64-security-options.c @@ -32,6 +32,9 @@ // WARN-NOT: warning: ignoring '-mbranch-protection=' option because the 'aarch64' architecture does not support it [-Wbranch-protection] +// RUN: %clang -target aarch64 -c %s -### 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=ABSENT-RA-HARDEN + // RUN: %clang -target aarch64 -c %s -### -mharden-pac-ret=none 2>&1 | \ // RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN @@ -56,11 +59,15 @@ // RUN: %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=load-return-address 2>&1 | \ // RUN: FileCheck %s --check-prefixes=RA-HARDEN-LRA -// NO-RA-HARDEN: ignoring '-mharden-pac-ret' as it requires return address signing -// NO-RA-HARDEN-NOT: "-mharden-pac-ret" -// NO-RA-HARDEN-LRA: ignoring '-mharden-pac-ret' as it requires return address signing -// RA-HARDEN-NONE: "-mharden-pac-ret=none" -// RA-HARDEN-LRA: "-mharden-pac-ret=load-return-address" +// RUN: not %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=foo 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=BAD-HARDEN-PROTECTION + +// ABSENT-RA-HARDEN-NOT: "-mharden-pac-ret" +// NO-RA-HARDEN: ignoring '-mharden-pac-ret' as it requires return address signing +// NO-RA-HARDEN-NOT: "-mharden-pac-ret" +// RA-HARDEN-NONE: "-mharden-pac-ret=none" +// RA-HARDEN-LRA: "-mharden-pac-ret=load-return-address" +// BAD-HARDEN-PROTECTION: unsupported argument 'foo' to option '-mharden-pac-ret=' // RA-OFF: "-msign-return-address=none" // RA-NON-LEAF: "-msign-return-address=non-leaf" diff --git a/clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c b/clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c index 3e218253137d6..a98a80c85c819 100644 --- a/clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c +++ b/clang/test/Sema/aarch64-harden-pac-ret-attr-err-warn.c @@ -16,6 +16,6 @@ __attribute__((target("branch-protection=bti,harden-pac-ret=load-return-address" void badvalue3(void) {} -__attribute__((target("branch-protection=bti,harden-pac-ret=inexistent"))) // expected-error {{invalid or misplaced pac-ret hardening specification 'inexistent'}} +__attribute__((target("branch-protection=bti,harden-pac-ret=inexistent"))) // expected-error {{invalid or misspelled pac-ret hardening specification 'inexistent'}} void badvalue4(void) {} diff --git a/clang/test/Sema/aarch64-harden-pac-ret-attr.c b/clang/test/Sema/aarch64-harden-pac-ret-attr.c index c05e33ee28055..28795003e3a5b 100644 --- a/clang/test/Sema/aarch64-harden-pac-ret-attr.c +++ b/clang/test/Sema/aarch64-harden-pac-ret-attr.c @@ -1,5 +1,8 @@ -// RUN: %clang_cc1 -triple aarch64 -emit-llvm -target-cpu generic -target-feature +v8.5a %s -o - \ -// RUN: | FileCheck %s --check-prefix=CHECK +// RUN: %clang_cc1 -triple aarch64 -emit-llvm -target-cpu generic -target-feature +v8.5a %s -o - | FileCheck %s + +// The following test that the function attributes take precedence over command-line options +// RUN: %clang_cc1 -triple aarch64 -emit-llvm -target-cpu generic -target-feature +v8.5a %s -msign-return-address=all -mharden-pac-ret=none -o - | FileCheck %s +// RUN: %clang_cc1 -triple aarch64 -emit-llvm -target-cpu generic -target-feature +v8.5a %s -msign-return-address=all -mharden-pac-ret=load-return-address -o - | FileCheck %s __attribute__ ((target("branch-protection=pac-ret,harden-pac-ret=none"))) void f1() {} @@ -33,11 +36,19 @@ __attribute__ ((target("branch-protection=pac-ret+leaf+b-key,harden-pac-ret=load void f8() {} // CHECK: define{{.*}} void @f8() #[[#F8:]] -// CHECK-DAG: attributes #[[#F1]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="none" -// CHECK-DAG: attributes #[[#F2]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" -// CHECK-DAG: attributes #[[#F3]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="none" -// CHECK-DAG: attributes #[[#F4]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="load-return-address" -// CHECK-DAG: attributes #[[#F5]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="none" "sign-return-address-key"="b_key" -// CHECK-DAG: attributes #[[#F6]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" -// CHECK-DAG: attributes #[[#F7]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="none" "sign-return-address-key"="b_key" -// CHECK-DAG: attributes #[[#F8]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" +// These check patterns rely on the fact that "sign-return-address-harden" appears after "sign-return-address" + +// CHECK: attributes #[[#F1]] = { {{.*}} "sign-return-address"="non-leaf" +// CHECK-NOT: "sign-return-address-harden" +// CHECK: attributes #[[#F2]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" +// CHECK: attributes #[[#F3]] = { {{.*}} "sign-return-address"="all" +// CHECK-NOT: "sign-return-address-harden" +// CHECK: attributes #[[#F4]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="load-return-address" +// CHECK: attributes #[[#F5]] = { {{.*}} "sign-return-address"="non-leaf" +// CHECK-NOT: "sign-return-address-harden" +// CHECK: "sign-return-address-key"="b_key" +// CHECK: attributes #[[#F6]] = { {{.*}} "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" +// CHECK: attributes #[[#F7]] = { {{.*}} "sign-return-address"="all" +// CHECK-NOT: "sign-return-address-harden" +// CHECK: "sign-return-address-key"="b_key" +// CHECK: attributes #[[#F8]] = { {{.*}} "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" >From 63514f49bd2b1cf82f7d924c62ba12ff12ac7e29 Mon Sep 17 00:00:00 2001 From: Victor Campos <[email protected]> Date: Thu, 16 Jul 2026 14:58:12 +0100 Subject: [PATCH 3/3] Changes: - Emit error if -mharden-pac-ret=load-return-address is used alongside -mexecute-only. - Add driver tests for the point above and also for the use of hardening with -fptrauth-returns. - Create new doc page for the feature. --- clang/docs/ReturnAddressSigningHardening.rst | 178 +++++++++++++++++++ clang/docs/index.rst | 2 +- clang/include/clang/Options/Options.td | 2 +- clang/lib/Driver/ToolChains/Clang.cpp | 14 +- clang/test/Driver/aarch64-security-options.c | 20 +++ 5 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 clang/docs/ReturnAddressSigningHardening.rst diff --git a/clang/docs/ReturnAddressSigningHardening.rst b/clang/docs/ReturnAddressSigningHardening.rst new file mode 100644 index 0000000000000..063132d6e6f0b --- /dev/null +++ b/clang/docs/ReturnAddressSigningHardening.rst @@ -0,0 +1,178 @@ +################################## + Return Address Signing Hardening +################################## + +.. contents:: + :local: + +************** + Introduction +************** + +Return Address Signing Hardening is a mitigation against the PACMAN +attack, which aims to bypass Pointer Authentication on AArch64 targets. +The hardening mechanism described here is specific to pointer +authentication of return addresses. + +Return Address Signing, also known as Pointer Authentication Code +(PAC-RET), is a feature devised to protect programs against Return +Oriented Programming (ROP), in which attackers may hijack the return +address of functions in order to direct execution to malicious code. + +PAC-RET can be enabled via different command-line options: + + - ``-mbranch-protection=`` with ``pac-ret``, ``pac-ret+leaf`` or + ``standard`` as value. + - ``-msign-return-address=`` with ``non-leaf`` or ``all`` as value. + - ``-fptrauth-returns``. + +More information can be found in :doc:`Pointer Authentication +<PointerAuthentication>`. + +Return Address Signing Hardening is a mechanism to strenghthen Return +Address Signing against the PACMAN attack in AArch64 targets. It can be +enabled with ``-mharden-pac-ret=load-return-address``. + +*************** + PACMAN attack +*************** + +PACMAN is an attack that aims to extract valuable information about +pointer authentication codes using side-channels in speculative +execution. + +It is performed with the use of gadgets to try and guess PAC codes. +These guesses raise no faults because they are done in speculation. By +observing the effects of the guessed PAC code on the processor's cache, +it might be possible to determine the valid PAC code for the address to +which the attacker wants the program to return. + +A usual PACMAN gadget looks like this: + +.. code:: C + + void function() { + ... + if (condition) + return; + ... + } + +Such code would be compiled to: + +.. code:: asm + + paciasp + ... + cbz w0, .LBB0_2 + autiasp + ret + ... + +This code segment may be used as a gadget. A speculative execution of +this segment can happen as follows: + + - If the Link Register (LR) has the right PAC code, ``autiasp`` will + succeed and strip the PAC code out of the it. The processor's + instruction fetcher will then bring the code after the return into + the cache (that is, the instructions located at the address + pointed by LR). + + - If the LR has the wrong PAC code, ``autiasp`` will not succeed and + hence will write a predefined error value to the LR's higher bits. + Because of this, the instruction fetcher will not bring the code + after the return into the cache. + +This difference in behavior is what drives the PACMAN attack. An +attacker can try to guess PAC codes and monitor cache behavior until the +code after the return is observed to have been brought into the cache. + +Details can be found in https://pacmanattack.com. + +*********** + Hardening +*********** + +In order to mitigate the PACMAN attack, a hardening mechanism can be +enabled with ``-mharden-pac-ret=load-return-address``. + +.. code:: asm + + paciasp + ... + cbz w0, .LBB0_2 + autiasp + mov x8, x30 + xpaclri + ldr w30, [x30] + ret x8 + ... + +The idea is to always bring the code after the return into cache (the +instructions located at the address pointed by LR), therefore minimizing +the difference between a speculative execution with a correct PAC code +and with an incorrect one. + + - ``autiasp`` performs the authentication step. + - ``mov x8, x30`` copies the return address (LR and x30 are + synonyms) to a temporary. + - ``xpaclri`` strips the PAC code out of the return address in x30. + - ``ldr w30, [x30]`` performs a load of the return address in x30. + - ``ret x8`` returns to the authenticated return address. + +The load operation brings the code into the cache even if the +authentication step fails. As a consequence, in either case the code is +loaded into the cache. Furthermore, the return operation uses the +original return address before stripping, so the return address +protection is still kept in place in a normal non-speculative execution. + +If FEAT_PAUTH is present, the code sequence can use instructions only +available with said feature with no change in semantics: + +.. code:: asm + + autiasp + mov x8, x30 + xpaci x8 + ldr w8, [x8] + ret + +********************* + Command-line option +********************* + +Return address signing hardening can be enabled at module level with +``-mharden-pac-ret=load-return-address``. It requires PAC-RET to be +enabled at module level as well. + +******************** + Function attribute +******************** + +In addition to the command-line option +``-mharden-pac-ret=load-return-address``, the developer can enable the +mitigation at function level with the use of the corresponding function +attribute. + +.. code:: C + + __attribute__((target("branch-protection=pac-ret,harden-pac-ret=load-return-address"))) + void function() { + ... + } + +********* + Caveats +********* + +The load of return address brings the code into the shared +instruction/data cache, therefore this cache level can't be used as an +oracle to find out whether the authentication succeeded or not. However, +in the case of authentication success, the code is also fetched into the +instruction cache. An attacker who is able to measure this cache level +specifically may still be able to carry out the exploit. + +Another caveat is if the code at the return address contains a load +operation within the speculation window. If this is the case, this load +will only execute speculatively if authentication succeeds, thus opening +the program up for exploitation despite the mitigation. diff --git a/clang/docs/index.rst b/clang/docs/index.rst index 70c8737a2fe0d..92120f9559a93 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -47,6 +47,7 @@ Using Clang as a Compiler ControlFlowIntegrity LTOVisibility PointerAuthentication + ReturnAddressSigningHardening SafeStack ShadowCallStack SourceBasedCodeCoverage @@ -128,4 +129,3 @@ Indices and tables * :ref:`genindex` * :ref:`search` - diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 3ac001d05be82..4268f0530816f 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -5525,7 +5525,7 @@ def msign_return_address_EQ : Joined<["-"], "msign-return-address=">, def mharden_pac_ret_EQ : Joined<["-"], "mharden-pac-ret=">, Visibility<[ClangOption, CC1Option]>, Flags<[TargetSpecific]>, Group<m_Group>, - HelpText<"Select the return address signing hardening scheme. <arg> must be: none, load-return-address">, + HelpText<"Select the return address signing hardening scheme">, Values<"none,load-return-address">, NormalizedValues<["None", "LoadReturnAddress"]>, NormalizedValuesScope<"LangOptions::SignReturnAddressHardeningKind">, MarshallingInfoEnum<LangOpts<"SignReturnAddressHardening">, "None">; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index ed200b6ec3ec9..a1ba6f5aed527 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -1383,9 +1383,10 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, // Check CmdArgs because some toolchains bypass the driver args and add to // the frontend args directly. - bool HasPtrauthReturns = llvm::is_contained(CmdArgs, "-fptrauth-returns") || - Args.hasArgNoClaim(options::OPT_fno_ptrauth_returns, - options::OPT_fptrauth_returns); + bool HasPtrauthReturns = + llvm::is_contained(CmdArgs, "-fptrauth-returns") || + Args.hasFlagNoClaim(options::OPT_fptrauth_returns, + options::OPT_fno_ptrauth_returns, false); if (HardenPACRetArg) { if (!isAArch64) { @@ -1399,6 +1400,13 @@ static void CollectARMPACBTIOptions(const ToolChain &TC, const ArgList &Args, << HardenPACRetArg->getSpelling() << ArgValue; return; } + if (ArgValue != "none" && + Args.hasFlagNoClaim(options::OPT_mexecute_only, + options::OPT_mno_execute_only, false)) { + D.Diag(diag::err_drv_incompatible_options) + << HardenPACRetArg->getAsString(Args) << "-mexecute-only"; + return; + } } if (!A && Triple.isOSOpenBSD() && isAArch64) { diff --git a/clang/test/Driver/aarch64-security-options.c b/clang/test/Driver/aarch64-security-options.c index 321205abd8e1a..dbac73d1f9769 100644 --- a/clang/test/Driver/aarch64-security-options.c +++ b/clang/test/Driver/aarch64-security-options.c @@ -13,6 +13,9 @@ // RUN: %clang --target=aarch64 -c %s -### -mbranch-protection=standard 2>&1 | \ // RUN: FileCheck %s --check-prefix=RA-NON-LEAF --check-prefix=KEY-A --check-prefix=BTE-ON --check-prefix=GCS-ON --check-prefix=WARN +// RUN: not %clang -target arm64-apple-darwin -c %s -### -mbranch-protection=standard -fptrauth-returns 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-INCOMPATIBLE-PTRAUTHRETURNS + // If the -msign-return-address and -mbranch-protection are both used, the // right-most one controls return address signing. // RUN: %clang --target=aarch64 -c %s -### -msign-return-address=non-leaf -mbranch-protection=none 2>&1 | \ @@ -43,15 +46,23 @@ // RUN: %clang -target aarch64 -c %s -### -mbranch-protection=none -mharden-pac-ret=none 2>&1 | \ // RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN +// RUN: %clang -target arm64-apple-darwin -c %s -### -fno-ptrauth-returns -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN // RUN: %clang -target aarch64 -c %s -### -mbranch-protection=none -mharden-pac-ret=load-return-address 2>&1 | \ // RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN +// RUN: %clang -target arm64-apple-darwin -c %s -### -fno-ptrauth-returns -mharden-pac-ret=load-return-address 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=NO-RA-HARDEN // RUN: %clang -target aarch64 -c %s -### -mbranch-protection=pac-ret -mharden-pac-ret=none 2>&1 | \ // RUN: FileCheck %s --check-prefixes=RA-HARDEN-NONE +// RUN: %clang -target arm64-apple-darwin -c %s -### -fptrauth-returns -mharden-pac-ret=none 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-HARDEN-NONE // RUN: %clang -target aarch64 -c %s -### -mbranch-protection=pac-ret -mharden-pac-ret=load-return-address 2>&1 | \ // RUN: FileCheck %s --check-prefixes=RA-HARDEN-LRA +// RUN: %clang -target arm64-apple-darwin -c %s -### -fptrauth-returns -mharden-pac-ret=load-return-address 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-HARDEN-LRA // RUN: %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=none 2>&1 | \ // RUN: FileCheck %s --check-prefixes=RA-HARDEN-NONE @@ -61,6 +72,12 @@ // RUN: not %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=foo 2>&1 | \ // RUN: FileCheck %s --check-prefixes=BAD-HARDEN-PROTECTION +// RUN: not %clang -target arm64-apple-darwin -c %s -### -fptrauth-returns -mharden-pac-ret=foo 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=BAD-HARDEN-PROTECTION + +// RUN: not %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=load-return-address -mexecute-only 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=RA-HARDEN-INCOMPATIBLE-EXEC-ONLY +// RUN: %clang -target aarch64 -c %s -### -mbranch-protection=standard -mharden-pac-ret=none -mexecute-only // ABSENT-RA-HARDEN-NOT: "-mharden-pac-ret" // NO-RA-HARDEN: ignoring '-mharden-pac-ret' as it requires return address signing @@ -68,6 +85,7 @@ // RA-HARDEN-NONE: "-mharden-pac-ret=none" // RA-HARDEN-LRA: "-mharden-pac-ret=load-return-address" // BAD-HARDEN-PROTECTION: unsupported argument 'foo' to option '-mharden-pac-ret=' +// RA-HARDEN-INCOMPATIBLE-EXEC-ONLY: the combination of '-mharden-pac-ret=load-return-address' and '-mexecute-only' is incompatible // RA-OFF: "-msign-return-address=none" // RA-NON-LEAF: "-msign-return-address=non-leaf" @@ -76,6 +94,8 @@ // KEY-A: "-msign-return-address-key=a_key" // KEY-NOT: "-msign-return-address-key" +// RA-INCOMPATIBLE-PTRAUTHRETURNS: the combination of '-mbranch-protection=standard' and '-fptrauth-returns' is incompatible + // BTE-OFF-NOT: "-mbranch-target-enforce" // BTE-ON: "-mbranch-target-enforce" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
