Skip to content

"free(): invalid pointer" with AnsiStringMarshaller compiled with crossgen2 #54820

@gbalykov

Description

@gbalykov

Description

If marshalling ilstub compilation is allowed in crossgen2, ansi string marshalling for string allocated in native code leads to "free(): invalid pointer".

Problem is related to out string argument marshalling, which tries to free native_pointer - sizeof(void*).

However, it seems that this problem might arise in SPC.dll even without any changes in crossgen2 because currently all such ilstubs are compiled, see GeneratesPInvoke:

            if (((EcmaMethod)method).Module.Equals(method.Context.SystemModule))
                return true;

Simple example to reproduce:

c# part:

using System;
using System.Runtime.InteropServices;

namespace ndirect
{
    class Program
    {
        [DllImport("tmp.so", EntryPoint = "test_func")]
        internal static extern int testFunc(out string value);

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            string a;
            testFunc(out a);
            Console.WriteLine(a);
        }
    }
}

native part, compiled with gcc -shared -fPIC 1.c -o tmp.so

#include <malloc.h>
#include <string.h>

int test_func(char ** str)
{
  char * s = (char*) malloc(100);
  *str = s;
  strcpy(s, "my test string!");
  return strlen(s);
}

Output:

Hello World!
free(): invalid pointer
Aborted (core dumped)

Native code of compiled ilstub, which crashes:

10a10: 55                       push    rbp
                                UWOP_PUSH_NONVOL RBP(5)                                0

10a11: 41 57                    push    r15
                                UWOP_PUSH_NONVOL R15(15)                                0

10a13: 41 56                    push    r14
                                UWOP_PUSH_NONVOL R14(14)                                0

10a15: 41 55                    push    r13
                                UWOP_PUSH_NONVOL R13(13)                                0

10a17: 41 54                    push    r12
                                UWOP_PUSH_NONVOL R12(12)                                0

10a19: 53                       push    rbx
                                UWOP_PUSH_NONVOL RBX(3)                                0

10a1a: 48 83 ec 78              sub     rsp, 120
                                UWOP_ALLOC_SMALL 120                                0

10a1e: 48 8d ac 24 a0 00 00 00  lea     rbp, [rsp + 160]
10a26: 33 c0                    xor     eax, eax
10a28: 48 89 45 d0              mov     qword ptr [rbp - 48], rax
10a2c: 48 89 a5 60 ff ff ff     mov     qword ptr [rbp - 160], rsp
10a33: 48 89 7d c8              mov     qword ptr [rbp - 56], rdi
10a37: 48 8d 7d d0              lea     rdi, [rbp - 48]
10a3b: 48 89 7d c0              mov     qword ptr [rbp - 64], rdi
10a3f: 48 8d bd 68 ff ff ff     lea     rdi, [rbp - 152]
10a46: ff 15 ec 08 01 00        call    qword ptr [0x21338]   // PINVOKE_BEGIN (HELPER)
10a4c: 48 8b 05 25 09 01 00     mov     rax, qword ptr [0x21378] // int ndirect.Program.testFunc(ref string) (INDIRECT_PINVOKE_TARGET)
10a53: 48 8b 7d c0              mov     rdi, qword ptr [rbp - 64]
10a57: ff 10                    call    qword ptr [rax]
10a59: 8b d8                    mov     ebx, eax
10a5b: 48 8d bd 68 ff ff ff     lea     rdi, [rbp - 152]
10a62: ff 15 d8 08 01 00        call    qword ptr [0x21340]   // PINVOKE_END (HELPER)
10a68: 48 8b 7d d0              mov     rdi, qword ptr [rbp - 48]
10a6c: ff 15 e6 08 01 00        call    qword ptr [0x21358]   // string System.StubHelpers.AnsiBSTRMarshaler.ConvertToManaged(IntPtr) (METHOD_ENTRY_DEF_TOKEN)
                                RAX is live
10a72: 48 8b 7d c8              mov     rdi, qword ptr [rbp - 56]
                                RDI is live
10a76: 48 8b f0                 mov     rsi, rax
                                RSI is live
                                sp-56 is dead
10a79: ff 15 b1 08 01 00        call    qword ptr [0x21330]   // CHECKED_WRITE_BARRIER (HELPER)
                                RDI is dead
                                RAX is dead
                                RSI is dead
