diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1586,7 +1586,7 @@ /// Extra information which affects how the function is called, like /// regparm and the calling convention. - unsigned ExtInfo : 13; + unsigned ExtInfo : 14; /// The ref-qualifier associated with a \c FunctionProtoType. /// @@ -3662,8 +3662,8 @@ // adjust the Bits field below, and if you add bits, you'll need to adjust // Type::FunctionTypeBitfields::ExtInfo as well. - // | CC |noreturn|produces|nocallersavedregs|regparm|nocfcheck|cmsenscall| - // |0 .. 4| 5 | 6 | 7 |8 .. 10| 11 | 12 | + // | CC |noreturn|produces|nocallersavedregs|regparm|nocfcheck|cfcheck|cmsenscall| + // |0 .. 4| 5 | 6 | 7 |8 .. 10| 11 | 12 | 13 | // // regparm is either 0 (no regparm attribute) or the regparm value+1. enum { CallConvMask = 0x1F }; @@ -3675,7 +3675,8 @@ RegParmOffset = 8 }; enum { NoCfCheckMask = 0x800 }; - enum { CmseNSCallMask = 0x1000 }; + enum { CfCheckMask = 0x1000 }; + enum { CmseNSCallMask = 0x2000 }; uint16_t Bits = CC_C; ExtInfo(unsigned Bits) : Bits(static_cast(Bits)) {} @@ -3685,13 +3686,14 @@ // have all the elements (when reading an AST file for example). ExtInfo(bool noReturn, bool hasRegParm, unsigned regParm, CallingConv cc, bool producesResult, bool noCallerSavedRegs, bool NoCfCheck, - bool cmseNSCall) { + bool CfCheck, bool cmseNSCall) { assert((!hasRegParm || regParm < 7) && "Invalid regparm value"); Bits = ((unsigned)cc) | (noReturn ? NoReturnMask : 0) | (producesResult ? ProducesResultMask : 0) | (noCallerSavedRegs ? NoCallerSavedRegsMask : 0) | (hasRegParm ? ((regParm + 1) << RegParmOffset) : 0) | (NoCfCheck ? NoCfCheckMask : 0) | + (CfCheck ? CfCheckMask : 0) | (cmseNSCall ? CmseNSCallMask : 0); } @@ -3708,6 +3710,7 @@ bool getCmseNSCall() const { return Bits & CmseNSCallMask; } bool getNoCallerSavedRegs() const { return Bits & NoCallerSavedRegsMask; } bool getNoCfCheck() const { return Bits & NoCfCheckMask; } + bool getCfCheck() const { return Bits & CfCheckMask; } bool getHasRegParm() const { return ((Bits & RegParmMask) >> RegParmOffset) != 0; } unsigned getRegParm() const { @@ -3764,6 +3767,13 @@ return ExtInfo(Bits & ~NoCfCheckMask); } + ExtInfo withCfCheck(bool cfCheck) const { + if (cfCheck) + return ExtInfo(Bits | CfCheckMask); + else + return ExtInfo(Bits & ~CfCheckMask); + } + ExtInfo withRegParm(unsigned RegParm) const { assert(RegParm < 7 && "Invalid regparm value"); return ExtInfo((Bits & ~RegParmMask) | diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td --- a/clang/include/clang/AST/TypeProperties.td +++ b/clang/include/clang/AST/TypeProperties.td @@ -284,6 +284,9 @@ def : Property<"noCfCheck", Bool> { let Read = [{ node->getExtInfo().getNoCfCheck() }]; } + def : Property<"cfCheck", Bool> { + let Read = [{ node->getExtInfo().getCfCheck() }]; + } def : Property<"cmseNSCall", Bool> { let Read = [{ node->getExtInfo().getCmseNSCall() }]; } @@ -294,7 +297,7 @@ auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm, callingConvention, producesResult, noCallerSavedRegs, noCfCheck, - cmseNSCall); + cfCheck, cmseNSCall); return ctx.getFunctionNoProtoType(returnType, extInfo); }]>; } @@ -328,7 +331,7 @@ auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm, callingConvention, producesResult, noCallerSavedRegs, noCfCheck, - cmseNSCall); + cfCheck, cmseNSCall); FunctionProtoType::ExtProtoInfo epi; epi.ExtInfo = extInfo; epi.Variadic = variadic; diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2956,6 +2956,12 @@ let Documentation = [AnyX86NoCfCheckDocs]; } +def AnyX86CfCheck : DeclOrTypeAttr, TargetSpecificAttr{ + let Spellings = [GCC<"cf_check">]; + let Subjects = SubjectList<[FunctionLike]>; + let Documentation = [AnyX86NoCfCheckDocs]; +} + def X86ForceAlignArgPointer : InheritableAttr, TargetSpecificAttr { let Spellings = [GCC<"force_align_arg_pointer">]; // Technically, this appertains to a FunctionDecl, but the target-specific diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4599,6 +4599,10 @@ the function. 2. Appertains to a function pointer - do not track the target function of this pointer (by adding nocf_check prefix to the indirect-call instruction). +Conversely, when ``-mmannual-endbr`` is in effect, clang will refrain from +adding ENDBR instructions at the beginnings of functions; instead, users can +manually select which functions get the instruction with the use of the +``cf_check`` attribute. }]; } diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -108,7 +108,9 @@ CODEGENOPT(CFProtectionBranch , 1, 0) ///< if -fcf-protection is ///< set to full or branch. CODEGENOPT(IBTSeal, 1, 0) ///< set to optimize CFProtectionBranch. - +CODEGENOPT(ManualENDBR , 1, 0) ///< if -mmanual-endbr is set, which tells the + ///< compiler not to add ENDBR instructions to + ///< functions prologues automatically. CODEGENOPT(XRayInstrumentFunctions , 1, 0) ///< Set when -fxray-instrument is ///< enabled. CODEGENOPT(StackSizeSection , 1, 0) ///< Set when -fstack-size-section is enabled. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3323,6 +3323,9 @@ def warn_nocf_check_attribute_ignored : Warning<"'nocf_check' attribute ignored; use -fcf-protection to enable the attribute">, InGroup; +def warn_cf_check_attribute_ignored : + Warning<"'cf_check' attribute ignored; use -fcf-protection to enable the attribute">, + InGroup; def warn_attribute_after_definition_ignored : Warning< "attribute %0 after definition is ignored">, InGroup; diff --git a/clang/include/clang/CodeGen/CGFunctionInfo.h b/clang/include/clang/CodeGen/CGFunctionInfo.h --- a/clang/include/clang/CodeGen/CGFunctionInfo.h +++ b/clang/include/clang/CodeGen/CGFunctionInfo.h @@ -586,6 +586,9 @@ /// Whether this function has nocf_check attribute. unsigned NoCfCheck : 1; + /// Whether this function has cf_check attribute. + unsigned CfCheck : 1; + RequiredArgs Required; /// The struct representing all arguments passed in memory. Only used when @@ -674,6 +677,9 @@ /// Whether this function has nocf_check attribute. bool isNoCfCheck() const { return NoCfCheck; } + /// Whether this function has cf_check attribute. + bool isCfCheck() const { return CfCheck; } + /// getASTCallingConvention() - Return the AST-specified calling /// convention. CallingConv getASTCallingConvention() const { @@ -700,7 +706,7 @@ return FunctionType::ExtInfo(isNoReturn(), getHasRegParm(), getRegParm(), getASTCallingConvention(), isReturnsRetained(), isNoCallerSavedRegs(), isNoCfCheck(), - isCmseNSCall()); + isCfCheck(), isCmseNSCall()); } CanQualType getReturnType() const { return getArgsBuffer()[0].type; } @@ -741,6 +747,7 @@ ID.AddBoolean(HasRegParm); ID.AddInteger(RegParm); ID.AddBoolean(NoCfCheck); + ID.AddBoolean(CfCheck); ID.AddBoolean(CmseNSCall); ID.AddInteger(Required.getOpaqueData()); ID.AddBoolean(HasExtParameterInfos); @@ -769,6 +776,7 @@ ID.AddBoolean(info.getHasRegParm()); ID.AddInteger(info.getRegParm()); ID.AddBoolean(info.getNoCfCheck()); + ID.AddBoolean(info.getCfCheck()); ID.AddBoolean(info.getCmseNSCall()); ID.AddInteger(required.getOpaqueData()); ID.AddBoolean(!paramInfos.empty()); diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1931,6 +1931,8 @@ HelpText<"Enable cf-protection in 'full' mode">; def mibt_seal : Flag<["-"], "mibt-seal">, Group, Flags<[CoreOption, CC1Option]>, HelpText<"Optimize fcf-protection=branch/full (requires LTO).">; +def mmanual_endbr : Flag<["-"], "mmanual-endbr">, Group, Flags<[CoreOption, CC1Option]>, + HelpText<"Used with -fcf-protection, but tells the compiler to refrain from automatically adding ENDBR instructions to function prologues">; defm xray_instrument : BoolFOption<"xray-instrument", LangOpts<"XRayInstrument">, DefaultFalse, diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -9670,8 +9670,24 @@ return {}; if (lbaseInfo.getNoCallerSavedRegs() != rbaseInfo.getNoCallerSavedRegs()) return {}; + + // The following checks generate incompatible function pointer type + // warning when functions have cf_check/nocf_check attributes and get + // assigned to function pointers without them or vice-versa. + // For instance: + // + // int __attribute__((cf_check)) foo(void) { return 0 }; + // int (*fooptr)(void) = &foo; + // + // produces: + // + // warning: incompatible function pointer types initializing 'int (*)(void)' + // with an expression of type 'int (*)(void) __attribute__((cf_check))' + // [-Wincompatible-function-pointer-types] if (lbaseInfo.getNoCfCheck() != rbaseInfo.getNoCfCheck()) return {}; + if (lbaseInfo.getCfCheck() != rbaseInfo.getCfCheck()) + return {}; // FIXME: some uses, e.g. conditional exprs, really want this to be 'both'. bool NoReturn = lbaseInfo.getNoReturn() || rbaseInfo.getNoReturn(); diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -619,6 +619,8 @@ return false; if (EI1.getNoCfCheck() != EI2.getNoCfCheck()) return false; + if (EI1.getCfCheck() != EI2.getCfCheck()) + return false; return true; } diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1003,6 +1003,8 @@ OS << " __attribute__((no_caller_saved_registers))"; if (Info.getNoCfCheck()) OS << " __attribute__((nocf_check))"; + if (Info.getCfCheck()) + OS << " __attribute__((cf_check))"; } void TypePrinter::printFunctionNoProtoBefore(const FunctionNoProtoType *T, @@ -1724,6 +1726,7 @@ // FIXME: When Sema learns to form this AttributedType, avoid printing the // attribute again in printFunctionProtoAfter. case attr::AnyX86NoCfCheck: OS << "nocf_check"; break; + case attr::AnyX86CfCheck: OS << "cf_check"; break; case attr::CDecl: OS << "cdecl"; break; case attr::FastCall: OS << "fastcall"; break; case attr::StdCall: OS << "stdcall"; break; diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -824,6 +824,7 @@ FI->ReturnsRetained = info.getProducesResult(); FI->NoCallerSavedRegs = info.getNoCallerSavedRegs(); FI->NoCfCheck = info.getNoCfCheck(); + FI->CfCheck = info.getCfCheck(); FI->Required = required; FI->HasRegParm = info.getHasRegParm(); FI->RegParm = info.getRegParm(); @@ -2108,6 +2109,8 @@ FuncAttrs.addAttribute("no_caller_saved_registers"); if (TargetDecl->hasAttr()) FuncAttrs.addAttribute(llvm::Attribute::NoCfCheck); + if (TargetDecl->hasAttr()) + FuncAttrs.addAttribute(llvm::Attribute::CfCheck); if (TargetDecl->hasAttr()) FuncAttrs.addAttribute(llvm::Attribute::NoCallback); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -715,6 +715,14 @@ if (CodeGenOpts.IBTSeal) getModule().addModuleFlag(llvm::Module::Override, "ibt-seal", 1); + if (CodeGenOpts.ManualENDBR) { + // Indicate that the compiler should not automatically add ENDBR + // instructions to function prologues. Instead, each function that + // requires it must have the 'cf_check' attribute manually added in + // the source code. + getModule().addModuleFlag(llvm::Module::Override, "manual-endbr", 1); + } + // Add module metadata for return address signing (ignoring // non-leaf/all) and stack tagging. These are actually turned on by function // attributes, but we use module metadata to emit build attributes. This is diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6172,6 +6172,8 @@ if (IsUsingLTO) Args.AddLastArg(CmdArgs, options::OPT_mibt_seal); + Args.AddLastArg(CmdArgs, options::OPT_mmanual_endbr); + // Forward -f options with positive and negative forms; we translate these by // hand. Do not propagate PGO options to the GPU-side compilations as the // profile info is for the host-side compilation only. diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1476,6 +1476,9 @@ if (Opts.IBTSeal) GenerateArg(Args, OPT_mibt_seal, SA); + if (Opts.ManualENDBR) + GenerateArg(Args, OPT_mmanual_endbr, SA); + for (const auto &F : Opts.LinkBitcodeFiles) { bool Builtint = F.LinkFlags == llvm::Linker::Flags::LinkOnlyNeeded && F.PropagateAttrs && F.Internalize; @@ -1820,6 +1823,9 @@ if (Opts.PrepareForLTO && Args.hasArg(OPT_mibt_seal)) Opts.IBTSeal = 1; + if (Args.hasArg(OPT_mmanual_endbr)) + Opts.ManualENDBR = 1; + for (auto *A : Args.filtered(OPT_mlink_bitcode_file, OPT_mlink_builtin_bitcode)) { CodeGenOptions::BitcodeFileToLink F; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2173,6 +2173,13 @@ handleSimpleAttribute(S, D, Attrs); } +static void handleCfCheckAttr(Sema &S, Decl *D, const ParsedAttr &Attrs) { + if (!S.getLangOpts().CFProtectionBranch) + S.Diag(Attrs.getLoc(), diag::warn_cf_check_attribute_ignored); + else + handleSimpleAttribute(S, D, Attrs); +} + bool Sema::CheckAttrNoArgs(const ParsedAttr &Attrs) { if (!Attrs.checkExactlyNumArgs(*this, 0)) { Attrs.setInvalid(); @@ -8367,6 +8374,9 @@ case ParsedAttr::AT_AnyX86NoCfCheck: handleNoCfCheckAttr(S, D, AL); break; + case ParsedAttr::AT_AnyX86CfCheck: + handleCfCheckAttr(S, D, AL); + break; case ParsedAttr::AT_NoThrow: if (!AL.isUsedAsTypeAttr()) handleSimpleAttribute(S, D, AL); diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -135,6 +135,7 @@ case ParsedAttr::AT_Regparm: \ case ParsedAttr::AT_CmseNSCall: \ case ParsedAttr::AT_AnyX86NoCallerSavedRegisters: \ + case ParsedAttr::AT_AnyX86CfCheck: \ case ParsedAttr::AT_AnyX86NoCfCheck: \ CALLING_CONV_ATTRS_CASELIST @@ -7569,6 +7570,26 @@ return true; } + if (attr.getKind() == ParsedAttr::AT_AnyX86CfCheck) { + if (!S.getLangOpts().CFProtectionBranch) { + S.Diag(attr.getLoc(), diag::warn_cf_check_attribute_ignored); + attr.setInvalid(); + return true; + } + + if (S.CheckAttrTarget(attr) || S.CheckAttrNoArgs(attr)) + return true; + + // If this is not a function type, warning will be asserted by subject + // check. + if (!unwrapped.isFunctionType()) + return true; + + FunctionType::ExtInfo EI = unwrapped.get()->getExtInfo().withCfCheck(true); + type = unwrapped.wrap(S, S.Context.adjustFunctionType(unwrapped.get(), EI)); + return true; + } + if (attr.getKind() == ParsedAttr::AT_Regparm) { unsigned value; if (S.CheckRegparmAttr(attr, value)) diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -602,6 +602,7 @@ Abv->Add(BitCodeAbbrevOp(0)); // ProducesResult Abv->Add(BitCodeAbbrevOp(0)); // NoCallerSavedRegs Abv->Add(BitCodeAbbrevOp(0)); // NoCfCheck + Abv->Add(BitCodeAbbrevOp(0)); // CfCheck Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // CmseNSCall // FunctionProtoType Abv->Add(BitCodeAbbrevOp(0)); // IsVariadic diff --git a/clang/test/CodeGen/X86/x86-mmanual-endbr.c b/clang/test/CodeGen/X86/x86-mmanual-endbr.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/X86/x86-mmanual-endbr.c @@ -0,0 +1,4 @@ +// RUN: %clang -target i386-unknown-unknown -o - -emit-llvm -S -fcf-protection=branch -mmanual-endbr %s | FileCheck %s --check-prefix=MANUALENDBR + +// MANUALENDBR: "manual-endbr", i32 1 +__attribute__((cf_check)) void foo() {} diff --git a/clang/test/CodeGen/attributes.c b/clang/test/CodeGen/attributes.c --- a/clang/test/CodeGen/attributes.c +++ b/clang/test/CodeGen/attributes.c @@ -117,6 +117,12 @@ (*p)(); } +// CHECK: call void %{{[a-z0-9]+}}() [[CF_CHECK_CALL:#[0-9]+]] +void t25(f_t f1) { + __attribute__((cf_check)) f_t p = f1; + (*p)(); +} + // CHECK: attributes [[NUW]] = { noinline nounwind{{.*}} } // CHECK: attributes [[NR]] = { noinline noreturn nounwind{{.*}} } // CHECK: attributes [[COLDDEF]] = { cold {{.*}}} @@ -127,3 +133,4 @@ // CHECK: attributes [[COLDSITE]] = { cold {{.*}}} // CHECK: attributes [[HOTSITE]] = { hot {{.*}}} // CHECK: attributes [[NOCF_CHECK_CALL]] = { nocf_check } +// CHECK: attributes [[CF_CHECK_CALL]] = { cf_check } diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -15,6 +15,7 @@ // CHECK-NEXT: AlwaysDestroy (SubjectMatchRule_variable) // CHECK-NEXT: AlwaysInline (SubjectMatchRule_function) // CHECK-NEXT: Annotate () +// CHECK-NEXT: AnyX86CfCheck (SubjectMatchRule_hasType_functionType) // CHECK-NEXT: AnyX86NoCfCheck (SubjectMatchRule_hasType_functionType) // CHECK-NEXT: ArcWeakrefUnavailable (SubjectMatchRule_objc_interface) // CHECK-NEXT: ArmBuiltinAlias (SubjectMatchRule_function) diff --git a/clang/test/Sema/attr-cf_check.c b/clang/test/Sema/attr-cf_check.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-cf_check.c @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple=x86_64-unknown-unknown -verify -fcf-protection=branch -fsyntax-only %s + +// Function pointer definition. +typedef void (*FuncPointerWithCfCheck)(void) __attribute__((cf_check)); // no-warning +typedef void (*FuncPointer)(void); + +// Dont allow function declaration and definition mismatch. +void __attribute__((cf_check)) testCfCheck(); // expected-note {{previous declaration is here}} +void testCfCheck(){}; // expected-error {{conflicting types for 'testCfCheck'}} + +// No variable or parameter declaration +__attribute__((cf_check)) int i; // expected-warning {{'cf_check' attribute only applies to function}} +void testCfCheckImpl(double __attribute__((cf_check)) i) {} // expected-warning {{'cf_check' attribute only applies to function}} + +// Allow attributed function pointers as well as casting between attributed +// and non-attributed function pointers. +void testCfCheckMismatch(FuncPointer f) { + FuncPointerWithCfCheck fCfCheck = f; // expected-warning {{incompatible function pointer types}} + (*fCfCheck)(); // no-warning +} + +// 'cf_check' Attribute has no parameters. +int testCfCheckParams() __attribute__((cf_check(1))); // expected-error {{'cf_check' attribute takes no arguments}} diff --git a/clang/test/Sema/attr-cf_check.cpp b/clang/test/Sema/attr-cf_check.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-cf_check.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple=i386-unknown-unknown -verify -fcf-protection=branch -std=c++11 -fsyntax-only %s + +// Function pointer definition. +[[gnu::cf_check]] typedef void (*FuncPointerWithCfCheck)(void); // no-warning +typedef void (*FuncPointer)(void); + +// Dont allow function declaration and definition mismatch. +[[gnu::cf_check]] void testCfCheck(); // expected-note {{previous declaration is here}} +void testCfCheck(){}; // expected-error {{conflicting types for 'testCfCheck'}} + +// No variable or parameter declaration +int [[gnu::cf_check]] i; // expected-error {{'cf_check' attribute cannot be applied to types}} +void testCfCheckImpl(double i [[gnu::cf_check]]) {} // expected-warning {{'cf_check' attribute only applies to functions and function pointers}} + +// Allow attributed function pointers as well as casting between attributed +// and non-attributed function pointers. +void testCfCheckMismatch(FuncPointer f) { + FuncPointerWithCfCheck fCfCheck = f; // expected-error {{cannot initialize a variable of type}} + (*fCfCheck)(); // no-warning +} + +// 'cf_check' Attribute has no parameters. +[[gnu::cf_check(1)]] int testCfCheckParams(); // expected-error {{'cf_check' attribute takes no arguments}} diff --git a/clang/test/Sema/cf_check_attr_not_allowed.c b/clang/test/Sema/cf_check_attr_not_allowed.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/cf_check_attr_not_allowed.c @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -triple powerpc-unknown-linux-gnu -fsyntax-only -verify -fcf-protection=branch %s +// RUN: %clang_cc1 -triple arm-unknown-linux-gnu -fsyntax-only -verify -fcf-protection=branch %s +// RUN: %clang_cc1 -triple arm-unknown-linux-gnu -fsyntax-only -verify %s + +void __attribute__((cf_check)) foo(); // expected-warning-re{{{{((unknown attribute 'cf_check' ignored)|('cf_check' attribute ignored; use -fcf-protection to enable the attribute))}}}} diff --git a/llvm/bindings/go/llvm/ir_test.go b/llvm/bindings/go/llvm/ir_test.go --- a/llvm/bindings/go/llvm/ir_test.go +++ b/llvm/bindings/go/llvm/ir_test.go @@ -87,6 +87,7 @@ "zeroext", "cold", "nocf_check", + "cf_check", } for _, name := range attrTests { diff --git a/llvm/docs/BitCodeFormat.rst b/llvm/docs/BitCodeFormat.rst --- a/llvm/docs/BitCodeFormat.rst +++ b/llvm/docs/BitCodeFormat.rst @@ -1078,6 +1078,7 @@ * code 76: ``nosanitize_coverage`` * code 77: ``elementtype`` * code 78: ``disable_sanitizer_instrumentation`` +* code 79: ``cf_check`` .. note:: The ``allocsize`` attribute has a special encoding for its arguments. Its two diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -2115,6 +2115,13 @@ entity to fine grain the HW control flow protection mechanism. The flag is target independent and currently appertains to a function or function pointer. +``cf_check`` + This attribute manually indicates that control-flow check will be performed + on the attributed entity. It only makes sense together with -mmanual-endbr, + which tells the compiler that -fcf-protection=<> should not guess which + functions need HW control flow protection. Instead, functions that need it + will have the ``cf_check`` attribute manually added to the source code. The + flag is target independent and appertains only to functions. ``shadowcallstack`` This attribute indicates that the ShadowCallStack checks are enabled for the function. The instrumentation checks that the return address for the diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h --- a/llvm/include/llvm/AsmParser/LLToken.h +++ b/llvm/include/llvm/AsmParser/LLToken.h @@ -218,6 +218,7 @@ kw_noreturn, kw_nosync, kw_nocf_check, + kw_cf_check, kw_nounwind, kw_nosanitize_coverage, kw_null_pointer_is_valid, diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -677,6 +677,7 @@ ATTR_KIND_NO_SANITIZE_COVERAGE = 76, ATTR_KIND_ELEMENTTYPE = 77, ATTR_KIND_DISABLE_SANITIZER_INSTRUMENTATION = 78, + ATTR_KIND_CF_CHECK = 79, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -169,6 +169,9 @@ /// Disable Indirect Branch Tracking. def NoCfCheck : EnumAttr<"nocf_check", [FnAttr]>; +/// Enable Indirect Branch Tracking. +def CfCheck : EnumAttr<"cf_check", [FnAttr]>; + /// Function should not be instrumented. def NoProfile : EnumAttr<"noprofile", [FnAttr]>; diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h --- a/llvm/include/llvm/IR/Function.h +++ b/llvm/include/llvm/IR/Function.h @@ -552,6 +552,10 @@ /// Determine if the function should not perform indirect branch tracking. bool doesNoCfCheck() const { return hasFnAttribute(Attribute::NoCfCheck); } + /// Determine if the function is manually set to perform indirect branch + /// tracking. + bool doesCfCheck() const { return hasFnAttribute(Attribute::CfCheck); } + /// Determine if the function cannot unwind. bool doesNotThrow() const { return hasFnAttribute(Attribute::NoUnwind); diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h --- a/llvm/include/llvm/IR/InstrTypes.h +++ b/llvm/include/llvm/IR/InstrTypes.h @@ -1866,6 +1866,9 @@ /// Determine if the call should not perform indirect branch tracking. bool doesNoCfCheck() const { return hasFnAttr(Attribute::NoCfCheck); } + /// Determine if the call could perform indirect branch tracking. + bool doesCfCheck() const { return hasFnAttr(Attribute::CfCheck); } + /// Determine if the call cannot unwind. bool doesNotThrow() const { return hasFnAttr(Attribute::NoUnwind); } void setDoesNotThrow() { addFnAttr(Attribute::NoUnwind); } diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -670,6 +670,7 @@ KEYWORD(noreturn); KEYWORD(nosync); KEYWORD(nocf_check); + KEYWORD(cf_check); KEYWORD(noundef); KEYWORD(nounwind); KEYWORD(nosanitize_coverage); diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1447,6 +1447,8 @@ return Attribute::NoSync; case bitc::ATTR_KIND_NOCF_CHECK: return Attribute::NoCfCheck; + case bitc::ATTR_KIND_CF_CHECK: + return Attribute::CfCheck; case bitc::ATTR_KIND_NO_PROFILE: return Attribute::NoProfile; case bitc::ATTR_KIND_NO_UNWIND: diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -684,6 +684,8 @@ return bitc::ATTR_KIND_NOSYNC; case Attribute::NoCfCheck: return bitc::ATTR_KIND_NOCF_CHECK; + case Attribute::CfCheck: + return bitc::ATTR_KIND_CF_CHECK; case Attribute::NoProfile: return bitc::ATTR_KIND_NO_PROFILE; case Attribute::NoUnwind: diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp --- a/llvm/lib/Target/X86/X86ISelLowering.cpp +++ b/llvm/lib/Target/X86/X86ISelLowering.cpp @@ -4164,6 +4164,7 @@ bool HasNCSR = (CB && isa(CB) && CB->hasFnAttr("no_caller_saved_registers")); bool HasNoCfCheck = (CB && CB->doesNoCfCheck()); + bool HasCfCheck = (CB && CB->doesCfCheck()); bool IsIndirectCall = (CB && isa(CB) && CB->isIndirectCall()); const Module *M = MF.getMMI().getModule(); Metadata *IsCFProtectionSupported = M->getModuleFlag("cf-protection-branch"); @@ -4661,6 +4662,7 @@ } if (HasNoCfCheck && IsCFProtectionSupported && IsIndirectCall) { + assert(!HasCfCheck && "cf_check and nocf_check used at the same time"); Chain = DAG.getNode(X86ISD::NT_CALL, dl, NodeTys, Ops); } else if (CLI.CB && objcarc::hasAttachedCallOpBundle(CLI.CB)) { // Calls with a "clang.arc.attachedcall" bundle are special. They should be diff --git a/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp b/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp --- a/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp +++ b/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp @@ -23,6 +23,7 @@ #include "llvm/CodeGen/MachineFunctionPass.h" #include "llvm/CodeGen/MachineInstrBuilder.h" #include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/IR/DiagnosticInfo.h" using namespace llvm; @@ -131,6 +132,7 @@ const Module *M = MF.getMMI().getModule(); // Check that the cf-protection-branch is enabled. Metadata *isCFProtectionSupported = M->getModuleFlag("cf-protection-branch"); + Metadata *ManualENDBR = M->getModuleFlag("manual-endbr"); // NB: We need to enable IBT in jitted code if JIT compiler is CET // enabled. @@ -152,8 +154,25 @@ // If function is reachable indirectly, mark the first BB with ENDBR. if (needsPrologueENDBR(MF, M)) { - auto MBB = MF.begin(); - Changed |= addENDBR(*MBB, MBB->begin()); + if (!ManualENDBR || MF.getFunction().doesCfCheck()) { + auto MBB = MF.begin(); + Changed |= addENDBR(*MBB, MBB->begin()); + } else { + // When -mmanual-endbr is in effect, the compiler does not + // automatically add ENDBR instructions at the entry points of any + // functions, unless the function has the 'cf_check' attribute. + // Thus, every function that would need it (i.e. functions that + // could be jumped into from a function pointer (e.g. address + // taken functions)) should have the 'cf_check' attribute manually + // added to it. When they don't, the following diagnostics message + // helps programmers find the missing attribute. + DiagnosticInfoOptimizationFailure Diag( + MF.getFunction(), NULL, + "Function is possibly accessed from indirect branch, " + "but has no 'cf_check' attribute"); + LLVMContext &Ctx = MF.getFunction().getContext(); + Ctx.diagnose(Diag); + } } for (auto &MBB : MF) { diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -958,6 +958,7 @@ case Attribute::UWTable: case Attribute::VScaleRange: case Attribute::NoCfCheck: + case Attribute::CfCheck: case Attribute::MustProgress: case Attribute::NoProfile: break; diff --git a/llvm/test/CodeGen/X86/cf_check.ll b/llvm/test/CodeGen/X86/cf_check.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/cf_check.ll @@ -0,0 +1,62 @@ +; RUN: llc -mtriple=x86_64-unknown-unknown -x86-indirect-branch-tracking < %s | FileCheck %s + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This test verifies the handling of ''cf_check'' attribute by the backend. ;; +;; The file was generated using the following C code: ;; +;; ;; +;; void NoCfCheckFunc(void) {} ;; +;; void __attribute__((cf_check)) CfCheckFunc(void) {} ;; +;; ;; +;; typedef void(*FuncPointer)(void); ;; +;; void CfCheckCall(FuncPointer f) { ;; +;; __attribute__((cf_check)) FuncPointer p = f; ;; +;; (*p)(); ;; +;; CfCheckFunc(); ;; +;; } ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Make sure that a function without ''*cf_check'' attributes is not instrumented +; with endbr instruction when ''-mmanual-endbr'' is in effect +define void @NoCfCheckFunc() #0 { +; CHECK-LABEL: NoCfCheckFunc +; CHECK-NOT: endbr64 +entry: + ret void +} + +; Make sure that a function with ''cf_check'' attribute is instrumented +; with endbr instruction at the beginning. +define void @CfCheckFunc() #1 { +; CHECK-LABEL: CfCheckFunc +; CHECK: endbr64 +entry: + ret void +} + +; Ensure the notrack prefix is not added before an indirect call using a pointer +; with ''cf_check'' attribute. Also ensure a direct call to a function with +; the ''cf_check'' attribute is correctly generated without notrack prefix. +define void @CfCheckCall(void ()* %0) #0 { +; CHECK-LABEL: CfCheckCall +; CHECK-NOT: notrack call +; CHECK: callq CfCheckFunc +entry: + %1 = alloca void ()*, align 8 + %2 = alloca void ()*, align 8 + store void ()* %0, void ()** %1, align 8 + %3 = load void ()*, void ()** %1, align 8 + store void ()* %3, void ()** %2, align 8 + %4 = load void ()*, void ()** %2, align 8 + call void %4() #2 + call void @CfCheckFunc() #2 + ret void +} + +attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { cf_check noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #2 = { cf_check } + +!llvm.module.flags = !{!0, !1} + +!0 = !{i32 4, !"cf-protection-branch", i32 1} +!1 = !{i32 4, !"manual-endbr", i32 1} diff --git a/llvm/test/CodeGen/X86/missing_cf_check.ll b/llvm/test/CodeGen/X86/missing_cf_check.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/missing_cf_check.ll @@ -0,0 +1,71 @@ +; RUN: llc -mtriple=x86_64-unknown-unknown -x86-indirect-branch-tracking < %s 2>&1 | FileCheck %s + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Test that the compiler generates diagnostic messages for function pointer ;; +;; assignments in -mmanual-endbr mode. ;; +;; This program has been generated from the following source: ;; +;; ;; +;; __attribute__((nocf_check)) void foo(void) {} ;; +;; __attribute__((cf_check)) void bar(void) {} ;; +;; void baz(void) {} ;; +;; ;; +;; void foobar(void) { ;; +;; void (*fooptr)(void) = &foo; /* Should always warn. */ ;; +;; void (*barptr)(void) = &bar; /* Should never warn. */ ;; +;; void (*bazptr)(void) = &baz; /* Should warn with -mmanual-endbr. */ ;; +;; (*fooptr)(); ;; +;; (*barptr)(); ;; +;; (*bazptr)(); ;; +;; } ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Ensure that functions with 'nocf_check' attribute generate a +; diagnostics message when assigned to function pointer. +define dso_local void @foo() #0 { +entry: + ret void +} + +; No warning should be generated for this function +define dso_local void @bar() #1 { +entry: + ret void +} + +; Ensure that functions without 'cf_check' attribute generate a +; diagnostics message when assigned to function pointer in +; -mmanual-endbr mode. +define dso_local void @baz() #2 { +entry: + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @foobar() #2 { +entry: + %0 = alloca void ()*, align 8 + %1 = alloca void ()*, align 8 + %2 = alloca void ()*, align 8 + store void ()* @foo, void ()** %0, align 8 + store void ()* @bar, void ()** %1, align 8 + store void ()* @baz, void ()** %2, align 8 + %3 = load void ()*, void ()** %0, align 8 + call void %3() + %4 = load void ()*, void ()** %1, align 8 + call void %4() + %5 = load void ()*, void ()** %2, align 8 + call void %5() + ret void +} + +attributes #0 = { nocf_check noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { cf_check noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #2 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.module.flags = !{!0, !1} + +!0 = !{i32 4, !"cf-protection-branch", i32 1} +!1 = !{i32 4, !"manual-endbr", i32 1} + +; CHECK: warning: :0:0: Function is possibly accessed from indirect branch, but has no 'cf_check' attribute +; CHECK: warning: :0:0: Function is possibly accessed from indirect branch, but has no 'cf_check' attribute