As I don't have enough reputation points to comment on Eric's answer, I have to make this post.
In my opinion, there is a problem with Eric's code as GCHandle.Alloc(workArray, ...)
is done incorrectly. It should not pin the null
value of workArray
but the actual array which will be created a couple of lines farther down.
Furthermore handle.Free()
can throw an InvalidOperationException
and therefore I suggest putting it after Marshal.ZeroFreeBSTR(...)
to have at least the binary string bstr
is pointing to zeroed out.
The amended code would be this:
public static T Process<T>(this SecureString src, Func<byte[], T> func)
{
IntPtr bstr = IntPtr.Zero;
byte[] workArray = null;
GCHandle? handle = null; // Change no. 1
try
{
/*** PLAINTEXT EXPOSURE BEGINS HERE ***/
bstr = Marshal.SecureStringToBSTR(src);
unsafe
{
byte* bstrBytes = (byte*)bstr;
workArray = new byte[src.Length * 2];
handle = GCHandle.Alloc(workArray, GCHandleType.Pinned); // Change no. 2
for (int i = 0; i < workArray.Length; i++)
workArray[i] = *bstrBytes++;
}
return func(workArray);
}
finally
{
if (workArray != null)
for (int i = 0; i < workArray.Length; i++)
workArray[i] = 0;
if (bstr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(bstr);
handle?.Free(); // Change no. 3 (Edit: no try-catch but after Marshal.ZeroFreeBSTR)
/*** PLAINTEXT EXPOSURE ENDS HERE ***/
}
}
These modifications ensure that the correct byte
Array is pinned in memory (changes no. 1 and 2). Furthermore, they avoid having the unencrypted binary string still loaded in memory in case handle?.Free()
throws an exception (change no. 3).