New Linux Kernel Flaw Allows Null Pointer Exploits 391
Posted
by
Soulskill
from the recipe-for-fun dept.
from the recipe-for-fun dept.
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.'"
DRM is defective by design. (Score:5, Informative)
I think that tag is mostly reserved for DRM related news...
And I have seen news about linux DRM modules also tagged that.
Re:Double standards (Score:4, Informative)
Thats because with Windows, no one would be able to marvel at how un-obvious the flaw is. According to The Register, the kernel actually has gaurds in place against just this type of valnerability, but the complier optimized them out during compiling. IMHO this makes this flaw a very good case study, even with security in place, you cannot really trust the compiler. (actually, this flaw apparently only occurs if security is in place... or if you use PulseAudio (in which case, you deserve it!)).
CFLAGS (Score:4, Informative)
CFLAGS+= -fno-delete-null-pointer-checks
Job done (should work with Gentoo, buggered if I know how to do this in other distros, DYOR), even with -O2/-O3. This is an optimisation/code conflict. The code itself is perfectly valid, so if your CFLAGS are -O -pipe you have nothing to worry about. GCC's info pages show what is enabled at various optimisation levels. -fdelete-null-pointer-checks is enabled at -O2. Of course, this only applies when you compile your own kernel. If vendors are supplying kernels compiled with -O2 without checking what it does to the code then it is obvious who is to blame.
Re:Serious bug in gcc? (Score:5, Informative)
gcc is definitely doing the wrong thing here.
Given the code:
a = foo->bar
if(foo) something()
gcc is doing precisely the wrong thing - optimising out the if on the theory that the app would have crashed if it was null.
What it *should* do is throw a warning (even an error, given the clear intent of the code) pointing out that the variable is dereferensed before it is tested.
This kind of error being missed by gcc is going to affect a *lot* of code - it's really not that uncommon a coding error, and is easy to do.
Re:Serious bug in gcc? (Score:3, Informative)
The description given by SANS is a bit misleading. What I believe is happening is:
Since point 2 is mostly true, the compiler is not completely wrong to assume point 3
As Spengler says, a bigger problem is that loading SELinux (or, it looks like, most other security modules) causes the NULL dereference protection to be disabled.
Re:CFLAGS (Score:4, Informative)
No. That doesn't fix the problem. All it does is stop the broken optimisation (why the *hell* did someone at gcc think such a thing should be default anyway?)
You need an -ferror-on-bogus-null-pointer-checks parameter so that the code can be fixed.
It's an easy error to make. It's the compilers job to warn you.. in this case not only did it fail to throw a warning it also made the problem worse by 'optimising' it.
Actually, it's already been fixed (Score:5, Informative)
Re:Double standards (Score:5, Informative)
This is arguably more of an issue in the compiler than in the kernel,
Not completely... from the SANS Storm Center [sans.org], the code was as follows:
struct sock *sk = tun->sk;
if (!tun) // if tun is NULL return error
return POLLERR;
The error was that the compiler optimized away the if statement, assuming that tun had already been initialized. The check should have been placed before the sock variable referenced it. Not entirely obvious maybe, but then again, it should have been checked before the assignment.
Re:Wait, what? (Score:3, Informative)
No. Technically, if tun is null, dereferencing it in the expression tun->sk invokes undefined behaviour -- not implementation-defined behaviour
I've seen a lot of people claiming that, however (as someone who hacks on a C compiler) there are a few things I take issue with in your assertion.
First, NULL is a preprocessor construct, not a language construct; by the time it gets to the compiler the preprocessor has replaced it with a magic constant[1]. The standard requires that it be defined as some value that may not be dereferenced, which is typically 0 (but doesn't have to be, and isn't on some mainframes). Dereferencing NULL is invalid, however that is not what is happening here.
The value &(tun->sk) is the address of tun, plus a fixed offset. The expression &(((struct foo*)0)->bar) is valid C and will give the value of the offset of the sk field in the foo struct. A typical definition of NULL is (void*)0, and &(((struct foo*)(void*)0)->bar) will also give the value of the offset of the bar field.
In this case, it is tun->sk, not &(tun->sk) which is being loaded, however the pointer arithmetic which generates the address happens first. If tun is NULL then this is NULL + {the offset of sk}. While dereferencing NULL is explicitly not permitted, pointer arithmetic on NULL is permitted, and dereferencing any non-NULL memory address is permitted.
This is obvious from an implementation perspective. If pointer arithmetic on the NULL address were not permitted then every single pointer arithmetic expression would require a check at every intermediate stage to make sure that it did not pass through NULL. For example (a - a + a + 1) would be an invalid pointer arithmetic expression on platforms where NULL is address 0 if address computations were not permitted on NULL.
[1] GCC and C++1x both declare a new language keyword for an invalid pointer, but this is not part of standard C.
Re:Serious bug in gcc? (Score:3, Informative)
This sound more like a gcc/embeded os bug. There is no requirement in c/c++ that the null pointer is (int)0. That is: It don't have to be all 0 bits.
It just need to be distinct from any valid pointer, so if you run on a platform where you use memory address 0(Valid, but still wierd), you need
to config gcc(If possible, I don't know if gcc supports this) to use an other bit pattern as null pointer(say 0xeffff), and then you need to configure your embeded os, to never
return a memory address/buffer that contain the address you have used as null pointer.
Re:Just don't use that version (Score:3, Informative)
Sound familiar?
Then they have to upload it to their package management servers and put the fix out there for you to use.
This might not sound like a lot of work, but who needs a new kernel when they are busy with a whole truck full of other packages that people would notice an upgrade in (OpenOffice.org, KDE, Gnome etc.)?
If they keep to a kernel version for as long as possible they don't have to rewrite their own patches for a new version, they don't have to wonder if a new problem that comes through is due to the new kernel and they don't have to expend resources on a fairly back-of-the-store package that people (who use stock kernels) don't care about much.
Contrast that with other users (like myself) who eschew the patches that a distro puts into their kernels and download the latest vanilla sources from kernel.org (usually the same day it comes out), go through the config to make sure nothing major needs tweaking and compile a kernel that will only run at full potential (if I configured everything right
Even on my Arch Linux system that is very good at using up to date sources I prefer to roll my own. If you (and others like you) would prefer to run a newer kernel, read up on it (Scroogle Is Your Friend) and don't be afraid to try. The worst that can happen (if you keep your original kernel in the boot menu, and the modules to support it) is that your new shiny kernel sits there and tells you that there is no way on earth it can boot the system and it is having a bit of a panic with itself. Fixed by booting into your stock kernel and trying again.
Good luck with it if you choose to go that direction, and remember to read the changelogs for the newer kernels to see what has been improved and fixed. The latest stable kernel is 2.6.30.1, not 2.6.30. It isn't bleeding edge, it is the latest stable. Bleeding edge kernels have -xxn appended (where xxn is letters and numbers depending on what type of kernel or patches are added).
Re:Wait, what? (Score:2, Informative)
You are completely wrong and you should learn some C before posting crap like this.
The NULL pointer has the value 0 and no other value. Period. Internally, it can be represented by other bit-patterns than all-0. But the C standard demands that
void *x = 0;
generates the NULL pointer.
The last paragraph is also completely wrong because you fail to realize that the substraction of two pointers gives an integer and not another pointer.
So: please, please don't post again until you've learnt the absolute basics of the C language to prevent further embarrassment!
Re:Serious bug in gcc? (Score:4, Informative)
That is: It don't have to be all 0 bits. It just need to be distinct from any valid pointer,
Correct - apart from the "just" bit.
It doesn't need to be all 0 bits.
It does need to be distinct from any valid pointer.
*and*
void *p = 0;
must generate a null pointer, and:
p == 0
must come out true if p is a null pointer. The internal implementation need not be all zeroes, but it does need to look rather like it to source code.
Re:Wait, what? (Score:5, Informative)
First, NULL is a preprocessor construct, not a language construct; by the time it gets to the compiler the preprocessor has replaced it with a magic constant[1].
Which must be either "0" or "(void *) 0".
The standard requires that it be defined as some value that may not be dereferenced, which is typically 0 (but doesn't have to be
Not true - the standard requires NULL to be defined as one of the two values given above.
and isn't on some mainframes
There are indeed some platforms where a null pointer is not an all-bits-zero value, but this is achieved by compiler magic behind the scenes. It is still created by assigning the constant value 0 to a pointer, and can be checked for by comparing a pointer with a constant 0.
Re:Just don't use that version (Score:1, Informative)
It is stable, but it will take a little while for the packages to find their way into the network for your particular distribution.
Generally any release with an even number at the end will be a stable release. The kernel development cycle is basically: add functionality (odd releases), make stable (even releases).
As for whether 2.6.30 is really bleeding edge, I wouldn't have said so. It's not yet in wide use, as not that many new distros have been released since it came out. See: Distros are often built on top of a kernel, so changing the kernel just before it's about to be released can rock the boat a little even if the kernel itself is stable. I personally wouldn't bother upgrading unless there's a feature in the kernel that you needed. 2.6.31 could be considered bleeding edge, however, as that's an unstable release that they'll be shoving a lot of new functionality or completing stuff that wasn't ready for 2.6.30.
That said, there were features that I wanted to check-out in the 2.6.30.1 kernel and so I did download and compile it (but only put it on the machine that needed it).
Re:Double standards (Score:1, Informative)
The check is not placed wrong, it should be safe as long as *sk isn't used before the check. The *sk assignment does not dereference to a memory location, it does nothing more than adding an offset to the other pointer: ((int)sk - (int)tun) is the offset of sk in 'struct sock'.
The compiler is wrong in assuming tun is always non-NULL at that point in the code.
It's certainly a compiler bug, it's the same as optimizing the 'if (!tun)' out of this code:
const int sk_off = 123;
int func(int tun)
{
int sk = tun + sk_off;
if (!tun) return -1;
return sk;
}
Re:Linus, you Rookie !! (Score:4, Informative)
Ok, I know I shouldn't be feeding the troll, but read the article: the kernel source itself is perfectly fine, is the compiler that optimizes the check away.
no exact code snippet found in Linux (Score:4, Informative)
I tried to google code search for "tun->sk" and Linux doesn't contain that snippet of code. Since SANS claimed that drivers/net/tun.c is at fault, I looked at that source file and didn't find any instances where "if (!...) return ...;" is performed after NULL dereference.
I think the only fascinating bit of the story is that the SElinux extension allows you to map a page at memory address 0 (the NULL page), making NULL dereferencing valid. I also found out about that [likai.org] a while ago, but I didn't know it has anything to do with SElinux. By the way, mapping the NULL page also works on Mac OS X.
However, mapping NULL page is typically NOT exploitable. A correct program will simply reject access to NULL pointer, giving it a special semantic regardless whether the memory page itself is valid or not.
Re:Double standards (Score:5, Informative)
No. You are wrong.
The code is grabbing the value of the sk field of the tun struct, not its address. Did you misread the code, or do you not actually know C? Or are you perhaps just on the sauce?
You're claiming the code reads struct sock **sk = &tun->sk when in reality, it reads struct sock* sk = tun->sk, which is completely different.
Re:Wait, what? (Score:4, Informative)
You're speaking with a voice of authority, which is dangerous because of how incorrect in general your post is.
Others have already pointed out that you are wrong about NULL. Here's precisely what the spec says about the argument to &:
The operand of the unary & operator shall be either a function designator, the result of a
[] or unary * operator, or an lvalue that designates an object that is not a bit-field and is
not declared with the register storage-class specifier.
(((struct foo*)(void*)0)->bar) in particular is none of those things, and your expression is not legal C.
Some apparent dereferences of null pointers are allowed. For instance:
void *a = 0;
void *b = &(*a);
The above is legal not because dereferencing a null pointer is legal, but rather because of an explicit exception to the rule carved out in section 6.5.3.2 of the spec, which says that in this case, the & and * cancel, and "the result is as if both were omitted".
Your expression is neither safe nor portable. If you do need to check the offset of a field in a structure, use the standard library offsetof() macro -- that's what it's for.
"Assembler" (Score:2, Informative)
Re:Double standards (Score:4, Informative)
Oh please, it's a response to
If this had been Windows, the article would have been tagged defectivebydesign.
You're not supposed to read the article, but at least the post you're criticizing.
code found in Linux 2.6.30 (Score:5, Informative)
Oh, found the code on lxr. It looks like Linux kernels up to 2.6.29.6 [linux.no] are NOT affected, and this is a vulnerability introduced in 2.6.30 [linux.no] due to a fairly significant rewrite of tun.c. Linux 2.6.30 was released in Jun 9, 2009, just a month ago. Funny the tun.c rewrite was not mentioned in the set of changes for 2.6.30.
I think this example actually shows a forte of Linux as open source. New vulnerability is found very quickly after "new" code is released.
the set of changes for 2.6.30 (Score:5, Informative)
Re:Wait, what? (Score:3, Informative)
The value &(tun->sk) is the address of tun, plus a fixed offset. The expression &(((struct foo*)0)->bar) is valid C and will give the value of the offset of the sk field in the foo struct. A typical definition of NULL is (void*)0, and &(((struct foo*)(void*)0)->bar) will also give the value of the offset of the bar field.
Wrong. If tun is a null pointer, then the only valid operations are the following:
1. Assign tun to a pointer variable of a matching type or of type void*, which will set that variable to a null pointer.
2. Cast tun to another pointer type, which will produce a null pointer.
3. Cast tun to an integral type, which will produce the value 0 (and this is true whatever bit pattern the compiler uses for null pointers)
4. Comparing tun to a pointer of a matching type or type void* using the == or != operators.
Dereferencing a pointer, even if it is just done to calculate an address, is undefined behaviour.
Re:"Assembler" (Score:2, Informative)
I remember this as well. And here's evidence:
http://www.amazon.com/Assembler-Language-Programming-Compatible-Computers/dp/0471886572/
Re:Linus, you Rookie !! (Score:5, Informative)
Ok, I know I shouldn't be feeding the troll, but read the article: the kernel source itself is perfectly fine, is the compiler that optimizes the check away.
Absolutely not. The code itself has a severe bug: If tun is a null pointer then it invokes undefined behaviour. Undefined behaviour means anything can happen. Anything can happen means a severe bug, especially in kernel code. The optimizing compiler just turned C source code that was buggy, but not obviously enough for the programmer, into assembler code that would have been obviously buggy to anyone. Most definitely not the fault of the compiler.
Re:CFLAGS (Score:3, Informative)
Because it makes sense on every modern platform on earth except for strange embedded ones, that's why. This kernel bug is the result of incorrect kernel code, not a GCC bug.
Re:Double standards (Score:4, Informative)
Assembly of any sort isn't that difficult once you get some experience with it, and with the proper macros and defines set up, it can actually be fairly quick to code in. Some chips are easier than others (the 68K was *awesome* to code for), but it just requires some attention to detail and a good understanding of how the machine works.
Re:Double standards (Score:3, Informative)
A bug exists with or without the optimization if the code you pasted is the actual code. tun being null makes the tun->sk reference invalid. You should end up with a panic at this point.
If the compiler optimized away the tun check without there being a previous tun check, there is also a compiler bug. The compiler shouldn't have assumed that tun was initialized just because it was read from, which is all a dereference is, a read and an add.
Re:Double standards (Score:4, Informative)
Yes, but the rest of us have written about 1000 times more code than you because we didn't spend our time checking a ton of assembly because we presume the compiler is flawed.
There are times when this sort of checking is acceptable if not required. The kernel is a good place to do it.
You aren't going to do this for KDE or Gnome however.
Re:Double standards (Score:3, Informative)
The exploit maps 0x00000000 to userspace using pulseaudio, this prevents the segfault.
Re:Double standards (Score:2, Informative)
(I think it'll be a cold day in hell when the BSDs move away from GCC.)
Oh, really! [freebsd.org] Time to buy me a new coat.
Re:Double standards (Score:3, Informative)
There aren't any important services that run setuid is there?
Oh...
Re:Wait, what? (Score:4, Informative)
Which must be either "0" or "(void *) 0". ...
There are indeed some platforms where a null pointer is not an all-bits-zero value, but this is achieved by compiler magic behind the scenes. It is still created by assigning the constant value 0 to a pointer, and can be checked for by comparing a pointer with a constant 0.
What you've said is technically true, but doesn't contradict or clarify the post to which you replied in any way, so I'm not sure what your point is.
As you point out, a NULL pointer is a pointer which is represented by "(void *) 0" in the C language. However, where you may be confused is that "(void *) 0 != (int) 0". At least, not always. The compiler is responsible for determining if any "0" is used in a pointer context and casting it to the appropriate value, which may not be the same as numeric "0". So, while it's always possible to check for a NULL pointer by comparing a pointer to 0 in code, the machine may use a different value for NULL pointers. When you check "if(p)", the binary code that is produced will be comparing the value of "p" to the NULL address which is appropriate for the machine on which it is running.
The C FAQ [c-faq.com] has more information.
Re:Just don't use that version (Score:5, Informative)
I'm very sorry, but you are wrong.
There is no longer an unstable/stable kernel branch difference. Essentially all new kernels are development versions. It is specifically up to the distribution vendors to pick stable kernels out of this continuous release stream.
Mart
Re:Wait, what? (Score:5, Informative)
In this case, it is tun->sk, not &(tun->sk) which is being loaded, however the pointer arithmetic which generates the address happens first. If tun is NULL then this is NULL + {the offset of sk}. While dereferencing NULL is explicitly not permitted, pointer arithmetic on NULL is permitted, and dereferencing any non-NULL memory address is permitted.
Raven, I've seen you make the same comment a few times in this story. Please stop pushing this nonsense.
The language standard calls * and -> operations "dereferencing". The way it works is that tun->sk dereferences the whole struct, then hands you the sk field from it.
When you implement this in your compiler you do an address computation first then load only the field because you don't want to load the whole struct when you don't need to, but that's an implementation detail. The compiler is required to act as if the pointer tun were being dereferenced.
It would be a major missed optimization bug if the compiler didn't eliminate the later if (!tun) operation. This is a case where the input code is simply wrong.
Re:Linus, you Rookie !! (Score:3, Informative)
The code makes a potentially undefined assignment, but before doing anything significant with it, it checks for the undefined condition. It's not technically wrong but it is against best practices. Without the invalid optimization it wouldn't be a problem. In turn, the optimization is in the opposite condition. It is technically wrong, but where best practices are followed, it does no harm.
Re:Double standards (Score:3, Informative)
Nope. PulseAudio is NOT necessary to trigger this flaw. Read the exploit source code.
PS: I hate PulseAudio bashing.
Re:Linus, you Rookie !! (Score:1, Informative)
Obviously, it is technically wrong since we're now reading a story about a null pointer exploit in the kernel...
Re:Linus, you Rookie !! (Score:4, Informative)
Umm - no - the *code* does the undefined behaviour and *then* checks if the undefined behaviour could happen. But, heck, mistakes happen - it was identified and fixed. Not much of a story really.
Re:Double standards (Score:1, Informative)
This kernel was released fifteen days ago. It is not present in any mainstream distro yet. Linux has a built in patch distribution system that works a hell of a lot better than Windows Update.
"MS puts a high priority on any security issue" What is that supposed to mean? That problems *don't* go unfixed for months or years? What planet do you come from?
Why don't you go take a look at how many people are using Win XP, IE 6, or any old version of any Microsoft software. The number of windows environments that are running completely patched, up to date software is a tiny minority.
Re:Linus, you Rookie !! (Score:2, Informative)
The code *IS* technically wrong: It dereferences a NULL pointer. The fact that the pointer is checked against NULL *after* dereferencing it does not help one bit. Once you invoke undefined behavior, the code could do ANYTHING you can imagine and it wouldn't be the compiler's fault.
Re:Bullshit (Score:3, Informative)
If, and only if "tun = 0; if (!tun) ..." be optimised, since NULL is not guarented to be 0.
I left your comments about some C programmers out, because they just make you look like an idiot.
A "null pointer constant" is by definition either an integer constant expression with a value of zero, or such an expression cast to (void *).
NULL is guaranteed to be a macro that evaluates to a "null pointer constant", with parentheses around it if needed.
In certain contexts (when assigned to a pointer lvalue, or when compared to a pointer expression), the compiler will replace a "null pointer constant" with a null pointer of the correct type.
When used as the operand of &&, || or !, or used as the controlling expression in an if, while, do-while statement or ?: expression, a null pointer is converted to an int with value 0, any valid pointer that is not a null pointer is converted to an int with value 1.
When a null pointer is cast to in integer type, the result is 0. When an integer with value 0 is cast to a pointer type, the result is a null pointer.
The representation of a null pointer (that is what is stored in memory and what memcpy would copy) is not guaranteed to be all-bits zero, but the compiler guarantees that all of the above is still true.
Dereferencing a null pointer invokes undefined behavior. Whether the instructions that a compiler generates to do this dereferencing cause a crash is irrelevant, the fact that a null pointer is dereferenced is enough. This also applies to pointer arithmetic, where the code will usually not crash, but nevertheless is undefined behaviour. And it is quite clear in the definition of the C language that a compiler is allowed to always assume that there is no undefined behavior. The dereferencing of tun invokes undefined behaviour when tun is a null pointer, therefore the compiler is allowed by the rules of the C language to _assume_ that tun is not a null pointer. Therefore it is absolutely legal for the compiler to remove the test.
Re:Serious bug in gcc? (Score:3, Informative)
That's not true. It's not your fault, you just assumed the GP was right, and he's not.
In your example, you thought b->two was (dereference of (0 + offset)) but actually it's ((dereference of 0) + an offset).
To get the former you need b to be an actual struct and use (*b.two). Or with it still being a pointer you could do some fancy pointer arithmetic, like *(((int *) b) + 1).