[Cryptography] defending against common errors (was: secure erasure)

John Denker jsd at av8n.com
Tue Sep 20 02:21:43 EDT 2016


On 09/11/2016 07:18 PM, Tony Arcieri wrote:

> I pointed out a compiler intrinsic, memset_s(), that solves
> this problem, 

Does it really?

It sounds nice in theory, but:
 -- Recent versions of gcc don't implement it.
 -- Recent versions of clang don't implement it.
 -- C++ inherits it from C, so using C++ doesn't help.

Is there a readily available compiler that actually supports
memset_s()????

> and pointed to a library (libsodium) that provides a cross-OS
> solution to this problem,

Does it really?

It sounds nice in theory, but on typical platforms,
including generic ubuntu linux, sodium_memzero() reduces
to a for-loop that simply assigns zero into each location.
It uses the "volatile" keyword, but (as usual) that provides
no guarantees.  Anybody on this list could have written this
code with near-zero effort.  This is an approach that was
considered and rejected, for reasonable reasons, in the
very first message in the "secure erasure" thread.

The libsodium documentation says it "tries to effectively
zero" the memory.  Trying is not good enough.  I wouldn't
go so far as to endorse the Yoda rule:
  “Do. Or do not. There is no try.”
but if something claims to reduce the attack surface I
would like to see a reasonably /quantitative/ claim.

>  but nobody cared.

I would care quite a bit if somebody could suggest a
solution that actually worked and was actually available.

If you need to erase things, here are some artful dodges
that may be helpful:
 -- compile the erasure routine separately from everything
  else.
 -- as previously mentioned, consider reading /dev/zero
 -- as previously mentioned, consider writing /dev/null
  or /dev/random
 -- consider keeping an _extern_ pointer to the memory
  you just erased.

All of these dodges are ways of defeating the infamous
"as-if rules" i.e. the rules about "observable" behavior.
They make it harder for the compiler to know who's observing
what.  In particular, as far as the compiler is concerned,
the extern pointer does a decent job of mimicking a possible
out-of-bounds reference coming from who-knows-where.

  To make sure that the extern pointer doesn't introduce
  a new type of vulnerability, don't set the pointer until
  after you have zeroized the memory.

It must be emphasized that these dodges do *not* solve the
whole problem.

Erasure may reduce the amount of time that critical data spends
lying around, which is nice, but it does *not* entirely close
the window.

================

Last but not least, it is worth saying again that it would
be a mistake to over-emphasize "erasure" per se.  Instead
we should focus on defending against common errors.

Erasure sometimes mitigates the damage caused by out-of-bounds
references ... but it is almost always easier and better to
get rid of the root cause, namely the out-of-bounds references.

In addition to straightforward bounds checking, there are also
techniques such as electric fence, mmap/munmap, aslr, et cetera
that make it much more likely that an out-of-bounds reference
will be detected before it can do widespread harm.  The notion
that bounds-checking would be unduly expensive is not supported
by the evidence.

A useful criterion is to ask whether such-and-such technique
would have prevented heartbleed, and at what cost.

Again:  we should focus on defending against common errors.
I've heard people complain that zeroization is harder in C++.
I'm not sure that's true, but even it were, bounds checking
is easier, so all-in-all C++ seems like the better choice.
Do not fixate on "erasure" per se!

There is a huge base of C code, but the migration path to C++
is really simple.  There are fancy C-to-Ada translators that
will do 80 or 90 percent of the work for you, whereas using
/bin/cat as a C-to-C++ translator does 99 percent of the work
for you.

Also, there has been some talk of changing the language spec
to deal with integer overflow.  Often the right response to an
overflow is to throw an exception.   Ditto for out-of-bounds
references.  I mention this because C++ has exceptions whereas
C does not.

  There is a distinction between hardware exceptions and C++
  language exceptions, and getting the language to respond
  properly to the hardware is a work in progress ... but
  still it is a lot less of a stretch than getting C to do
  the right thing.

On 09/19/2016 10:27 PM, Florian Weimer described memset_s() as:

>> an option feature in C11 and a bit of a joke because it is required
>> to call a callback in case of constraint violations.

Yeah.  That's related to the general lack of exceptions.



More information about the cryptography mailing list