jbevain / cecil

Cecil is a library to inspect, modify and create .NET programs and libraries.
MIT License
2.71k stars 619 forks source link

Possible ArgumentOutOfRangeException when there are 65535 params in metadata table #913

Open UlyssesWu opened 1 year ago

UlyssesWu commented 1 year ago

https://github.com/jbevain/cecil/blob/ba9c6c79f5b82c68517cb005fb1174cea7805a64/Mono.Cecil/AssemblyReader.cs#L979-L1003

(Sorry but I'm unable to provide a sample dll.)

We have a big dll which exactly has 65535 parameters in metadata table. image

When processing it using cecil, it throw exception like this:

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
    at Mono.Collections.Generic.Collection`1[T].get_Item (System.Int32 index)
    at Mono.Cecil.MetadataReader.ReadParameter (System.UInt32 param_rid, Mono.Cecil.MethodDefinition method)
    at Mono.Cecil.MetadataReader.ReadParameters (Mono.Cecil.MethodDefinition method, Mono.Cecil.Range param_range)
    at Mono.Cecil.MetadataReader.ReadMethod (System.UInt32 method_rid, Mono.Collections.Generic.Collection`1[T] methods)
    at Mono.Cecil.MetadataReader.ReadMethods (Mono.Cecil.TypeDefinition type)
    at Mono.Cecil.TypeDefinition+<>c.<get_Methods>b__43_0 (Mono.Cecil.TypeDefinition type, Mono.Cecil.MetadataReader reader)
    at Mono.Cecil.ModuleDefinition.Read[TItem,TRet] (TRet& variable, TItem item, System.Func`3[T1,T2,TResult] read)
    at Mono.Cecil.TypeDefinition.get_Methods ()

After some debugging I managed to locate the problem:

https://github.com/jbevain/cecil/blob/ba9c6c79f5b82c68517cb005fb1174cea7805a64/Mono.Cecil/AssemblyReader.cs#L995

When it's trying to read the 65535th (the last) param (it is used in a compiler-generated anonymous method), and image.GetTableIndexSize returns 2 (entry < 65536), ReadTableIndex returns 0, next_index = 0.

https://github.com/jbevain/cecil/blob/ba9c6c79f5b82c68517cb005fb1174cea7805a64/Mono.Cecil/AssemblyReader.cs#L1000

list.Length = next_index - start = 0u - 65535u = 4294901761 💥

image

On the other hand, dnlib already handled this problem, so it still works for our dll:

https://github.com/0xd4d/dnlib/blob/97e07a8f1ea0ccbf31231dad0f7cb093805b8eee/src/DotNet/MD/CompressedMetadata.cs#L143-L157

The magic happens here: image

jbevain commented 1 year ago

That's a good catch, thank you :)

jbevain commented 1 year ago

The magic actually happens here in dnlib:

uint endRid = !hasNext || (nextListRid == 0 && tableSourceRid + 1 == tableSource.Rows && tableDest.Rows == 0xFFFF) ? lastRid : nextListRid;
UlyssesWu commented 1 year ago
uint endRid = !hasNext || (nextListRid == 0 && tableSourceRid + 1 == tableSource.Rows && tableDest.Rows == 0xFFFF) ? lastRid : nextListRid;

Thanks for pointing out. In my situation, endRid was set to 0 at this line. Then it was set to 65535 in L153, which I thought was the key to keep endRid - startRid correct in L156.