Search code examples
c#pinvokesafehandle

Constraints vs abstract class using SafeHandle


There is a method within BCryptNative called GetInt32Property. It has the following signature:

internal static int GetInt32Property<T>(T algorithm, string property) where T : SafeHandle

This method only works when T is of type SafeBCryptAlgorithmHandle or SafeBCryptHashHandle. It calls native methods which are explicitly defined with those types of handles:

[DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty", CharSet = CharSet.Unicode)]
internal static extern ErrorCode BCryptGetAlgorithmProperty(SafeBCryptAlgorithmHandle hObject,
                                                            string pszProperty,
                                                            [MarshalAs(UnmanagedType.LPArray), In, Out] byte[] pbOutput,
                                                            int cbOutput,
                                                            [In, Out] ref int pcbResult,
                                                            int flags);

[DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty", CharSet = CharSet.Unicode)]
internal static extern ErrorCode BCryptGetHashProperty(SafeBCryptHashHandle hObject,
                                                       string pszProperty,
                                                       [MarshalAs(UnmanagedType.LPArray), In, Out] byte[] pbOutput,
                                                       int cbOutput,
                                                       [In, Out] ref int pcbResult,
                                                       int flags);

Microsoft uses function pointers / delegates to point to the correct native function. My question is, why didn't Microsoft just implemented the GetInt32Property method with the following signature:

internal static int GetInt32Property(SafeHandle algorithm, string property)

with the following native method:

[DllImport("bcrypt.dll", CharSet = CharSet.Unicode)]
internal static extern ErrorCode BCryptGetProperty(SafeHandle hObject,
                                                   string pszProperty,
                                                   [MarshalAs(UnmanagedType.LPArray), In, Out] byte[] pbOutput,
                                                   int cbOutput,
                                                   [In, Out] ref int pcbResult,
                                                   int flags);

Are there any downsides to this? (assuming that the SafeHandle passed to GetInt32Property is always either a SafeBCryptAlgorithmHandle or SafeBCryptHashHandle).

I'm just wondering about why Microsoft implemented this so relatively complicated.

Does it have to with:

  • Security-Transparent Code?
  • Type safety? (So that you never use any other than those two types)
  • Is it allowed to use SafeHandle explicitly?

According to the documentation the class must be inherited, and it is, however does a P/Invoked function handle it properly when given an abstract class of SafeHandle? Does it increment and decrement the reference counts appropriately?


Solution

  • It's hard to tell why Microsoft chose to implement something in one way or another, but I can answer your points.

    • The code isn't complex. The usage is clear (something like GetInt32Property(algorithm, str).
    • It doesn't force you to send one of the types you mentioned, you can still call it with a different class, as long as it implements SafeHandle.
    • The native methods that are used are actually the same. This is kind of odd, but I'm not an expert on this specific library so it may be for a good reason.
    • There's a hidden benefit for generic methods like this one. The typeof(T) == typeof(SafeBCryptHashHandle) type checks are not done in runtime, but in JIT time. This means that the method should perform slightly faster then a regular runtime check like algorith is SafeBCrypthHashHandle.

    The SafeHandle class is an abstract class. This means that you cannot create an instance of it, but you can inherit it. The native function only get marshaled data, they don't get real references to objects. Don't worry about reference counting.