miniwolf
miniwolf

Reputation: 339

Verification error on unsafe c# return type using Peverify and ILVerify

I have run into this issue when verifying some code containing an unsafe method that returns a pointer.

The example can be expressed as this:

public class A
{
    public static unsafe int* GetAnswer()
    {
        int fakeValue = 42;
        return &(fakeValue);
    }

    public static void Main()
    {
        int i = 0;
        unsafe { i = *A.GetAnswer(); }
        System.Console.WriteLine(i);
    }
}

I am using two separate verification tools, namely ILVerify and Peverify.

Steps to reproduce:

  1. compile example code using csc example.cs /t:library /unsafe
  2. verify peverify example.dll
  3. verify ILVerify.exe -r C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll example.dll

Both 2. and 3. will result in the error message below:

[IL]: Error: [C:\src\test\example.dll : A::GetAnswer()][offset 0x00000006][found address of Int32] Expected numeric type on the stack.

[IL]: Error: [C:\src\test\example.dll : A::Main()][offset 0x00000009][found Native Int] Expected ByRef on the stack. 2 Error(s) Verifying C:\src\test\example.dll

The mystery is that everything compiles and runs as expected, it will not verify. Does anyone have some insight knowledge about why this is the case?

Upvotes: 7

Views: 445

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1062975

Fundamentally: unsafe code is unverifiable. The exact messages you get back will often be vague and confusing, but then again: so is unsafe code (badum tsh)!

Worse: the code in the question is actively broken - there is no defined behaviour for what happens when you access a pointer from a stack-frame that has exited. In this case you'll usually get away with it and see the last values, but: it isn't defined.

If you want verifiable code, you're going to need to switch to ref return; for example:

static ref int GetAnswer(int[] arr)
{
    return ref arr[0];
}

static void Main()
{
    int i = 0;
    int[] j = new int[] { 42 };
    i = A.GetAnswer(j);
    System.Console.WriteLine(i);
}

This uses no unsafe code. GetAnswer returns a reference to the first element in the array (not the value of the first element) - as a managed pointer (ref T is a managed pointer; T* is an unmanaged pointer). Assigning i = {someRef} (rather than i = ref {someRef}) deferences the managed pointer, exactly like i = *{somePtr} does for an unmanaged pointer.

This verifies cleanly:

Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.0
Copyright (c) Microsoft Corporation.  All rights reserved.

All Classes and Methods in ConsoleApp35.exe Verified.

Upvotes: 7

Related Questions