In my entry about my understanding of modern C undefined behavior I alluded to how modern C created a security vulnerability in the Linux kernel because of variable initialization. Today I feel like showing you roughly the code involved and explaining this, rather than alluding to it indirectly.
We'll start with perfectly functional code:
int func(struct foo *arg)
{
struct bar *p;
if (arg == NULL)
return -EINVAL;
p = arg->f_p;
[.....]
This works right. Then someone comes along and fiddles with the code a bit:
int func(struct foo *arg)
{
struct bar *p = arg->f_p;
if (arg == NULL)
return -EINVAL;
[....]
In the Linux kernel dereferencing a NULL pointer does not immediately segfault so this code is (sort of) okay as written; if
arg
is
NULL
we will read some bit of low memory and then return. Then the optimizing compiler shows up.
The compiler knows that no conforming C program can ever dereference a null pointer; to do so is undefined behavior. Since the code dereferences
arg
without checking for
NULL
, the compiler is entitled to assume that
arg
is never
NULL
. This makes the
if
's expression into a constant value (it must always be false) and the whole
if
can then be eliminated as dead code. Now this function will continue on even if
arg
is
NULL
and in the process use whatever it fished out of memory as
p
( this enables the actual exploit ).
This is of course bad code (since you should never dereference potentially
NULL
pointers). But it would not naturally be fatal; it took a C compiler optimizing code to make it that.
(The exact code is used as an example here if you want to read it. It is somewhat more intricate than my version but not much so.)