[Cryptography] [Crypto-practicum] Secure erasure in C.

Ron Garret ron at flownet.com
Wed Sep 7 19:23:56 EDT 2016


On Sep 7, 2016, at 1:04 PM, Ray Dillinger <bear at sonic.net> wrote:

> 
> 
> When possible, I do operations that can be done in finite
> bounded memory using a static volatile buffer to hold
> sensitive data.  Design the program right so you absolutely
> know how much memory the operations need, and the static
> volatile buffer solves many problems.
> 
> Writes and reads aren't optimized away regardless of whether
> you're going to read that space again before exiting.
> Transient copies and virtual-memory caches don't get made
> because if the compiler can't assume the copies remain in
> sync to the buffer, then making copies is useless.  Finally
> out-of-memory errors don't happen at runtime (and can't be
> forced by an attacker to happen at runtime) if you've
> preallocated the secure buffer that you keep using.
> 
> That is definitely the preferred solution. As far as I know,
> the desired behavior is absolutely required by all versions of
> the C standard and the availability of static volatile buffers
> is the main reason why I write security code in C despite all
> of C's sharp pointy bits, vicious free-swinging hooks and
> bloody blades.
> 
> But every so often I need to securely delete a buffer that I
> get from somewhere else, or flatten a stack image before exiting
> a routine, and that turns out to be harder.
> 
> Non-volatile variables and buffers have MANY problems that
> mean they should usually be avoided for secure data. The
> compiler making copies in order to parallelize operations or
> relocating data for whatever other reasons, or the OS deciding
> that it needs to make a disk cache for virtual memory on a
> machine whose cache partition is unencrypted, etc.  Those
> problems can only be managed with careful configuration of
> the machine, runtime environment, and OS.
> 
> But aside from that,  it's actually hard to get a non-
> volatile buffer deleted.  The short version of the story is
> that most languages don't have volatile buffers, and the
> one that does has a standard that says unless something
> in the "officially observable" output is required by the
> standard, the compiler doesn't have to do it.  We want to
> delete non-volatile buffers when we're done with them, but
> when we're done with them we don't read them anymore, and
> the compiler often concludes that therefore the writes
> don't have to happen.  We often rely on observed behavior
> instead of the language standard, but in many cases observed
> behavior is just the optimizer not being able to figure out
> that the language standard will allow it to get away with
> skipping something.
> 
> In response to a compiler that made a completely unexpected
> pessimization of my code, I've recently been forced to revisit
> secure erasure of non-volatile buffers.
> 
> For years I'd been defining 'erase' using
> 
> //////
> static void *(*const volatile deleter)(void*,int,size_t)=memset;
> static void erase(void *buf,size_t len){deleter(buf, 0, len);}
> //////
> 
> 
> which, IIRC, I originally copied from the PGP source code
> back when Hal Finney was working on it.
> 
> It's a reasonably clever bit and has been standard in crypto
> code for a long time. Declaring the pointer to the routine
> as volatile  means the program is absolutely obligated to
> access that pointer when the routine is called.  It also
> means that the compiler can't make static assumptions about
> what the routine does because the pointer might change
> outside of program control.  And we've been assuming that
> because the compiler isn't allowed to guess what the routine
> does, it has to call it in order to make sure that if it
> does anything "observable" that thing actually happens.
> 
> And this has been true through all of computing history,
> Up to now.
> 
> Unfortunately while the standard absolutely obligates the
> program to access the function pointer and doesn't allow it
> to assume that the pointer value (and therefore the routine
> it points at) is unchanged, it does not obligate it to
> actually execute the routine.
> 
> And someone recently discovered some obscure combination of
> mind-boggling optimization levels, compilation environment,
> and system settings under which an Intel compiler actually
> doesn't.  Usually this kind of thing is discovered w/r/t
> gcc: Intel is something of a surprise.
> 
> When static analysis reveals that calling the function which
> 'deleter' is initialized with, does not in fact change the
> contents of volatile memory, nor non-volatile memory that
> the program will later access, nor cause other un-skippable
> side effects, the program can cache a copy of the function
> pointer.  Then when the call to 'deleter' is made, it must
> read the volatile pointer - which completes its obligation
> to the standard. But then, instead of just executing the
> procedure, it is allowed to compare the pointer's value to
> the cached value, and if they are identical may conclude
> that it need not actually execute the routine.
> 
> So.  I'm redefining 'erase.'  I think that these steps
> will ensure that secure deletion happens and would like
> feedback if anyone can think of any way for a compiler
> compliant to the language standard to pessimize this
> method.
>   1) Make calls to the RNG to get a key and IV, allocating
>      the space for key and IV out of a static volatile
>      buffer where the normal secure-deletion procedure
>      will work on them.
>   2) Encrypt the buffer in place using that key and IV.
>   3) Calculate a CRC of the encrypted buffer.
>   4) Write the CRC to a volatile variable.
>   5) Clear the key and IV from the volatile buffer.
> 
> Rationale/Assumptions:
>   1) Calls to the RNG cause side effects that can't be
>      elided (changes to RNG state), and produce results
>      which a static compiler cannot predict.
>   2) Encryption in-place in CBC mode using a random key
>      and IV should leave no recoverable information about
>      the original contents of the buffer once the key and
>      IV are secure-deleted. (Note; in-place encryption
>      requires avoiding creation of ciphertext longer
>      than the original buffer.  The IV should not be
>      written to the buffer, and I can encrypt the last
>      block starting at a different offset so it overlaps
>      the previous block placing its end is at the end
>      of the buffer).
>   3) The compiler can't elide the encryption because
>      the encrypted value will be used to calculate a
>      CRC.
>   4) The compiler has to calculate the CRC because it
>      can't otherwise figure out what it has to write
>      to a volatile variable and isn't allowed to guess.
>   5) The volatile variable containing the CRC is not a
>      risk because of random key and IV; it contains no
>      recoverable information about the original contents
>      of the buffer (and anyway it's in the volatile
>      buffer so it can be secure-deleted normally).
>   6) Although some "observable" effects are data-
>      dependent, there are no possible buffer contents
>      that could affect its operation for purposes of
>      an attack.  It is not vulnerable to timing attacks
>      because the time taken for encryption and CRC
>      does not depend on the buffer data.
>   7) Copies of the buffer will not be made because I'll
>      be calling a competently implemented encryption
>      library. I know this is the big assumption but
>      encryption implementation flaws are a much more
>      serious threat which must be fixed for other
>      reasons and fixing them for other reasons will
>      fix them for secure deletion as well. Therefore
>      they are out-of-scope as a problem specific to
>      secure deletion.
> 
> Can anybody think of any way that a standard-compliant
> compiler is allowed to mess this up?

No, but it seems like overkill to me.  AFAICT the problem is more straightforward than that:

> static void *(*const volatile deleter)(void*,int,size_t)=memset;
> static void erase(void *buf,size_t len){deleter(buf, 0, len);}

The problem seems to me to be that the buffer contents are not volatile.  You want:

static void *(*const volatile deleter)(volatile char*, int,size_t) =
  (void *(*const volatile)(volatile char *, int, size_t)) memset;

static void erase(volatile char *buf, size_t len) { deleter(buf, 0, len); }

But why not simply:

void erase(volatile char *buf, size_t len) {
  for (int i=0; i<len; i++) buf[i]=0;
}

Or, if you really want to be paranoid:

int  erase(volatile char *buf, size_t len) {
  for (int i=0; i<len; i++) buf[i]=0;
  for (int i=0; i<len; i++) if (buf[i]) return -1;
  return 0;
}

or if you want to be really REALLY paranoid:

volatile int _dummy = 0;

int  erase(volatile char *buf, size_t len) {
  for (int i=0; i<len; i++) buf[i]=0;
  for (int i=0; i<len; i++) if (buf[i]) return -1;
  _dummy++;  // Or _dummy = get_random_number() or gettimeofday()
  return 0;
}

rg



More information about the cryptography mailing list