Skip to content

Commit 5961ccf

Browse files
hoyosjsCopilot
andcommitted
Restrict DAC/DBI download to verifiable cases and verify DBI
Enforce that a DAC/DBI is only downloaded from a symbol server when it will be authenticode-verified before it is loaded and run. - SOS: only download DAC/DBI on a Windows host and only when DAC signature verification is enabled (downloaded => verified). On other hosts, or when verification is disabled, the matching DAC/DBI must be provided locally (collocated runtime or 'setclrpath'). - Verify the DBI signature on load like the DAC, via a shared TOCTOU-safe VerifyAndLoadLibrary helper; consolidate DAC/DBI verification onto the single DacSignatureVerificationEnabled setting. - dotnet-symbol: only stage PE debugging libraries (Windows DAC and the Windows-hosted cross-OS DAC/DBI) via KeyTypeFlags WindowsDebuggingLibrariesOnly; never download native ELF/Mach-O DAC/DBI, since a staged file may later be loaded by another tool. Warn on non-Windows --debugging. - IRuntime.GetDbiFilePath now returns whether the DBI requires signature verification, matching GetDacFilePath. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8acf622f-1741-40d6-b7df-135160672aa3
1 parent 5728f65 commit 5961ccf

9 files changed

Lines changed: 144 additions & 35 deletions

File tree

src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ public class Runtime : IRuntime, IDisposable
2323
private readonly IHostAssetResolver _hostAssetResolver;
2424
private readonly ISettingsService _settingsService;
2525
private readonly ISymbolService _symbolService;
26+
private readonly IConsoleService _consoleService;
2627
private Version _runtimeVersion;
2728
private ClrRuntime _clrRuntime;
2829
private string _dacFilePath;
29-
private bool _verifySignature; // This only applies to the regular DAC, not the CDAC
3030
private string _cdacFilePath;
3131
private string _dbiFilePath;
3232

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

4750
RuntimeType = GetRuntimeType(clrInfo.Flavor);
4851
RuntimeModule = services.GetService<IModuleService>().GetModuleFromBaseAddress(clrInfo.ModuleInfo.ImageBase);
@@ -101,13 +104,13 @@ public string GetDacFilePath(out bool verifySignature)
101104
{
102105
if (_dacFilePath is null)
103106
{
104-
_dacFilePath = GetLibraryPath(DebugLibraryKind.Dac);
105-
if (_dacFilePath is not null)
107+
_dacFilePath = GetLibraryPath(DebugLibraryKind.Dac, allowDownload: DownloadAllowed);
108+
if (_dacFilePath is null)
106109
{
107-
_verifySignature = _settingsService.DacSignatureVerificationEnabled;
110+
WriteDebugLibraryNotFoundWarning(DebugLibraryKind.Dac);
108111
}
109112
}
110-
verifySignature = _verifySignature;
113+
verifySignature = VerifyDebugLibrarySignature(_dacFilePath);
111114
return _dacFilePath;
112115
}
113116

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

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

134-
public string GetDbiFilePath()
137+
public string GetDbiFilePath(out bool verifySignature)
135138
{
136-
_dbiFilePath ??= GetLibraryPath(DebugLibraryKind.Dbi);
139+
if (_dbiFilePath is null)
140+
{
141+
_dbiFilePath = GetLibraryPath(DebugLibraryKind.Dbi, allowDownload: DownloadAllowed);
142+
if (_dbiFilePath is null)
143+
{
144+
WriteDebugLibraryNotFoundWarning(DebugLibraryKind.Dbi);
145+
}
146+
}
147+
verifySignature = VerifyDebugLibrarySignature(_dbiFilePath);
137148
return _dbiFilePath;
138149
}
139150

151+
/// <summary>
152+
/// The DAC and DBI are both verified according to the single DacSignatureVerificationEnabled
153+
/// setting; there is no path where one is verified and the other is not. The cDAC is the only
154+
/// debugging library that is never verified, and it is loaded through a separate path.
155+
/// </summary>
156+
private bool VerifyDebugLibrarySignature(string libraryPath) =>
157+
libraryPath is not null && _settingsService.DacSignatureVerificationEnabled;
158+
140159
#endregion
141160

142161
/// <summary>
@@ -218,7 +237,7 @@ InvalidDataException or
218237
return null;
219238
}
220239