10a7f: 90                       nop
10a80: 48 8b 7d d0              mov     rdi, qword ptr [rbp - 48]
10a84: ff 15 d6 08 01 00        call    qword ptr [0x21360]   // void System.StubHelpers.AnsiBSTRMarshaler.ClearNative(IntPtr) (METHOD_ENTRY_DEF_TOKEN)
10a8a: 8b c3                    mov     eax, ebx
10a8c: 48 83 c4 78              add     rsp, 120
10a90: 5b                       pop     rbx
10a91: 41 5c                    pop     r12
10a93: 41 5d                    pop     r13
10a95: 41 5e                    pop     r14
10a97: 41 5f                    pop     r15
10a99: 5d                       pop     rbp
10a9a: c3                       ret

Patch to allow marshalling ilstub compilation:

commit 129e85a182b9ed8561b3428f647df8dcf93ae49d
Author: Gleb Balykov <g.balykov@samsung.com>
Date:   Mon Jun 28 12:53:58 2021 +0300

    Partially allow marshalling ILSTUB compilation for ndirect methods using crossgen2 if SPC.dll is in the same bubble
    
    AnsiStringMarshaller is disallowed because it incorrectly handles pointers returned from native code.

diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs
index 92ef5346c77..eaa2821ac94 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs
@@ -329,10 +329,8 @@ public sealed override bool GeneratesPInvoke(MethodDesc method)
         {
             // PInvokes depend on details of the core library, so for now only compile them if:
             //    1) We're compiling the core library module, or
-            //    2) We're compiling any module, and no marshalling is needed
-            //
-            // TODO Future: consider compiling PInvokes with complex marshalling in version bubble
-            // mode when the core library is included in the bubble.
+            //    2) We're compiling any module, and no marshalling is needed, or
+            //    3) We're compiling any module, and core library module is in the same bubble, and marhaller supports compilation
 
             Debug.Assert(method is EcmaMethod);
 
@@ -344,6 +342,9 @@ public sealed override bool GeneratesPInvoke(MethodDesc method)
             if (((EcmaMethod)method).Module.Equals(method.Context.SystemModule))
                 return true;
 
+            if (_versionBubbleModuleSet.Contains(method.Context.SystemModule))
+                return !Marshaller.IsMarshallingNotSupported(method);
+
             return !Marshaller.IsMarshallingRequired(method);
         }
 
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Interop/IL/Marshaller.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Interop/IL/Marshaller.ReadyToRun.cs
index 636c698e8d5..4bc3d1340e2 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Interop/IL/Marshaller.ReadyToRun.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Interop/IL/Marshaller.ReadyToRun.cs
@@ -141,5 +141,37 @@ public static bool IsMarshallingRequired(MethodSignature methodSig, ParameterMet
 
             return false;
         }
+
+        public static bool IsMarshallingNotSupported(MethodDesc targetMethod)
+        {
+            Debug.Assert(targetMethod.IsPInvoke);
+
+            if (targetMethod.IsUnmanagedCallersOnly)
+                return true;
+
+            PInvokeMetadata metadata = targetMethod.GetPInvokeMethodMetadata();
+            PInvokeFlags flags = metadata.Flags;
+
+            if (flags.SetLastError)
+                return true;
+
+            if (!flags.PreserveSig)
+                return true;
+
+            if (MarshalHelpers.ShouldCheckForPendingException(targetMethod.Context.Target, metadata))
+                return true;
+
+            var marshallers = GetMarshallersForMethod(targetMethod);
+            for (int i = 0; i < marshallers.Length; i++)
+            {
+                if (marshallers[i].GetType() == typeof(NotSupportedMarshaller))
+                    return true;
+            }
+
+            return false;
+        }
     }
 }

More detailed description

This problem happens inside System.StubHelpers.AnsiBSTRMarshaler.ClearNative, which tries to cleanup bstr buffer, and subtracts sizeof(void*) from ptr (Marshal.FreeBSTR in Marshal.Unix.cs actually performs subtraction). This itself happens because System.StubHelpers.AnsiBSTRMarshaler.ConvertToManaged for some reason ignores non-bstr buffers. However, pointer returned from native code is not bstr, but a simple buffer. So, incorrect pointer is passed to free.

cc @alpencolt @jkotas

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions