Skip to content

Commit a216dfb

Browse files
[xabt] Add HasMetadata checks to all PEReader code paths to skip native Windows DLLs (#10938)
Context: #10862 ## Summary Extracted from PR #10862: Add `PEReader.HasMetadata` checks before calling `GetMetadataReader()` across all code paths that use `PEReader` to prevent exceptions when processing native (non-.NET) PE DLLs. ## Problem When NuGet packages (like MSTest) include native Windows `.dll` files that flow into `ResolvedFileToPublish`, calling `PEReader.GetMetadataReader()` on these files throws an exception because they are not .NET assemblies and have no CLI metadata. ## Fix Added `PEReader.HasMetadata` guards before `GetMetadataReader()` calls across all relevant code paths. In MSBuild tasks with log access, a `LogDebugMessage()` call is included when skipping non-.NET assemblies. Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
1 parent 64e5a28 commit a216dfb

File tree

7 files changed

+101
-1
lines changed

7 files changed

+101
-1
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/CheckForInvalidDesignerConfig.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ static bool HasResourceDesignerAssemblyReference (ITaskItem assembly)
3939
return false;
4040
}
4141
using var pe = new PEReader (File.OpenRead (assembly.ItemSpec));
42+
if (!pe.HasMetadata) {
43+
return false;
44+
}
4245
var reader = pe.GetMetadataReader ();
4346
return HasResourceDesignerAssemblyReference (reader);
4447
}

