
Security flaws caused by compiler optimizations - nfltn
https://www.redhat.com/en/blog/security-flaws-caused-compiler-optimizations
======
comex
> However, the programmer failed to inform the compiler that the call to
> ereport(ERROR,...) does not return. This implies that the division will
> always execute.

I don’t think that’s correct. The compiler is allowed to assume that functions
marked noreturn do not return, but it’s not allowed to assume that functions
_not_ marked noreturn _do_ return. In other words, it’s not undefined behavior
for a function to call abort(), enter an infinite loop, etc. instead of
returning. It would be very strange if it were!

There’s a somewhat related spec clause that lets the compiler assume that
certain types of loops will eventually terminate [1], but that doesn’t apply
here. Therefore I think the mentioned compiler optimization is illegal. The
issue was reported back in 2011; it would be interesting to see whether newer
versions of GCC, or Clang, behave the same way.

[1] [https://stackoverflow.com/questions/16436237/is-
while1-undef...](https://stackoverflow.com/questions/16436237/is-
while1-undefined-behavior-in-c)

~~~
slaymaker1907
An infinite loop with no side effects is undefined behavior.

~~~
comex
I didn't say no side effects. But even an infinite loop with no side effects
is well-defined if the controlling expression is a constant expression; see
the link in my previous post.

------
asveikau
I remember when CVE-2009-1897 was current, because it is an obvious example
where no one would expect the null check to work:

    
    
        struct sock *sk = tun->sk;
        unsigned int mask = 0;
        if (!tun)
    

Obviously "sk" is not used until after the null check, but if we read it line
by line as we would expect a naive compiler with no optimization to act, the
pointer is followed before the null check, and null should produce a crashing
program. It would seem that anyone expecting it to work would _assume the
evaluation of the initial assignment of sk to be lazy at first use_ , which is
a very strange assumption.

Still, I remember in 2009 people writing about that snippet as if it is a
surprising result and the compiler did something wrong.

~~~
tom_
When NULL corresponds to a valid address, you don't want the check stripped
out of this sort of code.

~~~
jfim
Out of curiosity, what are the expected contents of the zero page area? Is
access allowed to it just because it's coming from the kernel instead of a
userland process?

~~~
Gibbon1
On my ARM Cortex processor it's __StackTop

On a read it's a completely valid address. Write generates a bus fault.

Atmel ATMega parts it's the reset vector. A write does nothing.

~~~
Gibbon1
Braino. ATMega parts address 0 is the R0 register.

------
mrich
My take, after 12 years of industry C++ experience, working on code that needs
to be fast: Too much emphasis is placed on gaining another 0.5% performance
improvement, instead of slightly slower code that does what was intended. At
least offer some safe defaults and make the bleeding edge optional.

While we are debugging things like this, people are writing servers in
JavaScript and web services in Python :O No need to optimize so heavily, they
will waste it anyway :)

~~~
adrianN
That 0.5% improvement can help a lot when it's inside the Javascript engine or
the Python interpreter.

~~~
Gibbon1
It's usually a 0.5% improvement on a micro benchmark.

One of my suspicions is that at the low end where I operate the marginal cost
of higher speed is essentially zero. My firmware spends more than 9.99% of the
time sleeping. Micro optimizations of a few percent is meaningless. At the
other end superscalar processors are a moving target for micro optimizations.
And further a lot of tasks look like init -> process data -> clean up. Over
time the process data part has gotten very large. Making the init and clean up
parts of the code a smaller and smaller percentage of the execution time.
Micro optimizations in those parts of the code provide no value. Next is the
constant movement to push the data processing into either specialized CPU
instructions or GPU's.

~~~
adrianN
A single optimization pass might only improve a microbenchmark a bit, but all
passes taken together significantly speed up most programs. In the embedded
software that I have experience with we eventually had to turn on
optimizations because otherwise we would have had to switch to a new hardware
platform to run continuously more demanding workloads.

------
atq2119
The leaking of sensitive data due to DCE'd memset is an interesting one.
Generally, compilers are free to temporarily move data around to a lot of
places, such as on the stack for register spilling. Is there any programming
language at all which allows sensitive data to be annotated in such a way that
the compiler will promise not to leak it to memory indefinitely in some sense?
(E.g. all places that the data may be written to are cleaned up before
reaching some sort of security boundary)

~~~
nokcha
>Is there any programming language at all which allows sensitive data to be
annotated in such a way that the compiler will promise not to leak it to
memory indefinitely in some sense?

Somewhat related: there's a recent paper that develops a language with syntax
for marking data as secret; the compiler then goes even further and avoids
timing side-channel leaks:

Cauligi, Sunjay, et al. "FaCT: A flexible, constant-time programming
language." 2017 IEEE Cybersecurity Development (SecDev). IEEE, 2017.
[http://www.sysnet.ucsd.edu/~bjohanne/assets/papers/2017secde...](http://www.sysnet.ucsd.edu/~bjohanne/assets/papers/2017secdev.pdf)

------
qw3rty01
A better title would be security flaws caused by relying on undefined
behavior.

~~~
ummonk
Given that practically anything under the sun is undefined behavior for C and
C++, that isn’t saying much.

~~~
papermachete
What gives you this imimpression?

~~~
bhk
The C standard includes an appendix that lists ~200 examples of undefined
behavior. This list does not claim to be exhaustive.

Often, what constitutes undefined behavior is non-obvious (and not well
justified). For example, when adding two signed integers results in an
overflow, it is undefined behavior _even if your program never uses the
result_.

Due to C's definition of "undefined" behavior, it means that all of the
guarantees we rely on to ensure security go out the window whenever the
programmer steps on one of these land mines.

~~~
caspper69
Not all UB falls into this category. A lot of UB, such as your signed integer
addition example, is dependent upon the behavior of the underlying hardware.
Certain archs may throw an exception on signed integer overflow, or exhibit
otherwise inconsistent behavior, for example. The standard is the standard, of
course, but not all implementations inherit the UB of the standard.

~~~
Gibbon1
> A lot of UB, such as your signed integer addition example, is dependent upon
> the behavior of the underlying hardware.

That problem is you can no longer depend on that because the compiler writers
have decided they can do anything during AST optimization when there is
undefined behavior.

~~~
qw3rty01
That's because...they can. Because the behavior of the statement is undefined.

~~~
perl4ever
“Those who can make you believe absurdities, can make you commit atrocities.”
-Voltaire

Who knew he was talking about C compilers??

------
bigtrakzapzap
Basically, every kernel needs an explicit_bzero() system call because it's
very difficult to assure data flow properties of de/initialization without
something the compiler cannot optimize away.

~~~
rogueresearch
memset_s() was added to C11 for this.

~~~
jmgao
memset_s was added to C11 in an optional annex, and my understanding is that
there are zero platforms that actually implement it. (Microsoft implemented an
early draft of Annex K that doesn't actually include memset_s.)

~~~
rurban
Most libc's added an insecure version of memset_s, doing only the above
discussed compiler-barrier, but not a memory-barrier, which is needed for
Spectre, broken HW. The default memset should do the compiler-barrier. But
unfortunately you cannot talk with libc maintainers about security. Too much
arrogance. Thanks to this Redhat article for supporting the user-base on this.

You can use my safeclib, which implements the Annex K extensions.

