New Linux Kernel Flaw Allows Null Pointer Exploits
Trailrunner7 writes "A new flaw in the latest release of the Linux kernel gives attackers the ability to exploit NULL pointer dereferences and bypass the protections of SELinux, AppArmor and the Linux Security Module. Brad Spengler discovered the vulnerability and found a reliable way to exploit it, giving him complete control of the remote machine. This is somewhat similar to the magic that Mark Dowd performed last year to exploit Adobe Flash. Threatpost.com reports: 'The vulnerability is in the 2.6.30 release of the Linux kernel, and in a message to the Daily Dave mailing list Spengler said that he was able to exploit the flaw, which at first glance seemed unexploitable. He said that he was able to defeat the protection against exploiting NULL pointer dereferences on systems running SELinux and those running typical Linux implementations.'"
I always disable those security modules as they always end up to incompatibilities and other erratic behavior in software.
Exactly what do they do anyway?
The explanation given by the SANS Internet Storm Center seems strange. Firstly, the code they provide as an example shows one variable being assigned "sk" and then another variable being checked "tun". I think the "if" statement should be checking "sk" and not "tun". Secondly, the assignment could be a perfectly valid assignment of null; "tun->sk" could indeed be null. The compiler should certainly not optimize out the if statement just because the variable has already been assigned. This would be a major fault in the compiler.
Perhaps they have just oversimplified to the point of writing nonsense...
So, he's dereferencing tun, and then checking if tun was NULL? Looks like the compiler is performing an incorrect optimisation if it's removing the test, but it's still horribly bad style. This ought to be crashing at the sk = tun->sk line, because the structure is smaller than a page, and page 0 is mapped no-access (I assume Linux does this; it's been standard practice in most operating systems for a couple of decades to protect against NULL-pointer dereferencing). Technically, however, the C standard allows tun->sk to be a valid address, so removing the test is a semantically-invalid optimisation. In practice, it's safe for any structure smaller than a page, because the code should crash before reaching the test.
So, we have bad code in Linux and bad code in GCC, combining to make this a true GNU/Linux vulnerability.
I am TheRaven on Soylent News
Somebody mod this AC up.
I would love to however, it appears that only people running MS has mod points these days. I spoke unkindly of MS and my karma turned to "bad" AND I haven't seen mod points in three months. Only the shills are being able to use slashdot effectively it seems. That's just my opinion, however I could be wrong.
I say things which affects my Karma negatively. (and I don't care) For instance; All religion is false.
Sure. My last entire project has been specifically about that. Been working on a piece of software to go onto an embedded device with deterministic behaviour, with the hardware specs being 32kiBiByte RAM, no cache, 8MHz processor.
Most people I am forced to work with who have a comp sci degree are unable to work under such conditions. On the other hand, EE's and comp.eng graduates tend to be very nice to work with on such projects.
Isn't someone running a static checker on the Linux kernel? There are commercial tools which will find code that can dereference NULL. However, there aren't free tools that will do this.
The compiler is allowed to assume that no other code(Including no other thread, running the same code) change the value of a variable behind its back(As long as the variable it not volatile(Volatile got it's own can of worms)), so the optimization is safe.
so in the code
int *data=myFunc(); // The compiler is allowed to optimize this call out.
val=*data;
printf("%d\n",val);
val=*data;
printf("%d\n",val);
And the compiler is allowed to turn this into nothing:
int *val=myFunc(); // Returns a valid pointer to an int. // The compiler may remove anything in here.
*val=10;
if(*val==11) {
*val=42;
}
If multi-threaded code were allowed to do what you describe, then it would be impossible to do most of the optimizations that good c compilers do.
Funny enough a few months back I made a very similar error if not the exact same error while coding on the bootloader for Darwin/x86. Except in my case it wasn't exactly a true error because in the bootloader I know that a page zero dereference isn't going to fault the machine but will instead just read something out of the IVT.
So as I recall it seemed perfectly reasonable to go ahead and initialize a local variable with the contents of something in the zero page and then check for null and end the function. But GCC had other ideas. It assumed that because I had dereferenced the pointer a few lines above that the pointer must not be NULL so it just stripped my NULL check out completely. Had it warned about this like "warning: pointer is dereferenced before checking for NULL, removing NULL check" then that would have been great. But there was no warning so I wound up sitting in GDB (via VMware debug stub) stepping through the code then looking at the disassembly until I realized that.. oops.. the compiler assumed that this code would never be reached because in user-land it would have segfaulted 4 lines ago if the pointer was indeed NULL.
Obviously the fix is simple. Declare the variable but don't initialize it at that time. Do the null check and return if null. Then initialize the variable. If using C99 or C++ then you can actually defer the local variable declaration until after you've done the NULL check which IMO is preferable. It may be that the guy wrote it as C99 (where you can do this) then went oops, the compiler won't accept that in older C and simply moved the declaration and initialization statement up to the top of the function instead of splitting the declaration from the initialization. My recollection of how I managed to introduce this bug myself is shady but as I recall it was something like that.
int *data=myFunc();
val=*data;
printf("%d\n",val);
val=*data;
printf("%d\n",val);
Actually, the compiler isn't allowed to optimize that second assignment to val out unless it can see the source for printf and can prove that there are not other aliases to the memory that data points to that might be changing it.
Even if you assume the default printf(), myFunc might be returning a pointer to one of the buffers used for IO.
This is one of the reasons that C99 introduced the __restrict keyword; to allow the compiler the make the sort of optimization you are suggesting here.
Tim.
God said, "div D = rho, div B = 0, curl E = -@B/@t, curl H = J + @D/@t," and there was light.
The code by itself is not fine. The underlying bug is that the kernel is allowing memory at virtual address 0 to be valid. The compiler was designed for an environment where there can never be a valid object at 0, and has chosen the bit pattern 000...000 to be the null pointer. If you want to use C in an environment where 0 can be a valid address, then you need to use a C compiler that uses some bit pattern other then 000...000 for the null pointer.
And yes, compilers can do that. According to the C standard, assigning the constant 0 to a pointer type does NOT mean assigning the numerical representation of 0. It means assigning a machine dependent value that compares unequal when compared to any pointer to valid memory.
Furthermore, the Linux version of gcc is designed for an environment where accessing invalid memory causes a fault. Thus, the compiler is doing nothing wrong optimizing out that test for null, since in the environment for which it was designed, null pointers follow the C standard and do not point to a legitimate object, and accesses of illegitimate objects fault, and in such an environment that test can never be executed.
The kernel developers either need to make sure the kernel environment conforms to gcc's assumptions about its environment, or they should use a special version of gcc designed for the kernel environment.
The rest of us consider it a fundamental law of the universe.
Clearly, your universe is too small! Technically, the code dereferenced NULL+offset where offset (and so NULL+offset) is non zero (which I presume you are hard wired to consider to be the NULL value).
In an environment where a segv (or equivalent) won't be triggered, the code's not wrong until it makes use of an invalid dereference. The if would have prevented it. I don't think that makes it GOOD since in most environments it will fail.
In some languages or with some C optimizers, the assignment would never be evaluated at all until after the if.
Let's face it, the bug is a corner case in the complex interaction between the compiler and the kernel's vision of the environment.