221-
private string GetLibraryPath(DebugLibraryKind kind)
240+
private string GetLibraryPath(DebugLibraryKind kind, bool allowDownload)
222241
{
223242
Architecture currentArch = RuntimeInformation.ProcessArchitecture;
224243
string libraryPath = null;
@@ -239,7 +258,7 @@ private string GetLibraryPath(DebugLibraryKind kind)
239258
{
240259
continue;
241260
}
242-
if (libraryInfo.ArchivedUnder != SymbolProperties.None)
261+
if (libraryInfo.ArchivedUnder != SymbolProperties.None && allowDownload)
243262
{
244263
libraryPath = DownloadFile(libraryInfo);
245264
if (libraryPath is not null)
@@ -253,6 +272,32 @@ private string GetLibraryPath(DebugLibraryKind kind)
253272
return libraryPath;
254273
}
255274

275+
/// <summary>
276+
/// Symbol-server download of a DAC/DBI is only permitted when the downloaded binary will be
277+
/// authenticode-verified before it is loaded and run. That requires a Windows host (authenticode
278+
/// verification is Windows-only, and a non-Windows host cannot load a foreign-format PE DAC/DBI
279+
/// anyway) AND that verification has not been disabled. The DacSignatureVerification override only
280+
/// relaxes verification for locally-provided DAC/DBI (see 'setclrpath'); it must never allow a
281+
/// remotely-acquired, unauthenticated binary to be loaded. When download is not permitted the
282+
/// matching DAC/DBI must be provided locally.
283+
/// </summary>
284+
private bool DownloadAllowed =>
285+
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _settingsService.DacSignatureVerificationEnabled;
286+
287+
private void WriteDebugLibraryNotFoundWarning(DebugLibraryKind kind)
288+
{
289+
if (DownloadAllowed)
290+
{
291+
return;
292+
}
293+
string library = kind == DebugLibraryKind.Dbi ? "DBI" : "DAC";
294+
_consoleService?.WriteWarning(
295+
$"Could not find matching {library} for runtime: {RuntimeModule.FileName}{Environment.NewLine}" +
296+
$"Downloading debugging libraries from the symbol server is only supported on Windows with DAC signature verification enabled.{Environment.NewLine}" +
297+
$"Use 'setclrpath <directory>' to point at the directory that contains the matching DAC/DBI files{Environment.NewLine}" +
298+
$"(for example the runtime's shared framework directory). See 'soshelp setclrpath' for more information.{Environment.NewLine}");
299+
}
300+
256301
private string GetLocalPath(DebugLibraryInfo libraryInfo)
257302
{
258303
string localFilePath;
@@ -408,7 +453,7 @@ public override string ToString()
408453
if (_dacFilePath is not null)
409454
{
410455
sb.AppendLine();
411-
string verify = _verifySignature ? "(verify)" : "(don't verify)";
456+
string verify = VerifyDebugLibrarySignature(_dacFilePath) ? "(verify)" : "(don't verify)";
412457
sb.Append($" DAC: {_dacFilePath} {verify}");
413458
}
414459
if (_cdacFilePath is not null)
@@ -419,7 +464,8 @@ public override string ToString()
419464
if (_dbiFilePath is not null)
420465
{
421466
sb.AppendLine();
422-
sb.Append($" DBI: {_dbiFilePath}");
467+
string verify = VerifyDebugLibrarySignature(_dbiFilePath) ? "(verify)" : "(don't verify)";
468+
sb.Append($" DBI: {_dbiFilePath} {verify}");
423469
}
424470
return sb.ToString();
425471
}

src/Microsoft.Diagnostics.DebugServices/IRuntime.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public interface IRuntime
7575
/// <summary>
7676
/// Returns the DBI file path
7777
/// </summary>
78-
string GetDbiFilePath();
78+
/// <param name="verifySignature">returns whether the returned DBI requires signature verification.</param>
79+
string GetDbiFilePath(out bool verifySignature);
7980
}
8081
}

