SecureZeroMemory in a managed DLL?

L

Luigi

// SecureZeroMem.cpp

#include <windows.h>

extern "C" __declspec(dllexport) PVOID __stdcall SecureZeroMem(IN
PVOID ptr, IN SIZE_T cnt)
{
return SecureZeroMemory(ptr, cnt);
}


I used this code to create an unmanaged DLL with a "WIN32 Project".
Now I have created an "Empty CLR project" in Visual C++ and all builds
fine.



// Caller.cs

[DllImport("SecureZeroMem.dll", CharSet = CharSet.Auto, SetLastError =
true)]
private static extern IntPtr SecureZeroMem(IntPtr ptr, uint cnt);


I used this P/Invoke to call WIN32 code from C#.

What is the way I can use the C++/CLI code since I can now add the C++
DLL as a reference to my C# project?
Windows API will be called correctly or it's better not to use C++/CLI
for this purpose?



Using C++/CLI I could add the C++ project to my solution and I could
add all to GAC in an easier way (without having to do a multifile
assembly from the command line).


Thanks,
Luigi.
 
N

Nicholas Paldino [.NET/C# MVP]

Luigi,

If you are using C#, then you will want to access the DLL that you
created through the P/Invoke layer.

Mind you, if you are reading the values from memory into C#, you are
essentially copying the data and you have to be aware of securing that
memory as well.
 
P

Pavel Minaev

// SecureZeroMem.cpp

#include <windows.h>

extern "C" __declspec(dllexport) PVOID __stdcall SecureZeroMem(IN
PVOID ptr, IN SIZE_T cnt)
{
    return SecureZeroMemory(ptr, cnt);

}

I used this code to create an unmanaged DLL with a "WIN32 Project".
Now I have created an "Empty CLR project" in Visual C++ and all builds
fine.

// Caller.cs

[DllImport("SecureZeroMem.dll", CharSet = CharSet.Auto, SetLastError =
true)]
private static extern IntPtr SecureZeroMem(IntPtr ptr, uint cnt);

I used this P/Invoke to call WIN32 code from C#.

What is the way I can use the C++/CLI code since I can now add the C++
DLL as a reference to my C# project?
Windows API will be called correctly or it's better not to use C++/CLI
for this purpose?

Using C++/CLI I could add the C++ project to my solution and I could
add all to GAC in an easier way (without having to do a multifile
assembly from the command line).

If you are going to use SecureZeroMemory to clear a string from the
memory (such as password), then consider using the standard
System.Security.SecureString class in .NET.
 
A

Arne Vajhøj

Luigi said:
// SecureZeroMem.cpp

#include <windows.h>

extern "C" __declspec(dllexport) PVOID __stdcall SecureZeroMem(IN
PVOID ptr, IN SIZE_T cnt)
{
return SecureZeroMemory(ptr, cnt);
}

You need this because SecureZeroMemory is inline code ?
I used this code to create an unmanaged DLL with a "WIN32 Project".
Now I have created an "Empty CLR project" in Visual C++ and all builds
fine.

// Caller.cs

[DllImport("SecureZeroMem.dll", CharSet = CharSet.Auto, SetLastError =
true)]
private static extern IntPtr SecureZeroMem(IntPtr ptr, uint cnt);

I used this P/Invoke to call WIN32 code from C#.

I can not see what you really get from SecureZeroMem in C#. You
can not use it on managed data.

And if you have unmanaged data in some Win32 DLL, then I think you
should call SecureZeroMemory in that.
What is the way I can use the C++/CLI code since I can now add the C++
DLL as a reference to my C# project?
Windows API will be called correctly or it's better not to use C++/CLI
for this purpose?

Using C++/CLI I could add the C++ project to my solution and I could
add all to GAC in an easier way (without having to do a multifile
assembly from the command line).

I guess you could do that.

Arne
 
P

Pavel Minaev

I can not see what you really get from SecureZeroMem in C#. You
can not use it on managed data.

Why, of course it can be used on managed data - it's still a block of
memory, after all. So long as it's pinned first, I don't see a
problem. You can't get a pointer to an object, of course, but you can
get pointer to a field of an object...
 
A

Arne Vajhøj

Pavel said:
Why, of course it can be used on managed data - it's still a block of
memory, after all. So long as it's pinned first, I don't see a
problem. You can't get a pointer to an object, of course, but you can
get pointer to a field of an object...

I was able to google Marshal.UnsafeAddrOfPinnedArrayElement, so
yes it is possible.

I still consider using it with SecureZeroMem* a weird design.

Arne
 
P

Pavel Minaev

I was able to google Marshal.UnsafeAddrOfPinnedArrayElement, so
yes it is possible.

It's even easier than that:

char[] buffer;
...
fixed (char* p = buffer)
{
SecureZeroMemory((IntPtr)p, buffer.Length);
}
 
L

Luigi

I have made a class for Impersonation and I have a constructor that
accepts SecureStrings and another one that accepts strings.
MSDN suggests to use SecureZeroMemory to clean memory and since it is
an inline function I created a C++ DLL to call it from C#.
I then used C++/CLI to have the possibility of adding it to references
and deploying it to the GAC.
Since it is wrong to use SecureZeroMemory on a managed C# string what
should I do with the C# string?
If i convert it to a SecureString I have to use ToCharArray, but the
string and the char array are still unsecured and unsecurely cleaned.

Any help would be appreciated.

Thanks,
Luigi.
 
P

Pavel Minaev

I have made a class for Impersonation and I have a constructor that
accepts SecureStrings and another one that accepts strings.
MSDN suggests to use SecureZeroMemory to clean memory and since it is
an inline function I created a C++ DLL to call it from C#.

Technically, SecureZeroMemory macro simply suppresses compiler
optimization (specifically, dead code removal). If you can avoid that
by other means (e.g. by declaring the buffer as volatile), you don't
need SecureZeroMemory - you can just as well just iterate over it
yourself and zero all bytes.
I then used C++/CLI to have the possibility of adding it to references
and deploying it to the GAC.
Since it is wrong to use SecureZeroMemory on a managed C# string what
should I do with the C# string?

Do not use C# strings for this particular task at all. Use only
SecureString.
If i convert it to a SecureString I have to use ToCharArray

What do you have to use it for? Can you detail what exactly you're
trying to do here, preferrably with code?
 
L

Luigi

What do you have to use it for? Can you detail what exactly you're
trying to do here, preferrably with code?

SecureString let me be safe but I have to create a SecureString using
unsafe code (char*) which I would like to avoid or prompting the user
for a password (and using AppendChar).
If I have an encrypted password in app.config, when I decrypt it I
have a string "at risk".
Now I can convert it to SecureString (using str.ToCharArray and
AppendChar) or pass it unencrypted trying to clean memory.


Till now I used this code:

string str = "UnicodePassword";
passwordPtr = Marshal.StringToHGlobalUni(str);
//...
uint size = (uint)(2 * (str.Length + 1));
SecureZeroMem(passwordPtr, size);
Marshal.FreeHGlobal(passwordPtr);


Many people suggested me to pin the string in memory, so I should use
a code like this (tell me if I'm wrong):

string str = "UnicodePassword";
byte[] arr = System.Text.Encoding.Unicode.GetBytes(str);

// extra 2 for null terminator char on end that "C" expects
Array cArr = Array.CreateInstance(typeof(byte), arr.Length + 2);
Array.Copy(arr, cArr, arr.Length);
Array.Clear(arr, 0, arr.Length);

GCHandle handle = GCHandle.Alloc(cArr, GCHandleType.Pinned);
IntPtr passwordPtr = handle.AddrOfPinnedObject();

LogonUser(userName, domainName, passwordPtr, logonType,
LOGON32_PROVIDER_DEFAULT, out currToken);
SecureZeroMem(passwordPtr, cArr.Length);

handle.Free();


The problem is that str and arr are "at risk" and also cArr until
SecureZeroMem is called: wouldn't be better to convert to SecureString
and call Array.Clear on the temporary char array?


Thank for your help!
Luigi.
 
P

Pavel Minaev

SecureString let me be safe but I have to create a SecureString using
unsafe code (char*) which I would like to avoid

There's no point trying to avoid unsafe code, if the only alternative
is using P/Invoke - the end result is the same, since the latter is
also "unsafe".

You can also use SecureString.AppendChar
or prompting the user for a password (and using AppendChar).
If I have an encrypted password in app.config, when I decrypt it I
have a string "at risk".
Now I can convert it to SecureString (using str.ToCharArray and
AppendChar) or pass it unencrypted trying to clean memory.

It doesn't really matter, since as soon as you've got that first
System.String instance, you've already got it in memory, and will have
to clean it up, too...
Till now I used this code:

string str = "UnicodePassword";
passwordPtr = Marshal.StringToHGlobalUni(str);
//...
uint size = (uint)(2 * (str.Length + 1));
SecureZeroMem(passwordPtr, size);
Marshal.FreeHGlobal(passwordPtr);

This copies the string data, and then zeroes out the copy. The
original string remains untouched.
Many people suggested me to pin the string in memory, so I should use
a code like this (tell me if I'm wrong):

string str = "UnicodePassword";
byte[] arr = System.Text.Encoding.Unicode.GetBytes(str);

// extra 2 for null terminator char on end that "C" expects
Array cArr = Array.CreateInstance(typeof(byte), arr.Length + 2);
Array.Copy(arr, cArr, arr.Length);
Array.Clear(arr, 0, arr.Length);

GCHandle handle = GCHandle.Alloc(cArr, GCHandleType.Pinned);
IntPtr passwordPtr = handle.AddrOfPinnedObject();

LogonUser(userName, domainName, passwordPtr, logonType,
LOGON32_PROVIDER_DEFAULT, out currToken);
SecureZeroMem(passwordPtr, cArr.Length);

handle.Free();

Same here. You zero out the array, but the string from which you have
created it is still there.

I really don't see how you can stay secure here without avoiding
creation of any String instances. Since they're immutable, you can't
really "clear" them (well technically you still can, by taking the
pointer to the first element and zeroing the bytes within, but
strictly speaking this is not guaranteed to work - mutating the string
is U.B.). So long as your data remains in arrays (char[] or byte[],
doesn't matter), you can clear it, though even there I would be
suspicious - keep in mind that .NET GC is compacting, so it can move
those arrays around - and there is no guarantee that the "move" will
erase the bytes from the original place. So it seems that you're stuck
with SecureString and using non-GC'ed memory (either stackalloc'd or
Marshal.Alloc*) here.
 
L

Luigi

So long as your data remains in arrays (char[] or byte[],
doesn't matter), you can clear it
So it seems that you're stuck with SecureString and using
non-GC'ed memory (either stackalloc'd or Marshal.Alloc*) here.

Could you please tell me how to create a SecureString from an
encrypted password in app.config?
Using char* and the unsafe keyword?


Thanks a lot!

Luigi.
 
P

Pavel Minaev

Could you please tell me how to create a SecureString from an
encrypted password in app.config?
Using char* and the unsafe keyword?

If having encrypted password in memory is okay for you, then you read
it into plain string with the usual means, create a blank
SecureString, and then decrypt the encrypted string character by
character, adding the decrypted chars to a SecureString using
AppendChar. Since you did not say how exactly your string is
encrypted, I cannot be more specific here. If you can give more
details on the nature of encryption, I'll come up with the code.

If having password, even encrypted, uncleared is not okay, then you
will essentially have to write your own parser for app.config from
scratch (including parsing XML). Here's why: all existing .NET parsers
use plain strings internally, and can cache them arbitrarily - you
have no way to know, and cannot rely on that behavior anyway. So using
e.g. XmlReader (or anything higher-level) would leave your strings
exposed.

But then again, if encrypted password is okay in app.config, there's
no point in trying to secure it in memory.
 
L

Luigi

decrypt the encrypted string character by character, adding the decrypted chars to a SecureString using
AppendChar.

I will use System.Security.Cryptography.ProtectedData to use DPAPI/
Protected Storage or Triple DES.
I do not know if Triple DES is a good choice, but I would like to be
able to deploy the app.config to many machines without having to
encrypt again passwords each time as with DPAPI and no one has told me
wich is the best practice in this case.

if encrypted password is okay in app.config, there's no point in trying to secure it in memory.
Why it doesn't make any sense?
There is no risk in memory?
When I need to use that password to call LogonUser it would be better
if no one can read it from memory... am I wrong?


Thanks,
Luigi.
 
P

Pavel Minaev

I will use System.Security.Cryptography.ProtectedData to use DPAPI/
Protected Storage or Triple DES.
I do not know if Triple DES is a good choice, but I would like to be
able to deploy the app.config to many machines without having to
encrypt again passwords each time as with DPAPI and no one has told me
wich is the best practice in this case.

Where are you going to read the key to decrypt the password with?

And if your app has read access to that key and the config, then what
stops the attacker able to run your pgroam from decrypting the
password himself without bothering to look for it in memory?
Why it doesn't make any sense?
There is no risk in memory?

There is no more risk in memory than there is on disk. Note that I
specifically referred to the _encrypted_ password, not the decrypted
one. So, to clarify: if you are okay in keeping encrypted password in
memory (in exactly the same form as it is in app.config), and only
securely erasing decrypted password as soon as you've used it, then it
seems it can be done in an easy way.
 
A

Arne Vajhøj

Pavel said:
I was able to google Marshal.UnsafeAddrOfPinnedArrayElement, so
yes it is possible.

It's even easier than that:

char[] buffer;
...
fixed (char* p = buffer)
{
SecureZeroMemory((IntPtr)p, buffer.Length);
}

Very logical, but I was not aware that unsafe pointers
were castable to IntPtr like that.

Not much type safety left if using that with memcpy.

Arne
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top