2

I generally zero out memory the moment it's served its usefulness, but only with heap-allocated chunks. Is it a sin (or overkill) to do the same with arguments and stack values? Thanks in advance.

Edit:

Here's an example of what I mean:

void foo(int arg0, int *arg1) {
    int local;

    /* ... */

    arg0 = 0; /* passed by value, so zero the copied value */
    arg1 = 0; /* 0 instead of NULL (NULL might not be 0) */
    local = 0; /* MIGHT be on the stack, depending on optimizations */

    /* Edit: now those values should be safe for FUTURE use */
}
  • I think it's going to be highly OS and possibly HW dependent. At the very least dependent on what you're storing in memory. FWIW most modern OSes clear out the memory when allocated to a new process. – Steve Sether Mar 19 at 20:16
  • @SteveSether I'm wary of trusting the kernel's allocator, so I'm hoping to keep data lifespan as short as possible. – hiten Mar 19 at 20:30
  • 2
    I don't know that you should be dis-trustful of the allocator. It's a relatively unlikely thing to go wrong. You should possibly be more distrustful of the memory still accessible while the process is still live. Heartbleed, for instance. Or possibly things like the various spectre and similar side channel attacks. Zeroing out memory isn't costless of course. – Steve Sether Mar 19 at 20:55
  • 6
    Do note that with the default optimizer settings, most C compilers will simply ignore your code that zeroes out memory that "shouldn't" affect anything and not actually zero out that memory. – Peteris Mar 19 at 23:41
  • 2
    @hiten I wouldn't want to write code that is only secure with certain compiler flags. There are ways to securely erase stack variables. – Gilad Naaman Apr 15 at 15:47
3

The C standard specifies the behavior of code in terms of an abstract machine. This specification does not know about concepts such as "the stack". It knows variables with "automatic storage duration", meaning variables which are "gone" when the scope they've been defined in is left.

From the perspective of the C standard the code local = 0; has only the effect that subsequent accesses to local will yield the value 0.

Now, even though it's not explicitly called like this in the standard, C has an "as-if rule". This means that a compiler is free to implement the code however it likes as long as the "observable behavior" remains the same as that of the code when "run" by the abstract machine.

local = 0; has no observable behavior (assuming there's no code using local in an observable way after the assignment). Therefore, the compiler is allowed to discard that assignment; regardless of optimization settings.

Thus, "zero-ing" like this is a sin because it is not guaranteed to actually do anything and adds clutter to the code, making it less readable. Note that this also applies to dynamically allocated memory!

To really zero a variable You need to make this assignment "observable". The C standard luckily includes means to do so: via an assignment to an object of volatile qualified type.

So with a function like ...

void zero_out(unsigned char volatile * memory, size_t size)
{
  while(size--)
  {
    *memory++ = 0;
  }
}

... You can be sure that after calling zero_out(&local, sizeof(local)) the memory region (if any) where local is/was kept is zero. This disables any smart optimizations by the compiler, though, so for larger memory regions it might be slow!

Even better than my above function is to use a function provided by either the standard or the compiler to achieve the same (because it might be optimized):

  • C11 has memset_s which won't be optimized away. Problematic because memset_s is only optional, and currently not available on e.g. GCC and Clang.
  • MSVC has SecureZeroMemory

There are also other (potentially better) ways to implement my zero_out from above:

  • A volatile function pointer:

    void * (*volatile memset_here_to_stay)(void *, int, size_t) = memset;
    void zero_out(void * p, size s)
    {
      memset_here_to_stay(p, 0, s);
    }
    
  • An "external" function, implemented in assembly (or generated by another translation unit). Attention! Link time optimization might make this ineffective ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.