Slashdot Mirror


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.'"

7 of 391 comments (clear)

  1. Wait, what? by TheRaven64 · · Score: 4, Interesting
    This code looks right?

    struct sock *sk = tun->sk; // initialize sk with tun->sk
    ...
    if (!tun)
    return POLLERR; // if tun is NULL return error

    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
    1. Re:Wait, what? by pdh11 · · Score: 5, Interesting

      Technically, however, the C standard allows tun->sk to be a valid address, so removing the test is a semantically-invalid optimisation.

      No. Technically, if tun is null, dereferencing it in the expression tun->sk invokes undefined behaviour -- not implementation-defined behaviour. It is perfectly valid to remove the test, because no strictly conforming code could tell the difference -- the game is already over once you've dereferenced a null pointer. This is a kernel bug (and not even, as Brad Spengler appears to be claiming, a new class of kernel bug); it's not a GCC bug.

      But as other posters have said, it would indeed be a good security feature for GCC to warn when it does this.

      Peter

    2. Re:Wait, what? by Athanasius · · Score: 3, Interesting

      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).

      If you actually read the exploit code (see: http://grsecurity.net/~spender/cheddar_bay.tgz) the thing that really enables this exploit is one of two ways to map page zero. One of these seems to be a flaw with SELinux (either with the default settings and/or how the default config commonly ships) or using personality(2) to select a personality that explicitly allows this.

      From the exploit for the personality case:

      int main(void)
      {
              int ret;
              struct stat fstat;
       
              ret = personality(PER_SVR4);
       
              if (ret == -1) {
                      fprintf(stderr, "Unable to set personality!\n");
                      return 0;
              }

      Note you do need some setuid root program even with this (from my reading of the exploit code).

      In the SELinux case "it just works" without needing the setuid program it seems.

  2. Re:Serious bug in gcc? by TheRaven64 · · Score: 5, Interesting
    Except that his explanation is wrong. Dereferencing NULL is illegal, but pointer arithmetic on NULL is legal (and, even if it were illegal, would be practically impossible for a compiler to check). This statement is not dereferencing NULL, it is (potentially) performing pointer arithmetic on NULL (adding the offset of the sk field to it) and then dereferencing the result. This may or may not be valid, depending on how NULL is defined (it doesn't have to be 0 in C, although it usually is), what the offset of sk is, and what the memory layout of the target platform is.

    On most modern platforms, NULL is defined as (void*)0 and the entire bottom page of memory is mapped as no-access. On some embedded systems, however, the bottom few hundred bytes are used for I/O and you get the addresses of these by adding a value to 0. On these systems it is perfectly valid (and correct) C to define a structure which has the layout of the attached devices and then cast 0 to a pointer to this structure and use that for I/O.

    --
    I am TheRaven on Soylent News
  3. Re:Double standards by Shinobi · · Score: 4, Interesting

    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.

  4. Re:Serious bug in gcc? by Rockoon · · Score: 3, Interesting

    In effect, the code is a form of broken defensive programming (you check after the fact whether you've screwed up). It's wrong, but we still wouldn't want the compiler to silently remove the check. So I think the ideal solution (besides fixing the code) is to add a warning to the compiler. NULL pointer dereferences are a bug in the vast majority of cases, and checking for a NULL pointer after dereferencing it (in such a way that the compiler recognizes it and is about to remove the check) is at best redundant and more likely a bug.

    My problem with this sort of thinking is when you throw in macros and templates and whatnot, there can end up being hundreds, thousands, even millions of "redundant" tests againt NULL specified by the expanded source. Now, I suspect that simply adding this warning to GCC and then compiling some large project would generate so many such warnings that the only reasonable choice would be to then disable that warning. The warning would then have no value, and if so then that certainly doesnt address the "problem."

    As far as the other stuff.. my point was that the arguement that the compiler should never optimize away such if() statements is flawed. I was responding to someone who did in fact make such a claim. There are certainly cases where the pointer absolutely cannot be NULL (or absolutely must be) .. ex, the pointer was just assigned, or its nested within another test for null.

    --
    "His name was James Damore."
  5. Re:Just like Linux by Jimithing+DMB · · Score: 3, Interesting

    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.