src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public override bool RunTask ()
5757
void ProcessAssembly(ITaskItem assemblyItem, List<ITaskItem> output)
5858
{
5959
using var pe = new PEReader (File.OpenRead (assemblyItem.ItemSpec));
60+
if (!pe.HasMetadata) {
61+
Log.LogDebugMessage ($"Skipping non-.NET assembly: {assemblyItem.ItemSpec}");
62+
return;
63+
}
6064
var reader = pe.GetMetadataReader ();
6165
// Check in-memory cache
6266
var module = reader.GetModuleDefinition ();

src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ void Extract (
256256
Log.LogDebugMessage ($"Refreshing {fileName}");
257257

258258
using (var pe = new PEReader (File.OpenRead (assemblyPath))) {
259+
if (!pe.HasMetadata) {
260+
Log.LogDebugMessage ($"Skipping non-.NET assembly: {assemblyPath}");
261+
continue;
262+
}
259263
var reader = pe.GetMetadataReader ();
260264
foreach (var handle in reader.ManifestResources) {
261265
var resource = reader.GetManifestResource (handle);

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/FilterAssembliesTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,78 @@ public async Task GuavaListenableFuture ()
100100
var expected = new [] { "Xamarin.Google.Guava.ListenableFuture.dll" };
101101
CollectionAssert.AreEqual (expected, actual);
102102
}
103+
104+
[Test]
105+
public void NativeDll_Skipped ()
106+
{
107+
// Create a minimal valid PE file without CLI metadata (a native DLL)
108+
var nativeDll = Path.Combine (tempDirectory, "native.dll");
109+
CreateNativePE (nativeDll);
110+
var actual = Run (nativeDll);
111+
CollectionAssert.IsEmpty (actual, "Native DLLs without CLI metadata should be skipped.");
112+
}
113+
114+
/// <summary>
115+
/// Creates a minimal valid PE file without CLI metadata, simulating a native DLL.
116+
/// </summary>
117+
static void CreateNativePE (string path)
118+
{
119+
using var fs = File.Create (path);
120+
using var writer = new BinaryWriter (fs);
121+
122+
// DOS header: 'MZ' magic + padding to e_lfanew at offset 0x3C
123+
writer.Write ((ushort) 0x5A4D); // e_magic = 'MZ'
124+
writer.Write (new byte [58]); // pad to offset 0x3C
125+
writer.Write ((uint) 0x80); // e_lfanew = 0x80
126+
127+
// Pad to PE signature at offset 0x80
128+
writer.Write (new byte [0x80 - 0x40]);
129+
130+
// PE signature: 'PE\0\0'
131+
writer.Write ((uint) 0x00004550);
132+
133+
// COFF header (20 bytes)
134+
writer.Write ((ushort) 0x14C); // Machine = IMAGE_FILE_MACHINE_I386
135+
writer.Write ((ushort) 0); // NumberOfSections = 0
136+
writer.Write ((uint) 0); // TimeDateStamp
137+
writer.Write ((uint) 0); // PointerToSymbolTable
138+
writer.Write ((uint) 0); // NumberOfSymbols
139+
writer.Write ((ushort) 0xE0); // SizeOfOptionalHeader (PE32)
140+
writer.Write ((ushort) 0x2102); // Characteristics = DLL | EXECUTABLE_IMAGE | LARGE_ADDRESS_AWARE
141+
142+
// Optional header (PE32) — minimal, no CLI header directory entry
143+
writer.Write ((ushort) 0x10B); // Magic = PE32
144+
writer.Write ((byte) 0); // MajorLinkerVersion
145+
writer.Write ((byte) 0); // MinorLinkerVersion
146+
writer.Write ((uint) 0); // SizeOfCode
147+
writer.Write ((uint) 0); // SizeOfInitializedData
148+
writer.Write ((uint) 0); // SizeOfUninitializedData
149+
writer.Write ((uint) 0); // AddressOfEntryPoint
150+
writer.Write ((uint) 0); // BaseOfCode
151+
writer.Write ((uint) 0); // BaseOfData
152+
writer.Write ((uint) 0x10000); // ImageBase
153+
writer.Write ((uint) 0x1000); // SectionAlignment
154+
writer.Write ((uint) 0x200); // FileAlignment
155+
writer.Write ((ushort) 4); // MajorOperatingSystemVersion
156+
writer.Write ((ushort) 0); // MinorOperatingSystemVersion
157+
writer.Write ((ushort) 0); // MajorImageVersion
158+
writer.Write ((ushort) 0); // MinorImageVersion
159+
writer.Write ((ushort) 4); // MajorSubsystemVersion
160+
writer.Write ((ushort) 0); // MinorSubsystemVersion
161+
writer.Write ((uint) 0); // Win32VersionValue
162+
writer.Write ((uint) 0x1000); // SizeOfImage
163+
writer.Write ((uint) 0x200); // SizeOfHeaders
164+
writer.Write ((uint) 0); // CheckSum
165+
writer.Write ((ushort) 3); // Subsystem = WINDOWS_CUI
166+
writer.Write ((ushort) 0); // DllCharacteristics
167+
writer.Write ((uint) 0x100000); // SizeOfStackReserve
168+
writer.Write ((uint) 0x1000); // SizeOfStackCommit
169+
writer.Write ((uint) 0x100000); // SizeOfHeapReserve
170+
writer.Write ((uint) 0x1000); // SizeOfHeapCommit
171+
writer.Write ((uint) 0); // LoaderFlags
172+
writer.Write ((uint) 16); // NumberOfRvaAndSizes
173+
// Data directories (16 entries × 8 bytes = 128 bytes), all zeroed — no CLI header
174+
writer.Write (new byte [128]);
175+
}
103176
}
104177
}

src/Xamarin.Android.Build.Tasks/Utilities/MetadataResolver.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ public MetadataReader GetAssemblyReader (string assemblyName)
1919
{
2020
var assemblyPath = Resolve (assemblyName);
2121
if (!cache.TryGetValue (assemblyPath, out PEReader reader)) {
22-
cache.Add (assemblyPath, reader = new PEReader (File.OpenRead (assemblyPath)));
22+
reader = new PEReader (File.OpenRead (assemblyPath));
23+
if (!reader.HasMetadata) {
24+
reader.Dispose ();
25+
throw new InvalidOperationException ($"Assembly '{assemblyPath}' is not a .NET assembly.");
26+
}
27+
cache.Add (assemblyPath, reader);
2328
}
2429
return reader.GetMetadataReader ();
2530
}

src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ public static bool HasMonoAndroidReference (ITaskItem assembly)
353353
return true;
354354

355355
using var pe = new PEReader (File.OpenRead (assembly.ItemSpec));
356+
if (!pe.HasMetadata) {
357+
return false; // not a .NET assembly (no CLI metadata)
358+
}
356359
var reader = pe.GetMetadataReader ();
357360
return HasMonoAndroidReference (reader);
358361
}
@@ -374,6 +377,10 @@ public static bool IsReferenceAssembly (string assembly, TaskLoggingHelper log)
374377
{
375378
using (var stream = File.OpenRead (assembly))
376379
using (var pe = new PEReader (stream)) {
380+
if (!pe.HasMetadata) {
381+
log.LogDebugMessage ($"Skipping non-.NET assembly: {assembly}");
382+
return false;
383+
}
377384
var reader = pe.GetMetadataReader ();
378385
var assemblyDefinition = reader.GetAssemblyDefinition ();
379386
foreach (var handle in assemblyDefinition.GetCustomAttributes ()) {

src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public void CreateImportMethods (IEnumerable<ITaskItem> libraries)
4444
primary.Members.Add (method);
4545
foreach (var assemblyPath in libraries) {
4646
using (var pe = new PEReader (File.OpenRead (assemblyPath.ItemSpec))) {
47+
if (!pe.HasMetadata) {
48+
Log.LogDebugMessage ($"Skipping non-.NET assembly: {assemblyPath.ItemSpec}");
49+
continue;
50+
}
4751
var reader = pe.GetMetadataReader ();
4852
var resourceDesignerName = GetResourceDesignerClass (reader);
4953
if (string.IsNullOrEmpty (resourceDesignerName)) {

0 commit comments

Comments
 (0)