Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 60 additions & 16 deletions src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ public class Runtime : IRuntime, IDisposable
private readonly IHostAssetResolver _hostAssetResolver;
private readonly ISettingsService _settingsService;
private readonly ISymbolService _symbolService;
private readonly IConsoleService _consoleService;
private Version _runtimeVersion;
private ClrRuntime _clrRuntime;
private string _dacFilePath;
private bool _verifySignature; // This only applies to the regular DAC, not the CDAC
private string _cdacFilePath;
private string _dbiFilePath;

Expand All @@ -43,6 +43,9 @@ public Runtime(IServiceProvider services, int id, ClrInfo clrInfo)
_hostAssetResolver = services.GetService<IHostAssetResolver>();
_settingsService = services.GetService<ISettingsService>() ?? throw new ArgumentException("ISettingsService required");
_symbolService = services.GetService<ISymbolService>() ?? throw new ArgumentException("ISymbolService required");
// IConsoleService is optional: when present it is used to surface actionable guidance
// (for example, to run 'setclrpath') when the DAC/DBI cannot be found or downloaded.
_consoleService = services.GetService<IConsoleService>();

RuntimeType = GetRuntimeType(clrInfo.Flavor);
RuntimeModule = services.GetService<IModuleService>().GetModuleFromBaseAddress(clrInfo.ModuleInfo.ImageBase);
Expand Down Expand Up @@ -101,13 +104,13 @@ public string GetDacFilePath(out bool verifySignature)
{
if (_dacFilePath is null)
{
_dacFilePath = GetLibraryPath(DebugLibraryKind.Dac);
if (_dacFilePath is not null)
_dacFilePath = GetLibraryPath(DebugLibraryKind.Dac, remoteDownloadAllowed: RemoteDownloadAllowed);
if (_dacFilePath is null)
{
_verifySignature = _settingsService.DacSignatureVerificationEnabled;
WriteDebugLibraryNotFoundWarning(DebugLibraryKind.Dac);
}
}
verifySignature = _verifySignature;
verifySignature = VerifyDebugLibrarySignature(_dacFilePath);
return _dacFilePath;
}

Expand All @@ -122,7 +125,7 @@ public string GetCDacFilePath()

// The cDAC is bundled with the diagnostics tool and is never downloaded, so a missing
// path means it isn't available for this host.
_cdacFilePath ??= GetLibraryPath(DebugLibraryKind.CDac);
_cdacFilePath ??= GetLibraryPath(DebugLibraryKind.CDac, remoteDownloadAllowed: false);
if (_cdacFilePath is null && _settingsService.CDacLoadPolicy == CDacLoadPolicy.UseCDac)
{
// The cDAC was explicitly forced but isn't bundled with this tool.
Expand All @@ -131,12 +134,28 @@ public string GetCDacFilePath()
return _cdacFilePath;
}

public string GetDbiFilePath()
public string GetDbiFilePath(out bool verifySignature)
{
_dbiFilePath ??= GetLibraryPath(DebugLibraryKind.Dbi);
if (_dbiFilePath is null)
{
_dbiFilePath = GetLibraryPath(DebugLibraryKind.Dbi, remoteDownloadAllowed: RemoteDownloadAllowed);
if (_dbiFilePath is null)
{
WriteDebugLibraryNotFoundWarning(DebugLibraryKind.Dbi);
}
}
verifySignature = VerifyDebugLibrarySignature(_dbiFilePath);
return _dbiFilePath;
}

/// <summary>
/// The DAC and DBI are both verified according to the single DacSignatureVerificationEnabled
/// setting; there is no path where one is verified and the other is not. The cDAC is the only
/// debugging library that is never verified, and it is loaded through a separate path.
/// </summary>
private bool VerifyDebugLibrarySignature(string libraryPath) =>
libraryPath is not null && _settingsService.DacSignatureVerificationEnabled;

#endregion

/// <summary>
Expand Down Expand Up @@ -218,7 +237,7 @@ InvalidDataException or
return null;
}

