diff --git a/clang/docs/ClangCommandLineReference.rst b/clang/docs/ClangCommandLineReference.rst --- a/clang/docs/ClangCommandLineReference.rst +++ b/clang/docs/ClangCommandLineReference.rst @@ -959,6 +959,10 @@ Generalize pointers in CFI indirect call type signature checks +.. option:: -fsanitize-kcfi-offset= + +Set the number of bytes between the KCFI type identifier and the start of the function. + .. option:: -fsanitize-coverage-allowlist=, -fsanitize-coverage-whitelist= Restrict sanitizer coverage instrumentation exclusively to modules and functions that match the provided special case list, except the blocked ones diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -1666,6 +1666,8 @@ flow analysis. - ``-fsanitize=cfi``: :doc:`control flow integrity ` checks. Requires ``-flto``. + - ``-fsanitize=kcfi``: kernel indirect call forward-edge control flow + integrity. - ``-fsanitize=safe-stack``: :doc:`safe stack ` protection against stack-based memory corruption errors. @@ -3939,6 +3941,8 @@ Enable control flow integrity (CFI) checks for cross-DSO calls. -fsanitize-cfi-icall-generalize-pointers Generalize pointers in CFI indirect call type signature checks + -fsanitize-kcfi-offset= + Offset between the KCFI type identifier and the start of a function. -fsanitize-coverage= Specify the type of coverage instrumentation for Sanitizers -fsanitize-hwaddress-abi= 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 @@ -692,6 +692,12 @@ let Documentation = [AlignValueDocs]; } +def KCFIUnchecked : Attr { + let Spellings = [Clang<"kcfi_unchecked">]; + let Subjects = SubjectList<[Var, TypedefName]>; + let Documentation = [KCFIUncheckedDocs]; +} + def AlignMac68k : InheritableAttr { // This attribute has no spellings as it is only ever created implicitly. let Spellings = []; 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 @@ -5399,6 +5399,32 @@ }]; } +def KCFIUncheckedDocs : Documentation { + let Category = DocCatType; + let Content = [{ +The ``kcfi_unchecked`` attribute causes Clang to not emit KCFI checks for indirect +calls made through annotated function pointer variables or types. + +.. code-block:: c + + int f1(void) { return 0; } + + int (*p1)(void) = f1; + p1(); // checked + + int (*p2)(void) __attribute__((kcfi_unchecked)) = f1; + p2(); // unchecked + + typedef typeof(f1) * __attribute__((kcfi_unchecked) unchecked_t; + ((unchecked_t)p2)(); // unchecked + + unchecked_t p3 = f1; + p3(); // unchecked + +``kcfi_unchecked`` is only supported for function pointers and function pointer types. +}]; +} + def ReinitializesDocs : Documentation { let Category = DocCatFunction; let Content = [{ 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 @@ -248,6 +248,8 @@ ///< CFI icall function signatures CODEGENOPT(SanitizeCfiCanonicalJumpTables, 1, 0) ///< Make jump table symbols canonical ///< instead of creating a local jump table. +CODEGENOPT(SanitizeKCFIOffset, 32, 0) ///< Define the KCFI type identifier offset from + ///< the start of the function. CODEGENOPT(SanitizeCoverageType, 2, 0) ///< Type of sanitizer coverage ///< instrumentation. CODEGENOPT(SanitizeCoverageIndirectCalls, 1, 0) ///< Enable sanitizer coverage 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 @@ -3056,6 +3056,9 @@ def warn_attribute_pointer_or_reference_only : Warning< "%0 attribute only applies to a pointer or reference (%1 is invalid)">, InGroup; +def err_attribute_function_pointers_only : Error< + "%0 attribute argument only applies to a function pointer or a function " + "pointer type">; def err_attribute_no_member_pointers : Error< "%0 attribute cannot be used with pointers to members">; def err_attribute_invalid_implicit_this_argument : Error< diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -223,6 +223,7 @@ FEATURE(is_trivially_copyable, LangOpts.CPlusPlus) FEATURE(is_union, LangOpts.CPlusPlus) FEATURE(modules, LangOpts.Modules) +FEATURE(kcfi, LangOpts.Sanitize.has(SanitizerKind::KCFI)) FEATURE(safe_stack, LangOpts.Sanitize.has(SanitizerKind::SafeStack)) FEATURE(shadow_call_stack, LangOpts.Sanitize.has(SanitizerKind::ShadowCallStack)) diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def --- a/clang/include/clang/Basic/Sanitizers.def +++ b/clang/include/clang/Basic/Sanitizers.def @@ -124,6 +124,9 @@ CFIDerivedCast | CFIICall | CFIMFCall | CFIUnrelatedCast | CFINVCall | CFIVCall) +// Kernel Control Flow Integrity +SANITIZER("kcfi", KCFI) + // Safe Stack SANITIZER("safe-stack", SafeStack) 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 @@ -1747,6 +1747,11 @@ PosFlag, NegFlag, BothFlags<[], " the jump table addresses canonical in the symbol table">>, Group; +def fsanitize_kcfi_offset + : Joined<["-"], "fsanitize-kcfi-offset=">, MetaVarName<"">, + Group, + HelpText<"Assume there are bytes between the KCFI type identifer and start of the function.">, + MarshallingInfoInt>; defm sanitize_stats : BoolOption<"f", "sanitize-stats", CodeGenOpts<"SanitizeStats">, DefaultFalse, PosFlag, NegFlag, diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -48,6 +48,7 @@ bool AsanOutlineInstrumentation = false; llvm::AsanDtorKind AsanDtorKind = llvm::AsanDtorKind::Invalid; std::string HwasanAbi; + unsigned int KCFIOffset = 0; bool LinkRuntimes = true; bool LinkCXXRuntimes = false; bool NeedPIE = false; diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -3151,6 +3151,48 @@ #undef SANITIZER_CHECK }; +void CodeGenFunction::EmitKCFICheck(llvm::Value *Ptr, llvm::ConstantInt *Hash) { + uint64_t Offset = CGM.getCodeGenOpts().SanitizeKCFIOffset; + if (Offset) + Ptr = Builder + .CreateConstInBoundsGEP( + Address(Builder.CreateBitCast(Ptr, Int8PtrTy), + CGM.getPointerAlign()), + -Offset) + .getPointer(); + + Address HashPtr = Builder.CreateConstInBoundsGEP( + Address(Builder.CreateBitCast(Ptr, Int64Ty->getPointerTo(0)), + CGM.getPointerAlign()), + -1); + llvm::Value *Test = Builder.CreateICmpEQ(Builder.CreateLoad(HashPtr), Hash); + llvm::BasicBlock *ContBB = createBasicBlock("kcfi.cont"); + llvm::BasicBlock *FailBB = createBasicBlock("kcfi.fail"); + llvm::Instruction *Branch = Builder.CreateCondBr(Test, ContBB, FailBB); + + llvm::MDBuilder MDHelper(getLLVMContext()); + Branch->setMetadata(llvm::LLVMContext::MD_prof, + MDHelper.createBranchWeights((1U << 20) - 1, 1)); + + EmitBlock(FailBB); + + llvm::FunctionCallee Handler = CGM.getModule().getOrInsertFunction( + "__kcfi_check_fail", + llvm::FunctionType::get(VoidTy, {Int64Ty, Int8PtrTy}, false)); + llvm::CallInst *Call = Builder.CreateCall( + Handler, {Hash, Builder.CreateBitCast(Ptr, Int8PtrTy)}); + + if (CGM.getCodeGenOpts().SanitizeRecover.has(SanitizerKind::KCFI)) + Builder.CreateBr(ContBB); + else { + Call->setDoesNotReturn(); + Call->setDoesNotThrow(); + Builder.CreateUnreachable(); + } + + EmitBlock(ContBB); +} + static void emitCheckHandlerCall(CodeGenFunction &CGF, llvm::FunctionType *FnType, ArrayRef FnArgs, @@ -5164,6 +5206,7 @@ const Decl *TargetDecl = OrigCallee.getAbstractInfo().getCalleeDecl().getDecl(); + auto OrigType = CalleeType; CalleeType = getContext().getCanonicalType(CalleeType); auto PointeeType = cast(CalleeType)->getPointeeType(); @@ -5220,11 +5263,11 @@ } const auto *FnType = cast(PointeeType); + bool IsIndirectCall = !TargetDecl || !isa(TargetDecl); // If we are checking indirect calls and this call is indirect, check that the // function pointer is a member of the bit set for the function type. - if (SanOpts.has(SanitizerKind::CFIICall) && - (!TargetDecl || !isa(TargetDecl))) { + if (SanOpts.has(SanitizerKind::CFIICall) && IsIndirectCall) { SanitizerScope SanScope(this); EmitSanitizerStatReport(llvm::SanStat_CFI_ICall); @@ -5257,6 +5300,14 @@ } } + if (SanOpts.has(SanitizerKind::KCFI) && IsIndirectCall) { + auto *TargetType = dyn_cast(OrigType); + if ((!TargetDecl || !TargetDecl->hasAttr()) && + (!TargetType || !TargetType->getDecl()->hasAttr())) + EmitKCFICheck(Callee.getFunctionPointer(), + CGM.CreateKCFITypeId(QualType(FnType, 0))); + } + CallArgList Args; if (Chain) Args.add(RValue::get(Builder.CreateBitCast(Chain, CGM.VoidPtrTy)), diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -4591,6 +4591,8 @@ /// passing to a runtime sanitizer handler. llvm::Constant *EmitCheckSourceLocation(SourceLocation Loc); + void EmitKCFICheck(llvm::Value *Ptr, llvm::ConstantInt *Hash); + /// Create a basic block that will either trap or call a handler function in /// the UBSan runtime with the provided arguments, and create a conditional /// branch to it. diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1374,6 +1374,9 @@ /// Generate a cross-DSO type identifier for MD. llvm::ConstantInt *CreateCrossDsoCfiTypeId(llvm::Metadata *MD); + /// Generate a KCFI type identifier for T. + llvm::ConstantInt *CreateKCFITypeId(QualType T); + /// Create a metadata identifier for the given type. This may either be an /// MDString (for external identifiers) or a distinct unnamed MDNode (for /// internal identifiers). @@ -1392,6 +1395,12 @@ void CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD, llvm::Function *F); + /// Set type hash as prefix data to the given function + void SetKCFITypePrefix(const FunctionDecl *FD, llvm::Function *F); + + /// Emit KCFI type identifier constants + void EmitKCFIConstants(); + /// Whether this function's return type has no side effects, and thus may /// be trivially discarded if it is unused. bool MayDropFunctionReturn(const ASTContext &Context, QualType ReturnType); @@ -1613,7 +1622,8 @@ llvm::AttrBuilder &FuncAttrs); llvm::Metadata *CreateMetadataIdentifierImpl(QualType T, MetadataTypeMap &Map, - StringRef Suffix); + StringRef Suffix, + bool OnlyExternal = true); }; } // end namespace CodeGen 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 @@ -45,6 +45,7 @@ #include "clang/Basic/Version.h" #include "clang/CodeGen/ConstantInitBuilder.h" #include "clang/Frontend/FrontendDiagnostic.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Triple.h" #include "llvm/Analysis/TargetLibraryInfo.h" @@ -63,6 +64,7 @@ #include "llvm/Support/MD5.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/X86TargetParser.h" +#include "llvm/Support/xxhash.h" using namespace clang; using namespace CodeGen; @@ -546,6 +548,8 @@ CodeGenFunction(*this).EmitCfiCheckFail(); CodeGenFunction(*this).EmitCfiCheckStub(); } + if (LangOpts.Sanitize.has(SanitizerKind::KCFI)) + EmitKCFIConstants(); emitAtAvailableLinkGuard(); if (Context.getTargetInfo().getTriple().isWasm() && !Context.getTargetInfo().getTriple().isOSEmscripten()) { @@ -1582,6 +1586,17 @@ return llvm::ConstantInt::get(Int64Ty, llvm::MD5Hash(MDS->getString())); } +llvm::ConstantInt *CodeGenModule::CreateKCFITypeId(QualType T) { + llvm::MDString *MDS = dyn_cast(CreateMetadataIdentifierImpl( + T, MetadataIdMap, "", /*OnlyExternal=*/false)); + if (!MDS) + return nullptr; + + return llvm::ConstantInt::get( + Int64Ty, + (llvm::xxHash64(MDS->getString()) & (-1ULL >> 16)) | (0x10CFULL << 48)); +} + void CodeGenModule::SetLLVMFunctionAttributes(GlobalDecl GD, const CGFunctionInfo &Info, llvm::Function *F, bool IsThunk) { @@ -2168,6 +2183,49 @@ F->addTypeMetadata(0, llvm::ConstantAsMetadata::get(CrossDsoTypeId)); } +void CodeGenModule::SetKCFITypePrefix(const FunctionDecl *FD, + llvm::Function *F) { + + if (isa(FD) && !cast(FD)->isStatic()) + return; + + F->setPrefixData(CreateKCFITypeId(FD->getType())); +} + +static bool allowKCFIIdentifier(const std::string &Name) { + // KCFI type identifier constants are only necessary for external assembly + // functions, which means it's safe to skip unusual names. Subset of + // MCAsmInfo::isAcceptableChar() and MCAsmInfoXCOFF::isAcceptableChar(). + for (const char &C : Name) { + if (llvm::isAlnum(C) || C == '_' || C == '.') + continue; + return false; + } + return true; +} + +void CodeGenModule::EmitKCFIConstants() { + llvm::Module &M = getModule(); + // Generate a weak constant with the expected KCFI type identifier for all + // address-taken function declarations. + for (auto &F : M.functions()) { + if (!F.isDeclaration() || !F.hasAddressTaken() || !F.hasPrefixData()) + continue; + + auto *Id = dyn_cast(F.getPrefixData()); + if (!Id) + continue; + + std::string Name = "__kcfi_typeid_" + F.getName().str(); + if (!allowKCFIIdentifier(Name)) + continue; + + std::string Asm = ".weak " + Name + "\n" + ".set " + Name + ", " + + std::to_string(Id->getSExtValue()) + "\n"; + M.appendModuleInlineAsm(Asm); + } +} + void CodeGenModule::SetFunctionAttributes(GlobalDecl GD, llvm::Function *F, bool IsIncompleteFunction, bool IsThunk) { @@ -2250,6 +2308,9 @@ !CodeGenOpts.SanitizeCfiCanonicalJumpTables) CreateFunctionTypeMetadataForIcall(FD, F); + if (LangOpts.Sanitize.has(SanitizerKind::KCFI)) + SetKCFITypePrefix(FD, F); + if (getLangOpts().OpenMP && FD->hasAttr()) getOpenMPRuntime().emitDeclareSimdFunction(FD, F); @@ -6430,7 +6491,8 @@ llvm::Metadata * CodeGenModule::CreateMetadataIdentifierImpl(QualType T, MetadataTypeMap &Map, - StringRef Suffix) { + StringRef Suffix, + bool OnlyExternal /*=true*/) { if (auto *FnType = T->getAs()) T = getContext().getFunctionType( FnType->getReturnType(), FnType->getParamTypes(), @@ -6440,7 +6502,7 @@ if (InternalId) return InternalId; - if (isExternallyVisible(T->getLinkage())) { + if (isExternallyVisible(T->getLinkage()) || !OnlyExternal) { std::string OutName; llvm::raw_string_ostream Out(OutName); getCXXABI().getMangleContext().mangleTypeName(T, Out); diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -37,7 +37,8 @@ static const SanitizerMask NotAllowedWithMinimalRuntime = SanitizerKind::Function | SanitizerKind::Vptr; static const SanitizerMask RequiresPIE = - SanitizerKind::DataFlow | SanitizerKind::HWAddress | SanitizerKind::Scudo; + SanitizerKind::DataFlow | SanitizerKind::HWAddress | SanitizerKind::Scudo | + SanitizerKind::KCFI; static const SanitizerMask NeedsUnwindTables = SanitizerKind::Address | SanitizerKind::HWAddress | SanitizerKind::Thread | SanitizerKind::Memory | SanitizerKind::DataFlow; @@ -145,17 +146,17 @@ struct Ignorelist { const char *File; SanitizerMask Mask; - } Ignorelists[] = {{"asan_ignorelist.txt", SanitizerKind::Address}, - {"hwasan_ignorelist.txt", SanitizerKind::HWAddress}, - {"memtag_ignorelist.txt", SanitizerKind::MemTag}, - {"msan_ignorelist.txt", SanitizerKind::Memory}, - {"tsan_ignorelist.txt", SanitizerKind::Thread}, - {"dfsan_abilist.txt", SanitizerKind::DataFlow}, - {"cfi_ignorelist.txt", SanitizerKind::CFI}, - {"ubsan_ignorelist.txt", - SanitizerKind::Undefined | SanitizerKind::Integer | - SanitizerKind::Nullability | - SanitizerKind::FloatDivideByZero}}; + } Ignorelists[] = { + {"asan_ignorelist.txt", SanitizerKind::Address}, + {"hwasan_ignorelist.txt", SanitizerKind::HWAddress}, + {"memtag_ignorelist.txt", SanitizerKind::MemTag}, + {"msan_ignorelist.txt", SanitizerKind::Memory}, + {"tsan_ignorelist.txt", SanitizerKind::Thread}, + {"dfsan_abilist.txt", SanitizerKind::DataFlow}, + {"cfi_ignorelist.txt", SanitizerKind::CFI | SanitizerKind::KCFI}, + {"ubsan_ignorelist.txt", + SanitizerKind::Undefined | SanitizerKind::Integer | + SanitizerKind::Nullability | SanitizerKind::FloatDivideByZero}}; for (auto BL : Ignorelists) { if (!(Kinds & BL.Mask)) @@ -165,7 +166,7 @@ llvm::sys::path::append(Path, "share", BL.File); if (D.getVFS().exists(Path)) IgnorelistFiles.push_back(std::string(Path.str())); - else if (BL.Mask == SanitizerKind::CFI && DiagnoseErrors) + else if (BL.Mask & SanitizerKind::CFI && DiagnoseErrors) // If cfi_ignorelist.txt cannot be found in the resource dir, driver // should fail. D.Diag(clang::diag::err_drv_no_such_file) << Path; @@ -676,11 +677,22 @@ << "-fsanitize-cfi-cross-dso" << "-fsanitize-cfi-icall-generalize-pointers"; + if (AllAddedKinds & SanitizerKind::KCFI && DiagnoseErrors) + D.Diag(diag::err_drv_argument_not_allowed_with) << "-fsanitize=cfi*" + << "-fsanitize=kcfi"; + CfiCanonicalJumpTables = Args.hasFlag(options::OPT_fsanitize_cfi_canonical_jump_tables, options::OPT_fno_sanitize_cfi_canonical_jump_tables, true); } + if (AllAddedKinds & SanitizerKind::KCFI) + if (Arg *A = Args.getLastArg(options::OPT_fsanitize_kcfi_offset)) { + StringRef S = A->getValue(); + if (S.getAsInteger(0, KCFIOffset) && DiagnoseErrors) + D.Diag(clang::diag::err_drv_invalid_value) << A->getAsString(Args) << S; + } + Stats = Args.hasFlag(options::OPT_fsanitize_stats, options::OPT_fno_sanitize_stats, false); @@ -1134,6 +1146,10 @@ if (CfiCanonicalJumpTables) CmdArgs.push_back("-fsanitize-cfi-canonical-jump-tables"); + if (KCFIOffset) + CmdArgs.push_back( + Args.MakeArgString("-fsanitize-kcfi-offset=" + Twine(KCFIOffset))); + if (Stats) CmdArgs.push_back("-fsanitize-stats"); diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -1034,6 +1034,9 @@ getTriple().getArch() == llvm::Triple::arm || getTriple().isWasm() || getTriple().isAArch64()) Res |= SanitizerKind::CFIICall; + if (getTriple().getArch() == llvm::Triple::x86_64 || + getTriple().isAArch64(64)) + Res |= SanitizerKind::KCFI; if (getTriple().getArch() == llvm::Triple::x86_64 || getTriple().isAArch64(64) || getTriple().isRISCV()) Res |= SanitizerKind::ShadowCallStack; 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 @@ -4697,6 +4697,23 @@ D->addAttr(::new (Context) ModeAttr(Context, CI, Name)); } +static void handleKCFIUncheckedAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + QualType Type; + + if (auto *TD = dyn_cast(D)) + Type = TD->getUnderlyingType(); + else if (auto *VD = dyn_cast(D)) + if (auto *TSI = VD->getTypeSourceInfo()) + Type = TSI->getType(); + + if (Type.isNull() || !Type->isFunctionPointerType()) { + S.Diag(D->getBeginLoc(), diag::err_attribute_function_pointers_only) << AL; + return; + } + + D->addAttr(::new (S.Context) KCFIUncheckedAttr(S.Context, AL)); +} + static void handleNoDebugAttr(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(::new (S.Context) NoDebugAttr(S.Context, AL)); } @@ -8387,6 +8404,9 @@ case ParsedAttr::AT_Mode: handleModeAttr(S, D, AL); break; + case ParsedAttr::AT_KCFIUnchecked: + handleKCFIUncheckedAttr(S, D, AL); + break; case ParsedAttr::AT_NonNull: if (auto *PVD = dyn_cast(D)) handleNonNullAttrParameter(S, PVD, AL); diff --git a/clang/test/CodeGen/kcfi.c b/clang/test/CodeGen/kcfi.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/kcfi.c @@ -0,0 +1,60 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -o - %s | FileCheck --check-prefixes=CHECK,OFFSET0,FATAL %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -fsanitize-recover=kcfi -o - %s | FileCheck --check-prefixes=CHECK,OFFSET0,RECOVER %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -fsanitize-kcfi-offset=1 -fpatchable-function-entry=1 -fpatchable-function-entry-offset=1 -o - %s | FileCheck --check-prefixes=CHECK,OFFSET1 %s +#if !__has_feature(kcfi) +#error Missing kcfi? +#endif + +// CHECK: module asm ".weak __kcfi_typeid_f4" +// CHECK: module asm ".set __kcfi_typeid_f4, [[#%d,HASH:]]" + +typedef int (*fn_t)(void); + +// CHECK: define dso_local i32 @f1(){{.*}} prefix i64 [[#HASH]] +int f1(void) { return 0; } + +// CHECK: define dso_local i32 @f2(){{.*}} prefix i64 [[#%d,HASH2:]] +unsigned int f2(void) { return 2; } + +// CHECK-LABEL: define dso_local i32 @__call(i32 ()*{{.*}} %f) +int __call(fn_t f) __attribute__((__no_sanitize__("kcfi"))) { + // CHECK-NOT: kcfi.fail + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK-NOT: kcfi.cont + // CHECK: %call = call i32 %[[#]]() + return f(); +} + +// CHECK-LABEL: define dso_local i32 @call(i32 ()*{{.*}} %f) +int call(fn_t f) { + // OFFSET1: %[[#I8CAST:]] = bitcast i32 ()* %{{.}} to i8* + // OFFSET1: %[[#OFFSET:]] = getelementptr inbounds i8, i8* %[[#I8CAST]], i64 -1 + // OFFSET1: %[[#CAST:]] = bitcast i8* %[[#OFFSET]] to i64* + // OFFSET0: %[[#CAST:]] = bitcast i32 ()* %{{.}} to i64* + // CHECK: %[[#GEPI:]] = getelementptr inbounds i64, i64* %[[#CAST]], i64 -1 + // CHECK: %[[#LOAD:]] = load i64, i64* %[[#GEPI]], align 8 + // CHECK: %[[#ICMP:]] = icmp eq i64 %[[#LOAD]], [[#HASH]] + // CHECK: br i1 %[[#ICMP]], label %kcfi.cont, label %kcfi.fail + // CHECK-LABEL: kcfi.fail{{.*}}: + // CHECK: call void @__kcfi_check_fail(i64 [[#HASH]], i8* + // FATAL: unreachable + // RECOVER: br label %kcfi.cont + // CHECK-LABEL: kcfi.cont{{.*}}: + // CHECK: %call = call i32 %[[#]]() + return f(); +} + +// CHECK-DAG: define internal i32 @f3(){{.*}} prefix i64 [[#HASH]] +static int f3(void) { return 1; } + +// CHECK-DAG: declare i32 @f4(){{.*}} prefix i64 [[#HASH]] +extern int f4(void); + +// CHECK-DAG: declare void @__kcfi_check_fail(i64, i8*) + +int test() { + return call(f1) + + __call((fn_t)f2) + + call(f3) + + call(f4); +} diff --git a/clang/test/CodeGen/kcfi_unchecked.c b/clang/test/CodeGen/kcfi_unchecked.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/kcfi_unchecked.c @@ -0,0 +1,120 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -o - %s | FileCheck %s + +#if !__has_feature(kcfi) +#error Missing kcfi +#endif + +// CHECK-LABEL: define dso_local i32 @f1(){{.*}} prefix i64 [[#%d,HASH:]] +int f1(void) { return 0; } + +typedef int (*fn_t)(void); +typedef int (*fn_unchecked_t)(void) __attribute__((kcfi_unchecked)); + +typedef typeof(f1) *fn_typeof_t; +typedef typeof(f1) *__attribute__((kcfi_unchecked)) fn_typeof_unchecked_t; + +// CHECK-LABEL: define{{.*}} i32 @checked() +int checked(void) { + // CHECK: kcfi.fail + // CHECK: call void @__kcfi_check_fail(i64 [[#HASH]] + // CHECK: kcfi.cont + // CHECK: call i32 % + return ({ &f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_typedef_cast() +int checked_typedef_cast(void) { + // CHECK: kcfi.fail + // CHECK: call void @__kcfi_check_fail(i64 [[#HASH]] + // CHECK: kcfi.cont + // CHECK: call i32 % + return ({ (fn_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_outside_typedef_cast() +int checked_outside_typedef_cast(void) { + // CHECK: kcfi.fail + // CHECK: call void @__kcfi_check_fail(i64 [[#HASH]] + // CHECK: kcfi.cont + // CHECK: call i32 % + return ((fn_t)({ &f1; }))(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_typeof_typedef_cast() +int checked_typeof_typedef_cast(void) { + // CHECK: kcfi.fail + // CHECK: call void @__kcfi_check_fail(i64 [[#HASH]] + // CHECK: kcfi.cont + // CHECK: call i32 % + return ({ (fn_typeof_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_var() +int checked_var(void) { + fn_t p = f1; + // CHECK: kcfi.fail + // CHECK: call void @__kcfi_check_fail(i64 [[#HASH]] + // CHECK: kcfi.cont + // CHECK: call i32 % + return p(); +} +// CHECK-LABEL: define{{.*}} i32 @checked_param(i32 ()* +int checked_param(fn_t p) { + // CHECK: kcfi.fail + // CHECK: call void @__kcfi_check_fail(i64 [[#HASH]] + // CHECK: kcfi.cont + // CHECK: call i32 % + return p(); +} + +// CHECK-LABEL: define{{.*}} i32 @unchecked_typedef_cast() +int unchecked_typedef_cast(void) { + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK: call i32 % + return ({ (fn_unchecked_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_outside_typedef_cast() +int unchecked_outside_typedef_cast(void) { + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK: call i32 % + return ((fn_unchecked_t)({ &f1; }))(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_typeof_typedef_cast() +int unchecked_typeof_typedef_cast(void) { + // CHECK-NOT: call void @__kcfi_check_fail + return ({ (fn_typeof_unchecked_t) & f1; })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_compound_var() +int unchecked_compound_var(void) { + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK: call i32 % + return ({ + fn_unchecked_t p = f1; + p; + })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_compound_local_typedef_cast() +int unchecked_compound_local_typedef_cast(void) { + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK: call i32 % + return ({ + typedef typeof(f1) *__attribute__((kcfi_unchecked)) fn_local_unchecked_t; + (fn_local_unchecked_t) & f1; + })(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_var() +int unchecked_var(void) { + fn_unchecked_t p = f1; + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK: call i32 % + return p(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_var_attr() +int unchecked_var_attr(void) { + fn_t __attribute__((kcfi_unchecked)) p = f1; + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK: call i32 % + return p(); +} +// CHECK-LABEL: define{{.*}} i32 @unchecked_param(i32 ()* +int unchecked_param(fn_t __attribute__((kcfi_unchecked)) p) { + // CHECK-NOT: call void @__kcfi_check_fail + // CHECK: call i32 % + return p(); +} diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c --- a/clang/test/Driver/fsanitize.c +++ b/clang/test/Driver/fsanitize.c @@ -667,6 +667,19 @@ // RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -fsanitize-stats -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-STATS // CHECK-CFI-STATS: -fsanitize-stats +// RUN: %clang -target x86_64-linux-gnu -fsanitize=kcfi -fsanitize=cfi -flto -fvisibility=hidden %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-KCFI-NOCFI +// CHECK-KCFI-NOCFI: error: invalid argument '-fsanitize=cfi*' not allowed with '-fsanitize=kcfi' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=kcfi -fsanitize-trap=kcfi %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-KCFI-NOTRAP +// CHECK-KCFI-NOTRAP: error: unsupported argument 'kcfi' to option '-fsanitize-trap' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=kcfi %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-KCFI +// CHECK-KCFI: "-fsanitize=kcfi" + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=kcfi -fsanitize-recover=kcfi %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-KCFI-RECOVER +// CHECK-KCFI-RECOVER: "-fsanitize=kcfi" +// CHECK-KCFI-RECOVER: "-fsanitize-recover=kcfi" + // RUN: %clang_cl -fsanitize=address -c -MDd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL // RUN: %clang_cl -fsanitize=address -c -MTd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL // RUN: %clang_cl -fsanitize=address -c -LDd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL 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 @@ -76,6 +76,7 @@ // CHECK-NEXT: IFunc (SubjectMatchRule_function) // CHECK-NEXT: InitPriority (SubjectMatchRule_variable) // CHECK-NEXT: InternalLinkage (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record) +// CHECK-NEXT: KCFIUnchecked (SubjectMatchRule_variable, SubjectMatchRule_type_alias) // CHECK-NEXT: LTOVisibilityPublic (SubjectMatchRule_record) // CHECK-NEXT: Leaf (SubjectMatchRule_function) // CHECK-NEXT: LoaderUninitialized (SubjectMatchRule_variable_is_global) diff --git a/clang/test/Sema/attr-kcfi_unchecked.c b/clang/test/Sema/attr-kcfi_unchecked.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-kcfi_unchecked.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + +int a __attribute__((kcfi_unchecked)); // expected-error {{'kcfi_unchecked' attribute argument only applies to a function pointer or a function pointer type}} +void *p __attribute__((kcfi_unchecked)); // expected-error {{'kcfi_unchecked' attribute argument only applies to a function pointer or a function pointer type}} +void (*f)(void) __attribute__((kcfi_unchecked)); + +typedef unsigned long l_unchecked_t __attribute__((kcfi_unchecked)); // expected-error {{'kcfi_unchecked' attribute argument only applies to a function pointer or a function pointer type}} +typedef int (*f_unchecked_t)(void) __attribute__((kcfi_unchecked)); + +void f1(unsigned long p __attribute__((kcfi_unchecked))) {} // expected-error {{'kcfi_unchecked' attribute argument only applies to a function pointer or a function pointer type}} +void f2(void *p __attribute__((kcfi_unchecked))) {} // expected-error {{'kcfi_unchecked' attribute argument only applies to a function pointer or a function pointer type}} +void f3(void (*p)(void) __attribute__((kcfi_unchecked))) {}