From a8b9c3cb703302d4b806cafe3493b0dee3ab68bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 3 Sep 2021 10:48:10 +0900 Subject: [PATCH] Implement Control Flow Guard annotations (#1488) This is a set of changes required to enable control flow guard enforcement within the process. Control flow guard is a security mitigation feature that validates that indirect calls only land on addresses that are valid targets of indirect calls. It has two parts: identifying valid targets of indirect calls within the process, and checking whether target of indirect call is valid before dispatching to it. This implements annotations and enforcement within the unmanaged parts of the NativeAOT runtime and the annotation-only part for the managed code. Enforcement will follow later. Three kinds of changes: * A new version of Runtime.lib that enables `/guard:cf` flag. This is in addition to the existing libraries since we don't want code to pay the perf penalty if CFG is not enabled. * Annotating methods as valid CFG targets in the AOT compiler and object file writer. * MSBuild support for new `Guard` property that enables all of this (passes a switch to the AOT compiler, selects the guarded version of runtime libraries to link with, and passes a switch to link.exe to enable CFG for the process). --- .../Microsoft.NETCore.Native.Windows.props | 2 + .../Microsoft.NETCore.Native.targets | 1 + .../nativeaot/Runtime/Full/CMakeLists.txt | 12 ++++ .../Compiler/CompilationBuilder.Aot.cs | 15 +++++ .../DependencyAnalysis/ObjectWriter.cs | 24 ++++++-- .../Compiler/RyuJitCompilation.cs | 4 ++ .../Compiler/RyuJitCompilationBuilder.cs | 3 + src/coreclr/tools/aot/ILCompiler/Program.cs | 18 ++++++ src/coreclr/tools/aot/ObjWriter/objwriter.cpp | 55 +++++++++++++++---- src/coreclr/tools/aot/ObjWriter/objwriter.h | 14 +++-- 10 files changed, 129 insertions(+), 19 deletions(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.props b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.props index 69c531e3781e30..adbdf38f5e7275 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.props +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.props @@ -19,6 +19,7 @@ The .NET Foundation licenses this file to you under the MIT license. lib Runtime Runtime.ServerGC + Runtime.ServerGC.GuardCF wmainCRTStartup WINDOWS CONSOLE @@ -76,6 +77,7 @@ The .NET Foundation licenses this file to you under the MIT license. + diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index 1b803961a9572e..8879b6880d9fe7 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -260,6 +260,7 @@ The .NET Foundation licenses this file to you under the MIT license. + diff --git a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt index 883d337e4b5978..7c87aef5b1b587 100644 --- a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt @@ -26,6 +26,12 @@ add_library(Runtime.ServerGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOU target_compile_definitions(Runtime.ServerGC PRIVATE -DFEATURE_SVR_GC) +if (CLR_CMAKE_TARGET_WIN32) + add_library(Runtime.ServerGC.GuardCF STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${RUNTIME_SOURCES_ARCH_ASM} ${SERVER_GC_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS}) + target_compile_definitions(Runtime.ServerGC.GuardCF PRIVATE -DFEATURE_SVR_GC) + target_compile_options(Runtime.ServerGC.GuardCF PRIVATE $<$,$>:/guard:cf>) +endif (CLR_CMAKE_TARGET_WIN32) + # Get the current list of definitions get_compile_definitions(DEFINITIONS) @@ -66,6 +72,12 @@ add_custom_target( add_dependencies(Runtime RuntimeAsmHelpers) add_dependencies(Runtime.ServerGC RuntimeAsmHelpers) +if (CLR_CMAKE_TARGET_WIN32) + add_dependencies(Runtime.ServerGC.GuardCF RuntimeAsmHelpers) +endif (CLR_CMAKE_TARGET_WIN32) install_static_library(Runtime aotsdk nativeaot) install_static_library(Runtime.ServerGC aotsdk nativeaot) +if (CLR_CMAKE_TARGET_WIN32) + install_static_library(Runtime.ServerGC.GuardCF aotsdk nativeaot) +endif (CLR_CMAKE_TARGET_WIN32) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs index 89fadc9a89a757..60fe612fb5f0fb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; + using Internal.JitInterface; namespace ILCompiler @@ -21,6 +23,7 @@ partial class CompilationBuilder protected bool _methodBodyFolding; protected bool _singleThreaded; protected InstructionSetSupport _instructionSetSupport; + protected SecurityMitigationOptions _mitigationOptions; partial void InitializePartial() { @@ -76,6 +79,12 @@ public CompilationBuilder UseDebugInfoProvider(DebugInformationProvider provider return this; } + public CompilationBuilder UseSecurityMitigationOptions(SecurityMitigationOptions options) + { + _mitigationOptions = options; + return this; + } + public CompilationBuilder UseMethodBodyFolding(bool enable) { _methodBodyFolding = enable; @@ -106,4 +115,10 @@ public ILScannerBuilder GetILScannerBuilder(CompilationModuleGroup compilationGr return new ILScannerBuilder(_context, compilationGroup ?? _compilationGroup, _nameMangler, GetILProvider(), GetPreinitializationManager()); } } + + [Flags] + public enum SecurityMitigationOptions + { + ControlFlowGuardAnnotations = 0x0001, + } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs index dff96477e500ed..9e88d1103739df 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs @@ -193,10 +193,10 @@ public void EmitSymbolDef(Utf8StringBuilder symbolName, bool global = false) } [DllImport(NativeObjectWriterFileName)] - private static extern int EmitSymbolRef(IntPtr objWriter, byte[] symbolName, RelocType relocType, int delta); - public int EmitSymbolRef(Utf8StringBuilder symbolName, RelocType relocType, int delta = 0) + private static extern int EmitSymbolRef(IntPtr objWriter, byte[] symbolName, RelocType relocType, int delta, SymbolRefFlags flags); + private int EmitSymbolRef(Utf8StringBuilder symbolName, RelocType relocType, int delta = 0, SymbolRefFlags flags = 0) { - return EmitSymbolRef(_nativeObjectWriter, symbolName.Append('\0').UnderlyingArray, relocType, delta); + return EmitSymbolRef(_nativeObjectWriter, symbolName.Append('\0').UnderlyingArray, relocType, delta, flags); } [DllImport(NativeObjectWriterFileName)] @@ -787,7 +787,17 @@ public int EmitSymbolReference(ISymbolNode target, int delta, RelocType relocTyp AppendExternCPrefix(_sb); target.AppendMangledName(_nodeFactory.NameMangler, _sb); - return EmitSymbolRef(_sb, relocType, checked(delta + target.Offset)); + SymbolRefFlags flags = 0; + + // For now consider all method symbols address taken. + // We could restrict this in the future to those that are referenced from + // reflection tables, EH tables, were actually address taken in code, or are referenced from vtables. + if ((_options & ObjectWritingOptions.ControlFlowGuard) != 0 && target is IMethodNode) + { + flags |= SymbolRefFlags.AddressTakenFunction; + } + + return EmitSymbolRef(_sb, relocType, checked(delta + target.Offset), flags); } public void EmitBlobWithRelocs(byte[] blob, Relocation[] relocs) @@ -1250,11 +1260,17 @@ private static string GetLLVMTripleFromTarget(TargetDetails target) return $"{arch}{sub}-{vendor}-{sys}-{abi}"; } + + private enum SymbolRefFlags + { + AddressTakenFunction = 0x0001, + } } [Flags] public enum ObjectWritingOptions { GenerateDebugInfo = 0x01, + ControlFlowGuard = 0x02, } } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index 340ba215484977..178962aa199347 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -70,6 +70,9 @@ protected override void CompileInternal(string outputFile, ObjectDumper dumper) if (_debugInformationProvider is not NullDebugInformationProvider) options |= ObjectWritingOptions.GenerateDebugInfo; + if ((_compilationOptions & RyuJitCompilationOptions.ControlFlowGuardAnnotations) != 0) + options |= ObjectWritingOptions.ControlFlowGuard; + ObjectWriter.EmitObject(outputFile, nodes, NodeFactory, options, dumper); } @@ -214,5 +217,6 @@ public enum RyuJitCompilationOptions { MethodBodyFolding = 0x1, SingleThreadedCompilation = 0x2, + ControlFlowGuardAnnotations = 0x4, } } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs index c79381bd73c922..ae9a07f73381d6 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs @@ -110,6 +110,9 @@ public override ICompilation ToCompilation() if (_singleThreaded) options |= RyuJitCompilationOptions.SingleThreadedCompilation; + if ((_mitigationOptions & SecurityMitigationOptions.ControlFlowGuardAnnotations) != 0) + options |= RyuJitCompilationOptions.ControlFlowGuardAnnotations; + var factory = new RyuJitNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider, GetPreinitializationManager()); JitConfigProvider.Initialize(_context.Target, jitFlagBuilder.ToArray(), _ryujitOptions); diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 01bf877bd03f27..f7b8db2a413c79 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -59,6 +59,7 @@ internal class Program private bool _methodBodyFolding; private bool _singleThreaded; private string _instructionSet; + private string _guard; private string _singleMethodTypeName; private string _singleMethodName; @@ -202,6 +203,7 @@ private ArgumentSyntax ParseCommandLine(string[] args) syntax.DefineOptionList("runtimeopt", ref _runtimeOptions, "Runtime options to set"); syntax.DefineOption("singlethreaded", ref _singleThreaded, "Run compilation on a single thread"); syntax.DefineOption("instructionset", ref _instructionSet, "Instruction set to allow or disallow"); + syntax.DefineOption("guard", ref _guard, "Enable mitigations. Options: 'cf': CFG (Control Flow Guard, Windows only)"); syntax.DefineOption("preinitstatics", ref _preinitStatics, "Interpret static constructors at compile time if possible (implied by -O)"); syntax.DefineOption("nopreinitstatics", ref _noPreinitStatics, "Do not interpret static constructors at compile time"); syntax.DefineOptionList("nowarn", ref _suppressedWarnings, "Disable specific warning messages"); @@ -476,6 +478,21 @@ private int Run(string[] args) if (typeSystemContext.InputFilePaths.Count == 0) throw new CommandLineException("No input files specified"); + SecurityMitigationOptions securityMitigationOptions = 0; + if (StringComparer.OrdinalIgnoreCase.Equals(_guard, "cf")) + { + if (_targetOS != TargetOS.Windows) + { + throw new CommandLineException($"Control flow guard only available on Windows"); + } + + securityMitigationOptions = SecurityMitigationOptions.ControlFlowGuardAnnotations; + } + else if (!String.IsNullOrEmpty(_guard)) + { + throw new CommandLineException($"Unrecognized mitigation option '{_guard}'"); + } + // // Initialize compilation group and compilation roots // @@ -727,6 +744,7 @@ static string ILLinkify(string rootedAssembly) .UseDependencyTracking(trackingLevel) .UseCompilationRoots(compilationRoots) .UseOptimizationMode(_optimizationMode) + .UseSecurityMitigationOptions(securityMitigationOptions) .UseDebugInfoProvider(debugInfoProvider); if (scanResults != null) diff --git a/src/coreclr/tools/aot/ObjWriter/objwriter.cpp b/src/coreclr/tools/aot/ObjWriter/objwriter.cpp index c72ff029e90ce0..c5301ffa8ab55b 100644 --- a/src/coreclr/tools/aot/ObjWriter/objwriter.cpp +++ b/src/coreclr/tools/aot/ObjWriter/objwriter.cpp @@ -174,7 +174,35 @@ bool ObjectWriter::Init(llvm::StringRef ObjectFilePath, const char* tripleName) return true; } -void ObjectWriter::Finish() { Streamer->Finish(); } +void ObjectWriter::Finish() { + + if (ObjFileInfo->getObjectFileType() == ObjFileInfo->IsCOFF + && AddressTakenFunctions.size() > 0) { + + // Emit all address-taken functions into the GFIDs section + // to support control flow guard. + Streamer->SwitchSection(ObjFileInfo->getGFIDsSection()); + for (const MCSymbol* S : AddressTakenFunctions) { + Streamer->EmitCOFFSymbolIndex(S); + } + + // Emit the feat.00 symbol that controls various linker behaviors + MCSymbol* S = OutContext->getOrCreateSymbol(StringRef("@feat.00")); + Streamer->BeginCOFFSymbolDef(S); + Streamer->EmitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC); + Streamer->EmitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL); + Streamer->EndCOFFSymbolDef(); + int64_t Feat00Flags = 0; + + Feat00Flags |= 0x800; // cfGuardCF flags this object as control flow guard aware + + Streamer->emitSymbolAttribute(S, MCSA_Global); + Streamer->emitAssignment( + S, MCConstantExpr::create(Feat00Flags, *OutContext)); + } + + Streamer->Finish(); +} void ObjectWriter::SwitchSection(const char *SectionName, CustomSectionAttributes attributes, @@ -377,10 +405,9 @@ void ObjectWriter::EmitRelocDirective(const int Offset, StringRef Name, const MC assert(!result.hasValue()); } -const MCExpr *ObjectWriter::GenTargetExpr(const char *SymbolName, - MCSymbolRefExpr::VariantKind Kind, +const MCExpr *ObjectWriter::GenTargetExpr(const MCSymbol* Symbol, MCSymbolRefExpr::VariantKind Kind, int Delta, bool IsPCRel, int Size) { - const MCExpr *TargetExpr = GetSymbolRefExpr(SymbolName, Kind); + const MCExpr *TargetExpr = MCSymbolRefExpr::create(Symbol, Kind, *OutContext); if (IsPCRel && Size != 0) { // If the fixup is pc-relative, we need to bias the value to be relative to // the start of the field, not the end of the field @@ -395,11 +422,17 @@ const MCExpr *ObjectWriter::GenTargetExpr(const char *SymbolName, } int ObjectWriter::EmitSymbolRef(const char *SymbolName, - RelocType RelocationType, int Delta) { + RelocType RelocationType, int Delta, SymbolRefFlags Flags) { bool IsPCRel = false; int Size = 0; MCSymbolRefExpr::VariantKind Kind = MCSymbolRefExpr::VK_None; + MCSymbol* Symbol = OutContext->getOrCreateSymbol(SymbolName); + + if ((int)Flags & (int)SymbolRefFlags::SymbolRefFlags_AddressTakenFunction) { + AddressTakenFunctions.insert(Symbol); + } + // Convert RelocationType to MCSymbolRefExpr switch (RelocationType) { case RelocType::IMAGE_REL_BASED_ABSOLUTE: @@ -429,30 +462,30 @@ int ObjectWriter::EmitSymbolRef(const char *SymbolName, break; case RelocType::IMAGE_REL_BASED_THUMB_MOV32: { const unsigned Offset = GetDFSize(); - const MCExpr *TargetExpr = GenTargetExpr(SymbolName, Kind, Delta); + const MCExpr *TargetExpr = GenTargetExpr(Symbol, Kind, Delta); EmitRelocDirective(Offset, "R_ARM_THM_MOVW_ABS_NC", TargetExpr); EmitRelocDirective(Offset + 4, "R_ARM_THM_MOVT_ABS", TargetExpr); return 8; } case RelocType::IMAGE_REL_BASED_THUMB_BRANCH24: { - const MCExpr *TargetExpr = GenTargetExpr(SymbolName, Kind, Delta); + const MCExpr *TargetExpr = GenTargetExpr(Symbol, Kind, Delta); EmitRelocDirective(GetDFSize(), "R_ARM_THM_CALL", TargetExpr); return 4; } case RelocType::IMAGE_REL_BASED_ARM64_BRANCH26: { - const MCExpr *TargetExpr = GenTargetExpr(SymbolName, Kind, Delta); + const MCExpr *TargetExpr = GenTargetExpr(Symbol, Kind, Delta); EmitRelocDirective(GetDFSize(), "R_AARCH64_CALL26", TargetExpr); return 4; } case RelocType::IMAGE_REL_BASED_ARM64_PAGEBASE_REL21: { - const MCExpr *TargetExpr = GenTargetExpr(SymbolName, Kind, Delta); + const MCExpr *TargetExpr = GenTargetExpr(Symbol, Kind, Delta); TargetExpr = AArch64MCExpr::create(TargetExpr, AArch64MCExpr::VK_CALL, *OutContext); EmitRelocDirective(GetDFSize(), "R_AARCH64_ADR_PREL_PG_HI21", TargetExpr); return 4; } case RelocType::IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A: { - const MCExpr *TargetExpr = GenTargetExpr(SymbolName, Kind, Delta); + const MCExpr *TargetExpr = GenTargetExpr(Symbol, Kind, Delta); TargetExpr = AArch64MCExpr::create(TargetExpr, AArch64MCExpr::VK_LO12, *OutContext); EmitRelocDirective(GetDFSize(), "R_AARCH64_ADD_ABS_LO12_NC", TargetExpr); @@ -460,7 +493,7 @@ int ObjectWriter::EmitSymbolRef(const char *SymbolName, } } - const MCExpr *TargetExpr = GenTargetExpr(SymbolName, Kind, Delta, IsPCRel, Size); + const MCExpr *TargetExpr = GenTargetExpr(Symbol, Kind, Delta, IsPCRel, Size); Streamer->emitValueImpl(TargetExpr, Size, SMLoc(), IsPCRel); return Size; } diff --git a/src/coreclr/tools/aot/ObjWriter/objwriter.h b/src/coreclr/tools/aot/ObjWriter/objwriter.h index be9545c26fff36..4840cf7859895a 100644 --- a/src/coreclr/tools/aot/ObjWriter/objwriter.h +++ b/src/coreclr/tools/aot/ObjWriter/objwriter.h @@ -60,6 +60,11 @@ enum class RelocType { IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A = 0x82, }; +enum class SymbolRefFlags +{ + SymbolRefFlags_AddressTakenFunction = 0x0001, +}; + class ObjectWriter { public: bool Init(StringRef FunctionName, const char* tripleName = nullptr); @@ -78,7 +83,7 @@ class ObjectWriter { void EmitSymbolDef(const char *SymbolName, bool global); void EmitWinFrameInfo(const char *FunctionName, int StartOffset, int EndOffset, const char *BlobSymbolName); - int EmitSymbolRef(const char *SymbolName, RelocType RelocType, int Delta); + int EmitSymbolRef(const char *SymbolName, RelocType RelocType, int Delta, SymbolRefFlags Flags); void EmitDebugFileInfo(int FileId, const char *FileName); void EmitDebugFunctionInfo(const char *FunctionName, int FunctionSize, unsigned MethodTypeIndex); @@ -154,7 +159,7 @@ class ObjectWriter { Triple GetTriple(); unsigned GetDFSize(); void EmitRelocDirective(const int Offset, StringRef Name, const MCExpr *Expr); - const MCExpr *GenTargetExpr(const char *SymbolName, + const MCExpr *GenTargetExpr(const MCSymbol* Symbol, MCSymbolRefExpr::VariantKind Kind, int Delta, bool IsPCRel = false, int Size = 0); void EmitARMExIdxPerOffset(); @@ -177,6 +182,7 @@ class ObjectWriter { bool FrameOpened; std::vector DebugVarInfos; std::vector DebugEHClauseInfos; + DenseSet AddressTakenFunctions; std::set Sections; int FuncId; @@ -243,9 +249,9 @@ DLL_EXPORT STDMETHODCALLTYPE void EmitSymbolDef(ObjectWriter *OW, const char *Sy } DLL_EXPORT STDMETHODCALLTYPE int EmitSymbolRef(ObjectWriter *OW, const char *SymbolName, - RelocType RelocType, int Delta) { + RelocType RelocType, int Delta, SymbolRefFlags Flags) { assert(OW && "ObjWriter is null"); - return OW->EmitSymbolRef(SymbolName, RelocType, Delta); + return OW->EmitSymbolRef(SymbolName, RelocType, Delta, Flags); } DLL_EXPORT STDMETHODCALLTYPE void EmitWinFrameInfo(ObjectWriter *OW, const char *FunctionName,