Skip to content

SafeHandle marshalling broken with Mono #82308

Description

@uweigand

As of #80715, many System.Net.Sockets.Tests test fail with the error:

System.Exception : Failed to create the 'shadow' (port blocker) socket in 16 attempts.

Looking at the behavior with strace shows the error is caused by failed attempts to bind to file descriptor 0 (which is of course not a socket):

[pid 774258] bind(0, {sa_family=AF_INET6, sin6_port=htons(44087), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_scope_id=0}, 28) = -1 ENOTSOCK (Socket operation on non-socket)

Investigating this closer shows that the bind call happens via a P/Invoke in the new test infrastructure:

[Runtime.InteropServices.DllImport("libc", SetLastError = true)]
static extern int bind(SafeSocketHandle socket, IntPtr socketAddress, uint addrLen);

This seems to trigger marshalling code in Mono that accesses the IntPtr wrapped by the SafeHandle:

/* Pull the handle field from SafeHandle */
cb_to_mono->methodBuilder.emit_ldarg (mb, argnum);
cb_to_mono->methodBuilder.emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle));
cb_to_mono->methodBuilder.emit_byte (mb, CEE_LDIND_I);
cb_to_mono->methodBuilder.emit_stloc (mb, conv_arg);

using the data structure here:
/*
* The definition of the first field in SafeHandle,
* Keep in sync with SafeHandle.cs, this is only used
* to access the `handle' parameter.
*/
typedef struct {
MonoObject base;
void *handle;
} MonoSafeHandle;

However, as of #71991 the definition of the SafeHandle type on the managed side is:

// IMPORTANT:
// - Do not add or rearrange fields as the EE depends on this layout,
// as well as on the values of the StateBits flags.
// - The EE may also perform the same operations using equivalent native
// code, so this managed code must not assume it is the only code
// manipulating _state.
#if DEBUG
private readonly string? _ctorStackTrace;
#endif
/// <summary>Specifies the handle to be wrapped.</summary>
protected IntPtr handle;

Note when the library is built for the Debug configuration, the layout no longer matches - in the place Mono expects the handle field, we now have the _ctorStackTrace field. As this is usually null, we get 0 as file descriptor ...

@stephentoub I see you updated the matching CoreCLR definition with #71991, but not the Mono definition. I guess this should be done there as well - however I'm not clear how to do this only for the Debug version of the library, this is something the Mono runtime doesn't currently appear to be doing ...

Also, I was quite confused why this problem only shows up in this one test, and not for all the "normal" places in the library where SafeHandles are marshalled to native code. I now see this is using a different mechanism:

[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_Bind")]
internal static unsafe partial Error Bind(SafeHandle socket, ProtocolType socketProtocolType, byte* socketAddress, int socketAddressLen);

which doesn't trigger the Mono marshalling code, but generates IL that uses calls to System.Runtime.InteropServices.SafeHandle:DangerousGetHandle instead of accessing the handle at a hard-coded offset.

CC @antonfirsov @vargaz @omajid @tmds

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions