in src/BinSkim.Rules/PERules/BA2024.EnableSpectreMitigations.cs [109:367]
public override void AnalyzePortableExecutableAndPdb(BinaryAnalyzerContext context)
{
PEBinary target = context.PEBinary();
// This rule requires both a binary + PDB to analyze.
// TODO: generally we emit a message of some kind for every binary
// and rule combination. We should do something appropriate here.
if (target.PE == null)
{
return;
}
Machine reflectionMachineType = target.PE.Machine;
// The current Machine enum does not have support for Arm64, so translate to our Machine enum
var machineType = (ExtendedMachine)reflectionMachineType;
if (!machineType.CanBeMitigated())
{
// QUESTION:
// Machine HW is unsupported for mitigations...
// should this be in the CanAnalyze() method or here and issue a warning?
return;
}
Pdb pdb = target.Pdb;
var masmModules = new List<ObjectModuleDetails>();
var mitigationNotEnabledModules = new List<ObjectModuleDetails>();
var mitigationDisabledInDebugBuild = new List<ObjectModuleDetails>();
var mitigationExplicitlyDisabledModules = new List<ObjectModuleDetails>();
StringToVersionMap allowedLibraries = context.Policy.GetProperty(AllowedLibraries);
foreach (DisposableEnumerableView<Symbol> omView in pdb.CreateObjectModuleIterator())
{
Symbol om = omView.Value;
ObjectModuleDetails omDetails = om.GetObjectModuleDetails();
// See if the item is in our skip list.
if (!string.IsNullOrEmpty(om.Lib))
{
string libFileName = string.Concat(Path.GetFileName(om.Lib), ",", omDetails.Language.ToString()).ToLowerInvariant();
if (allowedLibraries.TryGetValue(libFileName, out Version minAllowedVersion) &&
omDetails.CompilerBackEndVersion >= minAllowedVersion)
{
continue;
}
}
// We already opted-out of IL Only binaries, so only check for native languages
// or those that can appear in mixed binaries.
switch (omDetails.Language)
{
case Language.C:
case Language.Cxx:
{
if (omDetails.WellKnownCompiler != WellKnownCompilers.MicrosoftC &&
omDetails.WellKnownCompiler != WellKnownCompilers.MicrosoftCxx)
{
// TODO: https://github.com/Microsoft/binskim/issues/114
continue;
}
break;
}
case Language.MASM:
{
masmModules.Add(omDetails);
continue;
}
case Language.LINK:
{
// Linker is not involved in the mitigations, so no need to check version or switches at this time.
continue;
}
case Language.CVTRES:
{
// Resource compiler is not involved in the mitigations, so no need to check version or switches at this time.
continue;
}
case Language.HLSL:
{
// HLSL compiler is not involved in the mitigations, so no need to check version or switches at this time.
continue;
}
// Mixed binaries (/clr) can contain non C++ compilands if they are linked in via netmodules
// .NET IL should be mitigated by the runtime if any mitigations are necessary
// At this point simply accept them as safe until this is disproven.
case Language.CSharp:
case Language.MSIL:
case Language.ILASM:
{
continue;
}
case Language.Unknown:
{
// The linker may emit debug information for modules from static libraries that do not contribute to actual code.
// do not contribute to actual code. Currently these come back as Language.Unknown :(
// TODO: https://github.com/Microsoft/binskim/issues/116
continue;
}
default:
{
// TODO: https://github.com/Microsoft/binskim/issues/117
// Review unknown languages for this and all checks
}
continue;
}
// Get the appropriate compiler version against which to check this compiland.
// check that we are greater than or equal to the first fully supported release: 15.6 first
Version omVersion = omDetails.CompilerBackEndVersion;
CompilerMitigations availableMitigations = GetAvailableMitigations(context, machineType, omVersion);
if (availableMitigations == CompilerMitigations.None)
{
// Built with a compiler version {0} that does not support any Spectre
// mitigations. We do not report here. BA2006 will fire instead.
continue;
}
string[] mitigationSwitches = new string[] { "/Qspectre", "/Qspectre-load", "/Qspectre-load-cf", "/guardspecload" };
SwitchState effectiveState;
// Go process the command line to check for switches
effectiveState = omDetails.GetSwitchState(mitigationSwitches, null, SwitchState.SwitchDisabled, OrderOfPrecedence.LastWins);
if (effectiveState == SwitchState.SwitchDisabled)
{
SwitchState QSpectreState = SwitchState.SwitchNotFound;
SwitchState d2guardspecloadState = SwitchState.SwitchNotFound;
if (availableMitigations.HasFlag(CompilerMitigations.QSpectreAvailable))
{
QSpectreState = omDetails.GetSwitchState(mitigationSwitches[0] /*"/Qspectre"*/ , OrderOfPrecedence.LastWins);
}
if (availableMitigations.HasFlag(CompilerMitigations.D2GuardSpecLoadAvailable))
{
// /d2xxxx options show up in the PDB without the d2 string
// So search for just /guardspecload
d2guardspecloadState = omDetails.GetSwitchState(mitigationSwitches[1] /*"/guardspecload"*/, OrderOfPrecedence.LastWins);
}
// TODO: https://github.com/Microsoft/binskim/issues/119
// We should flag cases where /d2guardspecload is enabled but the
// toolset supports /qSpectre (which should be preferred).
if (QSpectreState == SwitchState.SwitchNotFound && d2guardspecloadState == SwitchState.SwitchNotFound)
{
// Built with tools that support the Spectre mitigations but these have not been enabled.
mitigationNotEnabledModules.Add(omDetails);
}
else
{
// Built with the Spectre mitigations explicitly disabled.
mitigationExplicitlyDisabledModules.Add(omDetails);
}
continue;
}
if (!availableMitigations.HasFlag(CompilerMitigations.NonoptimizedCodeMitigated))
{
string[] OdSwitches = { "/Od" };
// These switches override /Od - there is no one place to find this information on msdn at this time.
string[] OptimizeSwitches = { "/O1", "/O2", "/Ox", "/Og" };
bool debugEnabled = false;
if (omDetails.GetSwitchState(OdSwitches, OptimizeSwitches, SwitchState.SwitchEnabled, OrderOfPrecedence.LastWins) == SwitchState.SwitchEnabled)
{
debugEnabled = true;
}
if (debugEnabled)
{
// Built with /Od which disables Spectre mitigations.
mitigationDisabledInDebugBuild.Add(omDetails);
continue;
}
}
}
string line;
var sb = new StringBuilder();
if (mitigationExplicitlyDisabledModules.Count > 0)
{
// The following modules were compiled with Spectre
// mitigations explicitly disabled: {0}
line = string.Format(
RuleResources.BA2024_Warning_SpectreMitigationExplicitlyDisabled,
mitigationExplicitlyDisabledModules.CreateOutputCoalescedByLibrary());
sb.AppendLine(line);
}
if (mitigationNotEnabledModules.Count > 0)
{
// The following modules were compiled with a toolset that supports
// /Qspectre but the switch was not enabled on the command-line: {0}
line = string.Format(
RuleResources.BA2024_Warning_SpectreMitigationNotEnabled,
mitigationNotEnabledModules.CreateOutputCoalescedByLibrary());
sb.AppendLine(line);
}
if (mitigationDisabledInDebugBuild.Count > 0)
{
// The following modules were compiled with optimizations disabled(/ Od),
// a condition that disables Spectre mitigations: {0}
line = string.Format(
RuleResources.BA2024_Warning_OptimizationsDisabled,
mitigationDisabledInDebugBuild.CreateOutputCoalescedByLibrary());
sb.AppendLine(line);
}
if ((context.Policy.GetProperty(Reporting) & ReportingOptions.WarnIfMasmModulesPresent) == ReportingOptions.WarnIfMasmModulesPresent &&
masmModules.Count > 0)
{
line = string.Format(
RuleResources.BA2024_Warning_MasmModulesDetected,
masmModules.CreateOutputCoalescedByLibrary());
sb.AppendLine(line);
}
if (sb.Length > 0)
{
// '{0}' was compiled with one or more modules that do not properly enable code
// generation mitigations for speculative execution side-channel attack (Spectre)
// vulnerabilities. Spectre attacks can compromise hardware-based isolation,
// allowing non-privileged users to retrieve potentially sensitive data from the
// CPU cache. To resolve the issue, provide the /Qspectre switch on the compiler
// command-line (or /d2guardspecload in cases where your compiler supports this
// switch and it is not possible to update to a toolset that supports /Qspectre).
// The following modules are out of policy: {1}
context.Logger.Log(this,
RuleUtilities.BuildResult(FailureLevel.Warning, context, null,
nameof(RuleResources.BA2024_Warning),
context.TargetUri.GetFileName(),
sb.ToString()));
return;
}
// All linked modules ‘{0}’ were compiled with mitigations enabled that help prevent Spectre (speculative execution side-channel attack) vulnerabilities.
context.Logger.Log(this,
RuleUtilities.BuildResult(ResultKind.Pass, context, null,
nameof(RuleResources.BA2024_Pass),
context.TargetUri.GetFileName()));
}