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

7

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?

c#
cil
peverify
asked on Stack Overflow Jan 22, 2018 by miniwolf • edited Jun 20, 2020 by Community

1 Answer

7

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.
answered on Stack Overflow Jan 22, 2018 by Marc Gravell

User contributions licensed under CC BY-SA 3.0