You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ilasm: -DET derives the MVID, PE timestamp and PDB ID from a zero-filled buffer, so different assemblies share them #135214
With -DET, the native ilasm (src/coreclr/ilasm) derives the deterministic MVID, PE timestamp and PDB ID from SHA-256 of the metadata block. But it hashes that block before any metadata has been written into it. The block is still all zeros at that point, so the identity depends only on the metadata's size, not on the assembly's content. Different assemblies whose metadata is the same size get the same MVID, PE timestamp and PDB ID. This also happens without -DEBUG, for the MVID and timestamp.
Reproduction
Save this as A/Min.il, and again as B/Min.il with ldc.i4.1 changed to ldc.i4.2. The two files differ only in the method body, so their metadata is byte-identical:
ids.cs is a file-based app (.NET 10 SDK or later):
#:property UseAppHost=false
using System.Reflection.Metadata;usingSystem.Reflection.PortableExecutable;foreach(vardllinargs){usingvarpe=newPEReader(File.OpenRead(dll));varmd=pe.GetMetadataReader();varmvid=md.GetGuid(md.GetModuleDefinition().Mvid);usingvarpdb=MetadataReaderProvider.FromPortablePdbStream(File.OpenRead(Path.ChangeExtension(dll,".pdb")));varid=Convert.ToHexString(pdb.GetMetadataReader().DebugMetadataHeader!.Id.ToArray());Console.WriteLine($"{dll}: MVID {mvid} | PE stamp {(uint)pe.PEHeaders.CoffHeader.TimeDateStamp} | PDB ID {id}");}
Output with ilasm 10.0.0 (runtime.linux-x64.Microsoft.NETCore.ILAsm) on Linux x64:
A/Min.dll: MVID 942b4c7c-410c-6e42-36a4-cf6c83afabab | PE stamp 2978741164 | PDB ID 7C4C2B940C41426E36A4CF6C83AFABAB00000000
B/Min.dll: MVID 942b4c7c-410c-6e42-36a4-cf6c83afabab | PE stamp 2978741164 | PDB ID 7C4C2B940C41426E36A4CF6C83AFABAB00000000
The same identity comes out for a pair whose metadata differs in content but not in size (renaming F to G), and for a pair that differs only in a .line directive, whose PDBs differ in their sequence points. This metadata block is 464 bytes. The MVID is the first 16 bytes of SHA-256 of 464 zero bytes, and the PE stamp is the next 4. The PDB ID's trailing zero stamp is a separate defect, #135211.
Impact
Different module contents get the same MVID, so anything that keys cached method bodies or analysis on the MVID and a metadata token cannot tell them apart. Different PDBs share one PDB ID, so symbol stores keyed on the PDB file name and ID (or GUID) can return the wrong PDB. Whether a consumer then accepts the wrong symbols depends on its other checks; #135211 covers the separate matching and checksum defects. Repeated builds of the same input are still byte-identical, so -DET's repeatability is not affected.
Cause
In Assembler::CreatePEFile, GetSectionBlock reserves metaDataSize bytes (L1451). Under -DET the reserved block is hashed straight away (L1461), to produce deterministicGuid and deterministicTimestamp. These feed ChangeMvid (L1470), SetFileHeaderTimeStamp (L1472) and the PDB ID (L1489, L1492).
The metadata is written into the block only later, by EmitMetaDataAt.
This came in with #109091. The existing determinism checks in src/tests/Common/CLRTest.Jit.targets ("ILASM determinism") assemble the same input twice, so they cannot catch an identity that ignores content.
Suggested fix
Derive the identity from the assembly's content.
Hashing the populated metadata is not enough: the two files above have byte-identical metadata, and differ only in the method body in the IL section. The hash has to cover the image, with the MVID, the COFF timestamp and the CodeView GUID and stamp left zero; then those fields and the PDB ID are patched.
For comparison, Roslyn (through System.Reflection.Metadata) uses two hashes. PortablePdbBuilder hashes the PDB with its ID zeroed to get the PDB ID. PEBuilder hashes the PE image, with the MVID and timestamp reserved, to get those two. One hash for all three fields would also do for ilasm.
The write order seems to allow this:
nothing reaches disk before GenerateCeeFile in main.cpp, and the PDB is saved after the DLL;
CreatePEFile already patches the emitted metadata buffer in place after EmitMetaDataAt.
The emitter assigns a random MVID at DefineScope, so it would have to be zeroed before the metadata is emitted.
A regression test would assemble two pairs under -DET: the pair above, and a pair whose metadata differs in content but not in size. It would assert that their MVIDs, timestamps and PDB IDs differ.
This report and its analysis were prepared with AI assistance (Anthropic Claude and OpenAI Codex) under my direction. AI agents ran the reproduction on my machine. I reviewed the text before posting.
Description
With
-DET, the native ilasm (src/coreclr/ilasm) derives the deterministic MVID, PE timestamp and PDB ID from SHA-256 of the metadata block. But it hashes that block before any metadata has been written into it. The block is still all zeros at that point, so the identity depends only on the metadata's size, not on the assembly's content. Different assemblies whose metadata is the same size get the same MVID, PE timestamp and PDB ID. This also happens without-DEBUG, for the MVID and timestamp.Reproduction
Save this as
A/Min.il, and again asB/Min.ilwithldc.i4.1changed toldc.i4.2. The two files differ only in the method body, so their metadata is byte-identical:ids.csis a file-based app (.NET 10 SDK or later):Output with ilasm 10.0.0 (
runtime.linux-x64.Microsoft.NETCore.ILAsm) on Linux x64:The same identity comes out for a pair whose metadata differs in content but not in size (renaming
FtoG), and for a pair that differs only in a.linedirective, whose PDBs differ in their sequence points. This metadata block is 464 bytes. The MVID is the first 16 bytes of SHA-256 of 464 zero bytes, and the PE stamp is the next 4. The PDB ID's trailing zero stamp is a separate defect, #135211.Impact
Different module contents get the same MVID, so anything that keys cached method bodies or analysis on the MVID and a metadata token cannot tell them apart. Different PDBs share one PDB ID, so symbol stores keyed on the PDB file name and ID (or GUID) can return the wrong PDB. Whether a consumer then accepts the wrong symbols depends on its other checks; #135211 covers the separate matching and checksum defects. Repeated builds of the same input are still byte-identical, so
-DET's repeatability is not affected.Cause
Assembler::CreatePEFile,GetSectionBlockreservesmetaDataSizebytes (L1451). Under-DETthe reserved block is hashed straight away (L1461), to producedeterministicGuidanddeterministicTimestamp. These feedChangeMvid(L1470),SetFileHeaderTimeStamp(L1472) and the PDB ID (L1489, L1492).EmitMetaDataAt.CBlobFetcher::CPillar::MakeNewBlockzero-fills new storage.This came in with #109091. The existing determinism checks in
src/tests/Common/CLRTest.Jit.targets("ILASM determinism") assemble the same input twice, so they cannot catch an identity that ignores content.Suggested fix
Derive the identity from the assembly's content.
Hashing the populated metadata is not enough: the two files above have byte-identical metadata, and differ only in the method body in the IL section. The hash has to cover the image, with the MVID, the COFF timestamp and the CodeView GUID and stamp left zero; then those fields and the PDB ID are patched.
For comparison, Roslyn (through System.Reflection.Metadata) uses two hashes.
PortablePdbBuilderhashes the PDB with its ID zeroed to get the PDB ID.PEBuilderhashes the PE image, with the MVID and timestamp reserved, to get those two. One hash for all three fields would also do for ilasm.The write order seems to allow this:
GenerateCeeFileinmain.cpp, and the PDB is saved after the DLL;CreatePEFilealready patches the emitted metadata buffer in place afterEmitMetaDataAt.The emitter assigns a random MVID at
DefineScope, so it would have to be zeroed before the metadata is emitted.A regression test would assemble two pairs under
-DET: the pair above, and a pair whose metadata differs in content but not in size. It would assert that their MVIDs, timestamps and PDB IDs differ.Fix: #135227
Note
This report and its analysis were prepared with AI assistance (Anthropic Claude and OpenAI Codex) under my direction. AI agents ran the reproduction on my machine. I reviewed the text before posting.