2

Code Snippet:

int secret_foo(void)
{
  int key = get_secret();
  /* use the key to do highly privileged  stuff */
  ....

  /* Need to clear the value of key on the stack before exit */
  key = 0;      
  /* Any half decent compiler would probably optimize out the statement above */
  /* How can I convince it not to do that? */

  return result;
}

I need to clear the value of a variable key from the stack before returning (as shown in the code).

In case you are curious, this was an actual customer requirement (embedded domain).

aiao
  • 4,621
  • 3
  • 25
  • 47

4 Answers4

8

You can use volatile (emphasis mine):

Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine (that is, all writes are completed at some time before the next sequence point). This means that within a single thread of execution, a volatile access cannot be optimized out or reordered relative to another visible side effect that is separated by a sequence point from the volatile access.

 volatile int key = get_secret();
AlexD
  • 32,156
  • 3
  • 71
  • 65
  • 1
    It is my understanding that volatile only deals with memory storage and not the usage. The changed value of key is not used and so the compiler will removed it altogether, whether volatile or not – aiao May 26 '16 at 21:43
  • 2
    @aiao: Access to `volatile` cannot be optimized out. – AlexD May 26 '16 at 21:46
  • 1
    Not my DV. But don't you mean `register int key = get_secret();`? – Weather Vane May 26 '16 at 21:48
  • 1
    @aiao: Not true, because one possible meaning of `volatile` is "this is actually a memory mapped device-driver" By writing a particular value to to a particular part of memory, you may actually be telling some controller to turn something on or off. (Long ago, I worked with a set of Lights and Switches that were controlled by writing particular values to particular locations in memory) – abelenky May 26 '16 at 21:49
  • 1
    @WeatherVane No, I meant `volatile`. (`register` can be ignored anyway, and even if not, the value may stay there, which is not nice either.) – AlexD May 26 '16 at 21:50
  • @abelenky Please checkout this answer. http://stackoverflow.com/questions/4933314/unused-volatile-variable – aiao May 26 '16 at 21:58
  • 1
    @aiao As far as I understand they are talking about _unused_ variables. – AlexD May 26 '16 at 22:01
  • same difference! The variable is being optimized out since its value is not used, in the linked question. The statemet would be optimized out since its result will not be used. Some compiler will issue, "variable set but not used" warning, which makes me believe they are going to do some sort of optimization – aiao May 26 '16 at 22:03
  • @aiao If you write to a variable, the variable it is _used_, even if you do not access the variable later. If the variable is `volatile`, reading/writing cannot be optimized away. – AlexD May 26 '16 at 22:10
  • 2
    @aiao the C Standard says that access of volatile variables must not be optimized out – M.M May 26 '16 at 22:11
  • @M.M https://www.securecoding.cert.org/confluence/display/c/MSC06-C.+Beware+of+compiler+optimizations "In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).". I was also able find this back in ANSI also: http://port70.net/~nsz/c/c89/c89-draft.html – aiao May 26 '16 at 22:55
  • 2
    @aiao So they distinguish between **abstract machine** and **actual implementation**. And then: "_Accesses to volatile objects are evaluated **strictly according to the rules of the abstract machine**_" – AlexD May 26 '16 at 22:59
  • 1
    @aiao the parenthesized phrase clarifies that accessing a volatile object is a needed side-effect and so it must be evaluated – M.M May 27 '16 at 03:24
  • `volatile` only applies to compiler optimizations. However neighbor CPU would not immediately see the new value, so timing attacks are possible between CPU caches. – fukanchik May 27 '16 at 22:30
3

volatile might be overkill sometimes as it would also affect all the other uses of a variable.

Use memset_s (since C11): http://en.cppreference.com/w/c/string/byte/memset

memset may be optimized away (under the as-if rules) if the object modified by this function is not accessed again for the rest of its lifetime. For that reason, this function cannot be used to scrub memory (e.g. to fill an array that stored a password with zeroes). This optimization is prohibited for memset_s: it is guaranteed to perform the memory write.

int secret_foo(void)
{
  int key = get_secret();
  /* use the key to do highly privileged  stuff */
  ....

  memset_s(&key, sizeof(int), 0, sizeof(int));
  return result;
}

You can find other solutions for various platforms/C standards here: https://www.securecoding.cert.org/confluence/display/c/MSC06-C.+Beware+of+compiler+optimizations

Addendum: have a look at this article Zeroing buffer is insufficient which points out other problems (besides zeroing the actual buffer):

With a bit of care and a cooperative compiler, we can zero a buffer — but that's not what we need. What we need to do is zero every location where sensitive data might be stored. Remember, the whole reason we had sensitive information in memory in the first place was so that we could use it; and that usage almost certainly resulted in sensitive data being copied onto the stack and into registers.

Your key value might have been copied into another location (like a register or temporary stack/memory location) by the compiler and you don't have any control to clear that location.

fukanchik
  • 2,811
  • 24
  • 29
  • This looks nice. I assume that since this was added in C11, there is no ANSI-C (C89/C90) equivalent. Could you please confirm that? – aiao May 26 '16 at 22:07
  • @aiao I can't confirm that. Please see the securecoding link which i've added into the answer – fukanchik May 26 '16 at 22:11
  • @aiao to be precise, C11 is ANSI C, that's the only C language standard carried by ANSI today (just nitpicking, since you did specify that you meant "C89/C90") – Cubbi May 29 '16 at 00:30
1

If you go with dynamic allocation you can control wiping that memory and not be bound by what the system does with the stack.

int secret_foo(void)
{
  int *key = malloc(sizeof(int));
  *key = get_secret();
  memset(key, 0, sizeof(int));
  // other magical things...
  return result;
}
Fire Crow
  • 7,499
  • 4
  • 36
  • 35
  • 1
    I think you may still end up with the problem that the compiler sees that the final write to memory is never read from, and therefore could optimize it out. Moving from Stack to Heap does not solve the problem. – abelenky May 26 '16 at 21:51
  • ah, what about memset , will the compiler optimize a memset out as well – Fire Crow May 26 '16 at 21:57
  • Still there is no guarantee due to [as-if](http://en.cppreference.com/w/cpp/language/as_if) rule. – AlexD May 26 '16 at 22:03
1

One solution is to disable compiler optimizations for the section of the code that you dont want optimizations:

int secret_foo(void) {
     int key = get_secret();
     #pragma GCC push_options
     #pragma GCC optimize ("O0")

         key = 0;

     #pragma GCC pop_options
     return result;
}
Fantastic Mr Fox
  • 32,495
  • 27
  • 95
  • 175