Skip to content

Commit

Permalink
Implement Control Flow Guard annotations (dotnet#1488)
Browse files Browse the repository at this point in the history
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 `<ControlFlowGuard>Guard</ControlFlowGuard>` 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).
  • Loading branch information
MichalStrehovsky authored Sep 3, 2021
1 parent 1a17135 commit a8b9c3c
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<CppLibCreator>lib</CppLibCreator>
<FullRuntimeName>Runtime</FullRuntimeName>
<FullRuntimeName Condition="'$(ServerGarbageCollection)' != ''">Runtime.ServerGC</FullRuntimeName>
<FullRuntimeName Condition="'$(ControlFlowGuard)' == 'Guard'">Runtime.ServerGC.GuardCF</FullRuntimeName>
<EntryPointSymbol Condition="'$(EntryPointSymbol)' == ''">wmainCRTStartup</EntryPointSymbol>
<LinkerSubsystem Condition="'$(OutputType)' == 'WinExe' and '$(LinkerSubsystem)' == ''">WINDOWS</LinkerSubsystem>
<LinkerSubsystem Condition="'$(OutputType)' == 'Exe' and '$(LinkerSubsystem)' == ''">CONSOLE</LinkerSubsystem>
Expand Down Expand Up @@ -76,6 +77,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<LinkerArg Condition="'$(OutputType)' == 'WinExe' or '$(OutputType)' == 'Exe'" Include="/ENTRY:$(EntryPointSymbol)" />
<LinkerArg Condition="$(NativeLib) == 'Shared'" Include="/INCLUDE:CoreRT_StaticInitialization" />
<LinkerArg Include="/NATVIS:&quot;$(MSBuildThisFileDirectory)CoreRTNatVis.natvis&quot;" />
<LinkerArg Condition="'$(ControlFlowGuard)' == 'Guard'" Include="/guard:cf" />
</ItemGroup>

<ItemGroup Condition="'$(Configuration)' != 'Debug'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Condition="$(IlcDumpIL) == 'true'" Include="--ildump:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).il" />
<IlcArg Condition="$(NoWarn) != ''" Include='--nowarn:"$([MSBuild]::Escape($(NoWarn)))"' />
<IlcArg Condition="$(TrimmerSingleWarn) == 'true'" Include="--singlewarn" />
<IlcArg Condition="'$(ControlFlowGuard)' == 'Guard' and '$(TargetOS)' == 'windows'" Include="--guard:cf" />
<IlcArg Include="@(_IlcRootedAssemblies->'--root:%(Identity)')" />
<IlcArg Include="@(_IlcConditionallyRootedAssemblies->'--conditionalroot:%(Identity)')" />
<IlcArg Include="@(_IlcNoSingleWarnAssemblies->'--nosinglewarnassembly:%(Filename)')" />
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 $<$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>:/guard:cf>)
endif (CLR_CMAKE_TARGET_WIN32)

# Get the current list of definitions
get_compile_definitions(DEFINITIONS)

Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,6 +23,7 @@ partial class CompilationBuilder
protected bool _methodBodyFolding;
protected bool _singleThreaded;
protected InstructionSetSupport _instructionSetSupport;
protected SecurityMitigationOptions _mitigationOptions;

partial void InitializePartial()
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -106,4 +115,10 @@ public ILScannerBuilder GetILScannerBuilder(CompilationModuleGroup compilationGr
return new ILScannerBuilder(_context, compilationGroup ?? _compilationGroup, _nameMangler, GetILProvider(), GetPreinitializationManager());
}
}

[Flags]
public enum SecurityMitigationOptions
{
ControlFlowGuardAnnotations = 0x0001,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -214,5 +217,6 @@ public enum RyuJitCompilationOptions
{
MethodBodyFolding = 0x1,
SingleThreadedCompilation = 0x2,
ControlFlowGuardAnnotations = 0x4,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions src/coreclr/tools/aot/ILCompiler/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
//
Expand Down Expand Up @@ -727,6 +744,7 @@ static string ILLinkify(string rootedAssembly)
.UseDependencyTracking(trackingLevel)
.UseCompilationRoots(compilationRoots)
.UseOptimizationMode(_optimizationMode)
.UseSecurityMitigationOptions(securityMitigationOptions)
.UseDebugInfoProvider(debugInfoProvider);

if (scanResults != null)
Expand Down
55 changes: 44 additions & 11 deletions src/coreclr/tools/aot/ObjWriter/objwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -429,38 +462,38 @@ 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);
return 4;
}
}

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;
}
Expand Down
14 changes: 10 additions & 4 deletions src/coreclr/tools/aot/ObjWriter/objwriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -177,6 +182,7 @@ class ObjectWriter {
bool FrameOpened;
std::vector<DebugVarInfo> DebugVarInfos;
std::vector<DebugEHClauseInfo> DebugEHClauseInfos;
DenseSet<MCSymbol *> AddressTakenFunctions;

std::set<MCSection *> Sections;
int FuncId;
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit a8b9c3c

Please sign in to comment.