Marshalling of C Struct as return value of C# delegate

走远了吗. 提交于 2019-12-05 22:34:20

It is by all means fault in .NET.

tl;dr

.NET Framework 2 generates an incorrect (possibly insecure) stub.

How did I find this out?

I conducted some tests:

  1. Instead of a 8-byte long struct, I used 4-byte long struct. It works!
  2. Instead of using x86, I used x64. It works!

Figuring out that it works in all other cases, I decided to native-debug it with windbg to see where it crashes (because Visual Studio won't allow me to "step in" a native call with the disassembly window).

Guess what I found:

The .NET framework generated a stub that is calling memcpy, and it fails when it tries to copy into edi, which at that time had the value 0x16 (==22), which was the parameter sent in the C# code!

So, lets see what would happen if I were to send a valid pointer to the function:

unsafe
{
    long* ptr = &something;
    uint ptr_value = (uint)ptr;
    Console.WriteLine("Pointer address: {0:X}", (long)ptr);

    var statusBlock = someFn(ptr_value);
    Console.WriteLine("A: {0}", statusBlock.statusA);

    Console.WriteLine("B: {0:X}", statusBlock.statusB);
}

Output: (it works and doesn't crash when a valid pointer is given!)

Marshal.SizeOf(typeof(StatusBlock)) = 8
Running .NET Version 2
Pointer address: 49F15C
A: 0
B: 0

So, I conclude this is an unsalvageable problem in .NET Framework 2.

Why is it happening?

When a C function is defined to return a struct larger than 8 bytes, the function shall actually return a pointer to the struct in the local function's stack and the caller should use memcpy to copy it to it's own stack (this is a part of C's specifications and is implemented by the compiler - the programmer simply "returns" a struct and the compiler does the heavy lifting).

However, for 8 bytes (either struct or long long), most C compilers return it in eax:edx. Probably .NET's developers have missed that. The mistake is probably that someone wrote size >= 8 instead size > 8...

Edit: What's worse, it writes the result on the pointer given!

before: 0x1111222244445555
after : 0x1234ABCD007BEF5C

It changes the pointer to be the return value! As you can see, the first dword after the call is is 0x1234ABCD (as in the native DLL) and the second dword is the pointer to the value, i.e the parameter someVal that was given!

It's even more funny, because if you pass a pointer to a StatusBlock struct - it will actually work for this specific case (because the first dword in the return value is used as a pointer)

Solution

Return a long variable and make the struct yourself.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!