src/Microsoft.SymbolStore/KeyGenerators/ELFFileKeyGenerator.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,14 @@ public static IEnumerable<SymbolStoreKey> GetKeys(KeyTypeFlags flags, string pat
166166
// Creates all the special CLR keys if the path is the coreclr module for this platform
167167
if (fileName == CoreClrFileName)
168168
{
169-
foreach (string specialFileName in (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles)
169+
IEnumerable<string> specialFiles = (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles;
170+
if ((flags & KeyTypeFlags.WindowsDebuggingLibrariesOnly) != 0)
171+
{
172+
// Only the Windows-hosted cross-OS DAC/DBI (mscordaccore.dll / mscordbi.dll)
173+
// are PE images that can be authenticode-verified. Skip the native ELF images.
174+
specialFiles = specialFiles.Where(static specialFile => specialFile.EndsWith(".dll", StringComparison.OrdinalIgnoreCase));
175+
}
176+
foreach (string specialFileName in specialFiles)
170177
{
171178
yield return BuildKey(specialFileName, CoreClrPrefix, buildId);
172179
}

src/Microsoft.SymbolStore/KeyGenerators/KeyGenerator.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ public enum KeyTypeFlags
6565
/// <summary>
6666
/// Generate the r2r perfmap key of the binary (if one exists).
6767
/// </summary>
68-
PerfMapKeys = 0x80
68+
PerfMapKeys = 0x80,
69+
70+
/// <summary>
71+
/// When combined with <see cref="ClrKeys"/> or <see cref="DacDbiKeys"/>, restricts the
72+
/// generated DAC/DBI/SOS special-file keys to Windows (PE) images only: the native Windows
73+
/// DAC/DBI and the Windows-hosted cross-OS DAC/DBI. This is a defense in depth measure
74+
/// to ensure we only enumerate files we can authenticate when that are intended to run.
75+
/// </summary>
76+
WindowsDebuggingLibrariesOnly = 0x100
6977
}
7078

7179
/// <summary>

src/Microsoft.SymbolStore/KeyGenerators/MachOKeyGenerator.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,12 @@ public static IEnumerable<SymbolStoreKey> GetKeys(KeyTypeFlags flags, string pat
141141
/// Creates all the special CLR keys if the path is the coreclr module for this platform
142142
if (fileName == CoreClrFileName)
143143
{
144-
foreach (string specialFileName in (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles)
144+
IEnumerable<string> specialFiles = (flags & KeyTypeFlags.ClrKeys) != 0 ? s_coreClrSpecialFiles : s_dacdbiSpecialFiles;
145+
if ((flags & KeyTypeFlags.WindowsDebuggingLibrariesOnly) != 0)
146+
{
147+
specialFiles = specialFiles.Where(static specialFile => specialFile.EndsWith(".dll", StringComparison.OrdinalIgnoreCase));
148+
}
149+
foreach (string specialFileName in specialFiles)
145150
{
146151
yield return BuildKey(specialFileName, CoreClrPrefix, uuid);
147152
}

src/SOS/SOS.Hosting/RuntimeWrapper.cs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ private IntPtr CreateClrDataProcess(IntPtr dacHandle)
383383

384384
private IntPtr CreateCorDebugProcess()
385385
{
386-
string dbiFilePath = _runtime.GetDbiFilePath();
386+
string dbiFilePath = _runtime.GetDbiFilePath(out bool verifyDbiSignature);
387387
if (dbiFilePath == null)
388388
{
389389
Trace.TraceError($"Could not find matching DBI {dbiFilePath ?? ""} for this runtime: {_runtime.RuntimeModule.FileName}");
@@ -405,16 +405,13 @@ private IntPtr CreateCorDebugProcess()
405405

406406
if (_dbiHandle == IntPtr.Zero)
407407
{
408-
try
409-
{
410-
_dbiHandle = DataTarget.PlatformFunctions.LoadLibrary(dbiFilePath);
411-
}
412-
catch (Exception ex) when (ex is DllNotFoundException or BadImageFormatException)
408+
// Verify the DBI signature (when required) and load it, holding the verification
409+
// file lock through LoadLibrary to prevent a TOCTOU swap between verify and load.
410+
_dbiHandle = VerifyAndLoadLibrary(dbiFilePath, verifyDbiSignature, "DBI");
411+
if (_dbiHandle == IntPtr.Zero)
413412
{
414-
Trace.TraceError($"LoadLibrary({dbiFilePath}) FAILED {ex}");
415413
return IntPtr.Zero;
416414
}
417-
Debug.Assert(_dbiHandle != IntPtr.Zero);
418415
}
419416
ClrDebuggingVersion maxDebuggerSupportedVersion = new()
420417
{
@@ -549,38 +546,53 @@ private IntPtr GetCDacHandle()
549546
return _cdacHandle;
550547
}
551548

552-
private static IntPtr LoadDacLibrary(string dacFilePath, bool verifySignature)
549+
/// <summary>
550+
/// Verifies the signature (when required) and loads the native library, holding the
551+
/// verification file lock through LoadLibrary to prevent a TOCTOU race where the file could
552+
/// be swapped after verification but before loading. Returns IntPtr.Zero on failure.
553+
/// </summary>
554+
private static IntPtr VerifyAndLoadLibrary(string filePath, bool verifySignature, string description)
553555
{
554-
IntPtr dacHandle = IntPtr.Zero;
556+
IntPtr handle = IntPtr.Zero;
555557
IDisposable fileLock = null;
556558
try
557559
{
558560
if (verifySignature)
559561
{
560-
Trace.TraceInformation($"Verifying DAC signing and cert {dacFilePath}");
562+
Trace.TraceInformation($"Verifying {description} signing and cert {filePath}");
561563

562-
// Check if the DAC cert is valid before loading
563-
if (!AuthenticodeUtil.VerifyDacDll(dacFilePath, out fileLock))
564+
// Check if the cert is valid before loading
565+
if (!AuthenticodeUtil.VerifyDacDll(filePath, out fileLock))
564566
{
565567
return IntPtr.Zero;
566568
}
567569
}
568570
try
569571
{
570-
dacHandle = DataTarget.PlatformFunctions.LoadLibrary(dacFilePath);
572+
handle = DataTarget.PlatformFunctions.LoadLibrary(filePath);
571573
}
572574
catch (Exception ex) when (ex is DllNotFoundException or BadImageFormatException)
573575
{
574-
Trace.TraceError($"LoadLibrary({dacFilePath}) FAILED {ex}");
576+
Trace.TraceError($"LoadLibrary({filePath}) FAILED {ex}");
575577
return IntPtr.Zero;
576578
}
577579
}
578580
finally
579581
{
580-
// Keep DAC file locked until it loaded
582+
// Keep the file locked until it is loaded
581583
fileLock?.Dispose();
582584
}
583-
Debug.Assert(dacHandle != IntPtr.Zero);
585+
Debug.Assert(handle != IntPtr.Zero);
586+
return handle;
587+
}
588+
589+
private static IntPtr LoadDacLibrary(string dacFilePath, bool verifySignature)
590+
{
591+
IntPtr dacHandle = VerifyAndLoadLibrary(dacFilePath, verifySignature, "DAC");
592+
if (dacHandle == IntPtr.Zero)
593+
{
594+
return IntPtr.Zero;
595+
}
584596
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
585597
{
586598
DllMainDelegate dllmain = SOSHost.GetDelegateFunction<DllMainDelegate>(dacHandle, "DllMain");

src/Tools/dotnet-symbol/Program.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ public static void Main(string[] args)
161161

162162
case "--debugging":
163163
program.Debugging = true;
164+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
165+
{
166+
// Native (ELF/Mach-O) DAC/DBI are never downloaded from the symbol server because they
167+
// are loaded unverified; only the Windows and Windows-hosted cross-OS (PE) debugging
168+
// libraries are. Warn so users on non-Windows know to obtain the matching native DAC/DBI
169+
// from the runtime install or via SOS 'setclrpath' when debugging on this platform.
170+
tracer.Warning("Native DAC/DBI debugging libraries are not downloaded on this platform; only the Windows cross-OS debugging libraries are. Obtain native DAC/DBI from the matching runtime or via 'setclrpath'.");
171+
}
164172
break;
165173

166174
case "--windows-pdbs":
@@ -381,6 +389,15 @@ private IEnumerable<SymbolStoreKeyWrapper> GetKeys()
381389
{
382390
flags |= KeyTypeFlags.ForceWindowsPdbs;
383391
}
392+
if ((flags & (KeyTypeFlags.ClrKeys | KeyTypeFlags.DacDbiKeys)) != 0)
393+
{
394+
// Only download DAC/DBI images that the loader (SOS/dotnet-dump) can authenticode-verify
395+
// before running them: the native Windows DAC/DBI and the Windows-hosted cross-OS DAC/DBI
396+
// (PE images). Native ELF/Mach-O DAC/DBI are never downloaded here because they are loaded
397+
// unverified; they must be provided locally from the matching (trusted) runtime. This holds
398+
// on every host because the file downloaded now may be loaded later by a tool on any OS.
399+
flags |= KeyTypeFlags.WindowsDebuggingLibrariesOnly;
400+
}
384401
foreach (SymbolStoreKeyWrapper wrapper in generator.GetKeys(flags).Select((key) => new SymbolStoreKeyWrapper(key, inputFile)))
385402
{
386403
count++;
@@ -403,7 +420,6 @@ private IEnumerable<KeyGenerator> GetKeyGenerators(string inputFile)
403420
using (Stream inputStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read))
404421
{
405422
SymbolStoreFile file = new(inputStream, inputFile);
406-
string extension = Path.GetExtension(inputFile);
407423
yield return new FileKeyGenerator(Tracer, file);
408424
}
409425
}

src/tests/DbgShim.UnitTests/DbgShimTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ await RemoteInvoke(config, nameof(OpenVirtualProcess), static (string configXml)
261261
IRuntime runtime = runtimeService.EnumerateRuntimes().Single();
262262

263263
CorDebugDataTargetWrapper dataTarget = new(target.Services, runtime);
264-
LibraryProviderWrapper libraryProvider = new(target.OperatingSystem, runtime.RuntimeModule.BuildId, runtime.GetDbiFilePath(), runtime.GetDacFilePath(out bool verifySignature));
264+
LibraryProviderWrapper libraryProvider = new(target.OperatingSystem, runtime.RuntimeModule.BuildId, runtime.GetDbiFilePath(out bool _), runtime.GetDacFilePath(out _));
265265
ClrDebuggingVersion maxDebuggerSupportedVersion = new()
266266
{
267267
StructVersion = 0,

0 commit comments

Comments
 (0)