[Cryptography] Other obvious issues being ignored?

John Denker jsd at av8n.com
Wed Oct 21 13:56:10 EDT 2015


On 10/20/2015 08:40 PM, Jerry Leichter wrote:

> The vast majority of C code is *not* security code, and is helped by 
> such optimizations.  That's why compilers implement them.

Agreed.

> C may have had a simple, direct mapping to hardware back in the days
>  of the PDP-11, but today's machines are very different, and people 
> use C not because they want to do low-level optimization of every 
> aspect of the machine's operation, but because it's light weight and
>  can get really good performance for appropriate code.

Agreed.

> So ... I'd turn this around.  The issue isn't with C, or Java, or
> any other general purpose language.

I disagree.

It's not the only issue, but it *is* an issue when the language, 
by definition, is not secure and not securable.  One of my favorite
sayings is,
  There is no benefit in getting the wrong answer quickly.

In other words, "optimization" for speed at the expense of security
is not really an optimization, and a language that permits this is
a bad language.  If the machine is not secure, you cannot assume
that the results of /any/ calculation are correct.

> You make this sound like a criticism of C.

It's a criticism of C, and also of modern hardware.  A processor
nowadays is essentially a compiler unto itself;  it takes the
machine-language opcodes as rather loose hint as to you want
to do, and compiles that into a sequence of operations that
actually get performed.  Lots of pipelining, branch prediction,
conditional execution, et cetera.

As for the C language, let's consider a specific example, namely 
logical shift, according to section 5.8 of the language specification:
  http://sites.cs.queensu.ca/gradresources/stuff/cpp98.pdf#page=111

]] The behavior is undefined if the right operand is negative, or 
]] greater than or equal to the length in bits of the promoted left
]] operand

The problem is that "undefined" is waaaaay too vague.  According
to this specification, 1<<-1 could print an ascii-art portrait of
Alfred E. Neuman on /dev/console ... and then publish all of your
private keys on facebook.

At the very least, this could be improved by saying that the 
result will be set to an undefined value.  This limits the scope
of the damage.

It would be even better to specify that if the right argument is 
out of range, the result will be set to zero.  This gets rid of
the idea of "undefined".  The behavior is the same across all 
hardware platforms and across all versions of the compiler.

It would be much, much better to specify that if the right argument
is out of range, the result will be set to zero and an exception
will be thrown.

Now it could be argued that range-checking the arguments introduces
run-time inefficiency.  Two responses:
 -- This could be handled in hardware at essentially zero cost.
  The original C compiler reflected the hardware of its day, but
  the tables have long since turned:  Hardware is designed to do
  what the language needs.
 -- A decent optimizing compiler should be able to optimize
  away the checks in speed-critical situations.  Non-critical
  situations are not worth worrying about.

> General-purpose languages will always lean toward satisfying the vast
> majority of programming needs, which will likely provide a semantics
> that is not consistent with the needs of security-related code.

I disagree.

Again I say, there is no advantage in getting the wrong answer quickly.
If the calculation can't be done securely, there is no point in doing
the calculation at all.

> we really don't have a suitable way to express security-sensitive code. 

That ought to be a fixable problem.

It wasn't always a problem.  Back in the days of the PDP-11 and
6502, you could write in assembly language and have high confidence
that the machine would do as it was told.  If you told the machine 
to do an ASL it would actually do it, and it would do it in a 
reproducible amount of time.  There was no pipelining, no branch
prediction, et cetera.  If the registers got saved on interrupt,
you knew where they were saved, so there were no unaccounted-for
copies floating around.  There were of course /some/ side-effects,
e.g. TEMPEST, but the list of things you had to worry about was 
finite.

I'm vehemently not suggesting that we should write in assembly
language.  I am saying that a compiler should make things better,
not worse.

I don't have all the answers, but it seems like the list of what
we need is not very long:

  *) No UB!  No Undefined Behavior!  None!

  *) Constant time execution in some situations.  As mentioned
   above, this affects the hardware, not just the language 
   specification.

  *) Rigorous control of copied data, including zeroization
   when needed.

  *) Minimization of TEMPEST and other side effects.

  *) A few other things.

The computational model that we are asking for in connection with
copy-control is not particularly revolutionary.  On existing 
multi-user systems, the hardware and the kernel already have to
be scrupulous about zeroizing memory before handing it from one
process to another.  Security calls for a rather slight change,
i.e. zeroizing sooner rather than later.  A cold-boot attack is 
equivalent to a shared-memory coprocessor:  Eve is going to hook
up a hostile coprocessor and read all your memory.

To repeat: We are marking crypto-sensitive memory as /shared/ 
memory, shared with a hypothetical enemy process.  For example,
this means that a local variable is never "dead", because the
other process might be looking at it.  A language, a compiler, 
or a piece of hardware that cannot cope with shared memory is 
broken and needs to be fixed.



More information about the cryptography mailing list