private string GetLibraryPath(DebugLibraryKind kind)
private string GetLibraryPath(DebugLibraryKind kind, bool remoteDownloadAllowed)
{
Architecture currentArch = RuntimeInformation.ProcessArchitecture;
string libraryPath = null;
Expand All @@ -233,15 +252,14 @@ private string GetLibraryPath(DebugLibraryKind kind)
break;
}
// The cDAC is an analyzer-host artifact shipped inside the diagnostics tool
// (next to sos.dll, matching the host's RID). It is not symbol-store indexed
// by the target runtime, so never attempt to download it.
// (next to sos.dll, matching the host's RID), never look elsewhere.
if (libraryInfo.Kind == DebugLibraryKind.CDac)
{
continue;
}
if (libraryInfo.ArchivedUnder != SymbolProperties.None)
{
libraryPath = DownloadFile(libraryInfo);
libraryPath = DownloadFile(libraryInfo, remoteDownloadAllowed);
if (libraryPath is not null)
{
break;
Expand All @@ -253,6 +271,31 @@ private string GetLibraryPath(DebugLibraryKind kind)
return libraryPath;
}

/// <summary>
/// Remote symbol-server download of a DAC/DBI is only allowed when the downloaded binary will
/// be authenticode-verified before it is loaded and run: never download executable code that
/// will run unverified. Verification is a Windows-only capability, so remote download requires a
/// Windows host AND that verification has not been disabled (DacSignatureVerificationEnabled).
/// When download is not allowed the matching DAC/DBI must be provided from a trusted local
/// source; explicitly-configured local symbol stores (cache/directory) are always allowed.
/// </summary>
private bool RemoteDownloadAllowed =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _settingsService.DacSignatureVerificationEnabled;

private void WriteDebugLibraryNotFoundWarning(DebugLibraryKind kind)
{
if (RemoteDownloadAllowed)
{
return;
}
string library = kind == DebugLibraryKind.Dbi ? "DBI" : "DAC";
_consoleService?.WriteWarning(
$"Could not find matching {library} for runtime: {RuntimeModule.FileName}{Environment.NewLine}" +
$"The debugging libraries were not found locally and are not downloaded from the symbol server in this configuration.{Environment.NewLine}" +
$"Use 'setclrpath <directory>' to point at the directory that contains the matching DAC/DBI files{Environment.NewLine}" +
$"(for example the runtime's shared framework directory). See 'soshelp setclrpath' for more information.{Environment.NewLine}");
}

private string GetLocalPath(DebugLibraryInfo libraryInfo)
{
string localFilePath;
Expand Down Expand Up @@ -282,7 +325,7 @@ private string GetLocalPath(DebugLibraryInfo libraryInfo)
return localFilePath;
}

private string DownloadFile(DebugLibraryInfo libraryInfo)
private string DownloadFile(DebugLibraryInfo libraryInfo, bool remoteAllowed)
{
OSPlatform platform = Target.OperatingSystem;
string filePath = null;
Expand Down Expand Up @@ -348,7 +391,7 @@ private string DownloadFile(DebugLibraryInfo libraryInfo)
if (key is not null)
{
// Now download the DAC module from the symbol server
filePath = _symbolService.DownloadFile(key.Index, key.FullPathName);
filePath = _symbolService.DownloadFile(key.Index, key.FullPathName, remoteAllowed);
}
}
else
Expand Down Expand Up @@ -408,7 +451,7 @@ public override string ToString()
if (_dacFilePath is not null)
{
sb.AppendLine();
string verify = _verifySignature ? "(verify)" : "(don't verify)";
string verify = VerifyDebugLibrarySignature(_dacFilePath) ? "(verify)" : "(don't verify)";
sb.Append($" DAC: {_dacFilePath} {verify}");
}
if (_cdacFilePath is not null)
Expand All @@ -419,7 +462,8 @@ public override string ToString()
if (_dbiFilePath is not null)
{
sb.AppendLine();
sb.Append($" DBI: {_dbiFilePath}");
string verify = VerifyDebugLibrarySignature(_dbiFilePath) ? "(verify)" : "(don't verify)";
sb.Append($" DBI: {_dbiFilePath} {verify}");
}
return sb.ToString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,8 @@ public string DownloadSymbolFile(IModule module)
/// </summary>
/// <param name="index">index to lookup on symbol server</param>
/// <param name="file">the full path name of the file</param>
public string DownloadFile(string index, string file) => DownloadFile(new SymbolStoreKey(index, file));
/// <param name="remoteAllowed">if false, remote symbol servers are skipped</param>
public string DownloadFile(string index, string file, bool remoteAllowed) => DownloadFile(new SymbolStoreKey(index, file), remoteAllowed);

/// <summary>
/// Returns the portable PDB reader for the assembly path
Expand Down Expand Up @@ -697,7 +698,7 @@ private string DownloadPE(IModule module, KeyTypeFlags flags)
}

// Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
string downloadFilePath = DownloadFile(fileKey);
string downloadFilePath = DownloadFile(fileKey, remoteAllowed: true);
if (!string.IsNullOrEmpty(downloadFilePath))
{
Trace.TraceInformation("DownloadPE: downloaded {0}", downloadFilePath);
Expand Down Expand Up @@ -754,7 +755,7 @@ private string DownloadELF(IModule module, KeyTypeFlags flags)
}

// Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
string downloadFilePath = DownloadFile(fileKey);
string downloadFilePath = DownloadFile(fileKey, remoteAllowed: true);
if (!string.IsNullOrEmpty(downloadFilePath))
{
Trace.TraceInformation("DownloadELF: downloaded {0}", downloadFilePath);
Expand Down Expand Up @@ -811,7 +812,7 @@ private string DownloadMachO(IModule module, KeyTypeFlags flags)
}

// Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
string downloadFilePath = DownloadFile(fileKey);
string downloadFilePath = DownloadFile(fileKey, remoteAllowed: true);
if (!string.IsNullOrEmpty(downloadFilePath))
{
Trace.TraceInformation("DownloadMachO: downloaded {0}", downloadFilePath);
Expand All @@ -825,14 +826,15 @@ private string DownloadMachO(IModule module, KeyTypeFlags flags)
/// Download a file from the symbol stores/server.
/// </summary>
/// <param name="key">index of the file to download</param>
/// <param name="remoteAllowed">if false, remote symbol servers are skipped</param>
/// <returns>path to the downloaded file either in the cache or in the temp directory or null if error</returns>
private string DownloadFile(SymbolStoreKey key)
private string DownloadFile(SymbolStoreKey key, bool remoteAllowed)
{
string downloadFilePath = null;

if (IsSymbolStoreEnabled)
{
using SymbolStoreFile file = GetSymbolStoreFile(key);
using SymbolStoreFile file = GetSymbolStoreFile(key, remoteAllowed);
if (file != null)
{
try
Expand Down Expand Up @@ -917,7 +919,7 @@ private SymbolFile TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEn
{
Debug.Assert(codeViewEntry.MinorVersion == ImageDebugDirectory.PortablePDBMinorVersion);
SymbolStoreKey key = PortablePDBFileKeyGenerator.GetKey(pdbPath, data.Guid);
pdbStream = GetSymbolStoreFile(key)?.Stream;
pdbStream = GetSymbolStoreFile(key, remoteAllowed: true)?.Stream;
}
if (pdbStream == null)
{
Expand Down Expand Up @@ -1021,13 +1023,14 @@ private static bool IsSymweb(string server)
/// Attempts to download/retrieve from cache the key.
/// </summary>
/// <param name="key">index of the file to retrieve</param>
/// <param name="remoteAllowed">if false, remote symbol servers are skipped</param>
/// <returns>stream or null</returns>
private SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key)
private SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key, bool remoteAllowed)
{
Debug.Assert(IsSymbolStoreEnabled);
try
{
return _symbolStore.GetFile(key, CancellationToken.None).GetAwaiter().GetResult();
return _symbolStore.GetFile(key, remoteAllowed, CancellationToken.None).GetAwaiter().GetResult();
}
catch (Exception ex) when (ex is UnauthorizedAccessException or BadImageFormatException or IOException)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.Diagnostics.DebugServices/IRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public interface IRuntime
/// <summary>
/// Returns the DBI file path
/// </summary>
string GetDbiFilePath();
/// <param name="verifySignature">returns whether the returned DBI requires signature verification.</param>
string GetDbiFilePath(out bool verifySignature);
}
}
9 changes: 7 additions & 2 deletions src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,16 @@ public bool AddSymbolServer(
string DownloadSymbolFile(IModule module);

/// <summary>
/// Download a file from the symbol stores/server.
/// Download a file from the symbol stores. Remote symbol servers are only consulted when
/// <paramref name="remoteAllowed"/> is true; otherwise only explicitly-configured local stores
/// (cache/directory) are used. Pass false to avoid remotely downloading a binary that cannot be
/// verified before it is loaded and run (for example a DAC/DBI); pass true for data files such as
/// PDBs, metadata, and module images.
/// </summary>
/// <param name="index">index to lookup on symbol server</param>
/// <param name="file">the full path name of the file</param>
string DownloadFile(string index, string file);
/// <param name="remoteAllowed">if false, remote symbol servers are not consulted</param>
string DownloadFile(string index, string file, bool remoteAllowed);

/// <summary>
/// Returns the portable PDB reader for the assembly path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,14 @@ public static IEnumerable<SymbolStoreKey> GetKeys(KeyTypeFlags flags, string pat
// Creates all the special CLR keys if the path is the coreclr module for this platform
if (fileName == CoreClrFileName)
{
foreach (string specialFileName in (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles)
IEnumerable<string> specialFiles = (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles;
if ((flags & KeyTypeFlags.WindowsDebuggingLibrariesOnly) != 0)
{
// Only the Windows-hosted cross-OS DAC/DBI (mscordaccore.dll / mscordbi.dll)
// are PE images that can be authenticode-verified. Skip the native ELF images.
specialFiles = specialFiles.Where(static specialFile => specialFile.EndsWith(".dll", StringComparison.OrdinalIgnoreCase));
}
foreach (string specialFileName in specialFiles)
{
yield return BuildKey(specialFileName, CoreClrPrefix, buildId);
}
Expand Down
10 changes: 9 additions & 1 deletion src/Microsoft.SymbolStore/KeyGenerators/KeyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ public enum KeyTypeFlags
/// <summary>
/// Generate the r2r perfmap key of the binary (if one exists).
/// </summary>
PerfMapKeys = 0x80
PerfMapKeys = 0x80,

/// <summary>
/// When combined with <see cref="ClrKeys"/> or <see cref="DacDbiKeys"/>, restricts the
/// generated DAC/DBI/SOS special-file keys to Windows (PE) images only: the native Windows
/// DAC/DBI and the Windows-hosted cross-OS DAC/DBI. This is a defense in depth measure
/// to ensure we only enumerate files we can authenticate when that are intended to run.
/// </summary>
WindowsDebuggingLibrariesOnly = 0x100
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ public static IEnumerable<SymbolStoreKey> GetKeys(KeyTypeFlags flags, string pat
/// Creates all the special CLR keys if the path is the coreclr module for this platform
if (fileName == CoreClrFileName)
{
foreach (string specialFileName in (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles)
IEnumerable<string> specialFiles = (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles;
if ((flags & KeyTypeFlags.WindowsDebuggingLibrariesOnly) != 0)
{
specialFiles = specialFiles.Where(static specialFile => specialFile.EndsWith(".dll", StringComparison.OrdinalIgnoreCase));
}
foreach (string specialFileName in specialFiles)
{
yield return BuildKey(specialFileName, CoreClrPrefix, uuid);
}
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.SymbolStore/SymbolStores/HttpSymbolStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ public void ResetClientFailure()
_clientFailure = false;
}

/// <summary>
/// An HTTP symbol server is a remote store.
/// </summary>
public override bool IsRemote => true;

protected override async Task<SymbolStoreFile> GetFileInner(SymbolStoreKey key, CancellationToken token)
{
Uri uri = GetRequestUri(key.Index);
Expand Down
Loading