Changes in ILGenerator in .NET 8

  •   Posted in:
  • .NET

I was porting our application to .NET 8 and I found that some 3rd party library throwing InvalidProgramException with the message “Common Language Runtime detected an invalid program”. It is not something that you see often and I started investigating.

The code looks like this:

…
ilgenerator.Emit(OpCodes.Brtrue_S, (sbyte)(OpCodes.Newobj.Size + 4 + OpCodes.Throw.Size));
ilgenerator.ThrowException(typeof(ArgumentOutOfRangeException));
…

That 3rd party library trying to generate IL code that checks a boolean condition and if it is false then throw an exception and otherwise skip that block and continue execution. I reviewed the code it tries to generate and I didn’t find anything suspicious.

At that moment, I had an assumption that the IL code was not valid and newer version of .NET is more strict and does not allow that invalid code. I reviewed that code again and everything looks correct. In the next step, I checked that the code actually executes with .NET 6 and it does. This is an important step as well, because for .NET this code may not be executed at all.

Firstly I copied that code to the new C# console app and reproduced the problem. This is a very important step because even if I can edit the compiled assembly with dnSpy, it is much slower to edit and much slower to try different things because our application is quite big.

Then I checked it in .NET 7 and it also doesn’t work. Sometimes it helps to know where a breaking change is so I can use the proper version when searching the internet. But the search didn’t yield any results.

In the next step, I was trying to create a similar program in C# as that program trying to generate and check the generated IL code. It looks almost identical. Then I changed my test code to be exactly the same but it still does not work.

Then I decided to check binary opcodes for my IL code and code in C#. They also look identical except for references to the methods which are supposed to be different.  And at that moment I changed my assumption to a new one. The code is correct but it looks like I need to use the ILGenerator differently.

Finally, I found a newer version of that library that worked and that code looks differently

Label label = ilgenerator.DefineLabel();
…
ilgenerator.Emit(OpCodes.Brtrue_S, label);
ilgenerator.ThrowException(typeof(ArgumentOutOfRangeException));
ilgenerator.MarkLabel(label);
…

After that, I applied these changes to my code and viola it works fine. Then I decided to output the content of the m_ILStream field from the ILGenerator for the version with .NET 6 and the new version with .NET 8. And the difference was in only one byte. In .NET 6 it would be 6 after brtrue.s instruction and in .NET 8 it would be zero. To me, it looks like it would resolved during the compilation phase.

I believe that Microsoft decided to fix a potential security issue when an application can place an incorrect offset and jump to the wrong place and violate the safety of runtime. And as a result, they are not allowed to use manual offset.

I hope it helps someone.

Comments

Post comment