User12341921
User12341921

Reputation: 49

Clear stack variable memory

I have 8 uints which represent a security key like this:

uint firstParam = ...;
uint secondParam = ...;
uint thirdParam = ...;
uint etcParam = ...;
uint etcParam = ...;

They are allocated as local variables, inside of an UNSAFE method. Those keys are very sensitive. I was wondering do those locals on the stack get deleted when the method is over? Does the UNSAFE method have an affect on this? MSDN says that Unsafe code is automatically pinned in memory.

If they are not removed from memory, will assigning them all to 0 help at the end of the method, even though analyzers will say this has no effect?

So I tested zeroing out the variables. However, in x64 Release mode the zeroing is removed from the final product (checked using ILSpy) Is there any way to stop this?

Here is the sample code (in x64 Release)

private static void Main(string[] args)
{
    int num = new Random().Next(10, 100);
    Console.WriteLine(num);
    MethodThatDoesSomething(num);
    num = 0;                         // This line is removed!
    Console.ReadLine();
}

private static void MethodThatDoesSomething(int num)
{
    Console.WriteLine(num);
}

The num = 0 statement is removed in x64 release.

I cannot use SecureString because I'm P/Invoking into a native method which takes the UInts as a paramter.

I'm P/Invoking into the unmanaged method AllocateAndInitializeSid, which takes 8 uints as parameters. What could I do in this scenerio?

I have tried adding

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]

to the sample code (above Main method), however, the num = 0 is STILL removed!

Upvotes: 0

Views: 505

Answers (2)

pid
pid

Reputation: 11607

EDIT: after some reasoning I've come to correct this answer.

DO NOT use SecureString, as @Servy and @Alejandro point out in the comments, it is not considered really secure anymore and will give a misguided sense of security, probably leading to futhering unconsidered exposures.

I have striked the passages I'm not comfortable with anymore and, in their place, would recommend as follows.

To assign firstParam use:

firstParam = value ^ OBFUSCATION_MASK;

To read firstParam use (again):

firstParam ^ OBFUSCATION_MASK;

The ^ (bitwise XOR) operator is the inverse of itself, so applying it twice returns the original value. By reducing the time the value exists without obfuscation (for the CPU time is actually the number of machine code cycles), its exposure is also reduced. When the value is stored for long-term (say, 2-3 microseconds) it should always be obfuscated. For example:

private static uint firstParam; // use static so that the compiler cannot remove apparently "useless" assignments

public void f()
{
    // somehow acquire the value (network? encrypted file? user input?)
    firstParam = externalSourceFunctionNotInMyCode() ^ OBFUSCATION_MASK; // obfuscate immediately
}

Then, several microseconds later:

public void g()
{
    // use the value
    externalUsageFunctionNotInMyCode(firstParam ^ OBFUSCATION_MASK);
}

The two external[Source|Usage]FunctionNotInMyCode() are entry and exit points of the value. The important thing is that as long as the value is stored in my code it is never in the plain, it's always obfuscated. What happens before and after my code is not under our control and we must live with it. At some point values must enter and/or exit. Otherwise what program would that be?

One last note is about the OBFUSCATION_MASK. I would randomize it for every start of the application, but ensure that the entropy is high enough, that means that the count of 0 and 1 is maybe not fifty/fifty, but near it. I think RNGCryptoServiceProvider will suffice. If not, it's always possible to count the bits or compute the entropy:

private static readonly uint OBFUSCATION_MASK = cryptographicallyStrongRandomizer();

At that point it's relatively difficult to identify the sensitive values in the binary soup and maybe even irrelevant if the data was paged out to disk.

As always, security must be balanced with cost and efficiency (in this case, also readability and maintainability).


ORIGINAL ANSWER:

Even with pinned unmanaged memory you cannot be sure if the physical memory is paged out to the disk by the OS.

In fact, in nations where Internet Bars are very common, clients may use your program on a publicly accessible machine. An attacker may try and do as follows:

  1. compromise a machine by running a process that occasionally allocates all the RAM available;
  2. wait for other clients to use that machine and run a program with sensitive data (such as username and password);
  3. once the rogue program exhausts all RAM, the OS will page out the virtual memory pages to disk;
  4. after several hours of usage by other clients the attacker comes back to the machine to copy unused sectors and slack space to an external device;
  5. his hope is that pagefile.sys changed sectors several times (this occurs through sector rotation and such, which may not be avoided by the OS and can depend on hardware/firmware/drivers);
  6. he brings the external device to his dungeon and slowly but patiently analyze the gathered data, which is mainly binary gibberish, but may have slews of ASCII characters.

By analyzing the data with all the time in the world and no pressure at all, he may find those sectors to which pagefile.sys has been written several "writes" before. There, the content of the RAM and thus heap/stack of programs can be inspected.

If a program stored sensitive data in a string, this procedure would expose it.

Now, you're using uint not string, but the same principles still apply. To be sure to not expose any sensitive data, even if paged out to disk, you can use secure versions of types, such as SecureString.

The usage of uint somewhat protects you from ASCII scanning, but to be really sure you should never store sensitive data in unsafe variables, which means you should somehow convert the uint into a string representation and store it exclusively in a SecureString.

Hope that helps someone implementing secure apps.

Upvotes: 2

Alejandro
Alejandro

Reputation: 7811

In .NET, you can never be sure that variables are actually cleared from memory.

Since the CLR manages the memory, it's free to move them around, liberally leaving old copies behind, including if you purposely overwrite them with zeroes o other random values. A memory analyzer or a debugger may still be able to get them if it has enough privileges.

So what can you do about it?

Just terminating the method leaves the data behind in the stack, and they'll be eventually overwritten by something else, without any certainity of when (or if) it'll happen.

Manually overwriting it will help, provided the compiler doesn't optimize out the "useless" assignment (see this thread for details). This will be more likely to success if the variables are short-lived (before the GC had the chance to move them around), but you still have NO guarrantes that there won't be other copies in other places.

The next best thing you can do is to terminate the whole process immediately, preferably after overwritting them too. This way the memory returns to the OS, and it'll clear it before giving it away to another process. You're still at the mercy of kernel-mode analyzers, though, but now you've raised the bar significantly.

Upvotes: 1

Related Questions