How do you manually marshal a BSTR from .NET to COM?
I am trying to pass a secret stored in a SecureString
from my .NET application to a method on a COM object. I want to avoid converting the SecureString
to a String
because that's not how you're supposed to use SecureString
. The secret would then be in clear text in managed memory and hang around there for a long while. Instead I'm trying to do it the Right Way™: converting it to a BSTR in unmanaged memory, passing it to the COM method, and then clearing the BSTR with Marshal.ZeroFreeBSTR
, so that the password is exposed for only a limited time.
The .NET interface that TblImp.exe generates for this COM object expects the secret to be passed as a String
, which it then marshals to a BSTR. That's not what I want since it means I have to put my secret in a String
, so following this Lean Method for Invoking COM, I've created my own interface so that I can customize it and get rid of the String
. Here's what I'm starting with:
[ComImport, Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITheComObjectDispatcher
{ // +--- the naughty string
[return: MarshalAs(UnmanagedType.BStr)] // |
[DispId(1)] // V
string TheMethod([In, MarshalAs(UnmanagedType.BStr)] string secret);
}
[ComImport, Guid("YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY")]
public class TheComObject
{}
This works - but I have to convert the SecureString
to a String
to use it, like so:
private string UseTheMethod(SecureString secret)
{
var comObject = new TheComObject();
var comObjectDispatcher = (ITheComObjectDispatcher)comObject;
var secretAsString = NaughtilyConvertSecureStringToString(secret);
return comObjectDispatcher.TheMethod(secretAsString);
}
private static string NaughtilyConvertSecureStringToString(SecureString value)
{
var valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
try
{
return Marshal.PtrToStringUni(valuePtr); // now the secret is in managed memory :(
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
Instead, I want to use a method like Marshal.SecureStringToBSTR
and skip the conversion to String
. I've tried this...
[return: MarshalAs(UnmanagedType.BStr)]
[DispId(1)]
string TheMethod([In, MarshalAs(UnmanagedType.BStr)] IntPtr secret);
... and this...
[return: MarshalAs(UnmanagedType.BStr)]
[DispId(1)]
string TheMethod([In] IntPtr secret);
... with this calling code...
private string UseTheMethod(SecureString secret)
{
var comObject = new TheComObject();
var comObjectDispatcher = (ITheComObjectDispatcher)comObject;
var secretPtr = Marshal.SecureStringToBSTR(secret);
try
{
return comObjectDispatcher.TheMethod(secretPtr);
}
finally
{
Marshal.ZeroFreeBSTR(secretPtr);
}
}
... but it doesn't work: the wrong value is getting passed to the COM method. It doesn't fail with an error - it just gives a different result.
Questions:
- Can I pass a BSTR directly as a IntPtr like this? What am I doing wrong?
- Is there a way to debug the marshaling process to see what value is being passed?