lkml.org 
[lkml]   [1997]   [Apr]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    From
    SubjectRe: 2nd Linux kernel patch to remove stack exec
    Date
    Hello!

    > The basic idea is that buffer exploit calls rely on executing code after
    > a "return" instruction. The trampoline codes generated by compilers
    > apparently do so using a jump instruction, so you can tell the
    > difference.

    Actually, the trampolines are not the code that jumps onto the stack, they're
    on the stack themselves. It is a normal function call (via a function
    pointer) that gets the control to the trampoline on the stack. And the
    instruction causing a GPF is a call %reg, generated by GCC.

    > So if the EIP changes to a stack instruction, you take a GPF, and then
    > you figure out how you got there; if it was via a return instruction,
    > you assume it was due to a buffer exploit bug.

    Correct. However, there still is a little hole with my implementation (why
    did noone find it? I understood myself today). It is correct that the first
    instruction to move control a different way in most cases has to be a RET,
    however, as I mentioned earlier, in some cases the program may already have
    some suitable code in its code segment. Now, the problem is: the size of
    such code is too small, so it's likely to almost always exist. An example:
    we find a RETF instruction (one byte) somewhere in the code segment (not
    even necessarily on an instruction boundary), and overwrite the return address
    to point to that instruction, and some more bytes with the address for the
    RETF. That way our RET jumps to a place in the code segment (no GPF here),
    and the RETF can jump onto the stack, since the GPF handler only checks
    for a near return instruction, not a far return one.

    Adding a check for RETF also will help somewhat, but not much, since there're
    many two-byte instructions to pass the control onto the stack, and that
    still leaves quite a high probability that some suitable bytes exist in the
    code segment. The solution I did now (running this while typing this message)
    is to only allow a few instructions, not reject a few, in the GPF handler.
    Those allowed are only call %reg, where %reg is any of %eax, %ecx, %edx,
    %ebx, %ebp, %esi, %edi (all GP registers, except for %esp). These are the
    only instructions that GCC can generate for trampoline calls. Note that this
    way it is also required that a register has the correct value to exploit,
    so most likely only one of those 7 instructions will fit -- call %ebp (I
    wish I could disable this one, but GCC can still use it AFAIK if compiled
    with -fomit-frame-pointer). I did a search for the two bytes (call %ebp)
    in a bunch of binaries in my /bin, and found out that only about 5% of
    them had the bytes, which I find reasonable.

    It looks like we'll have to decide between disabling everything but these
    call instructions (might break something yet unknown), and disabling the
    two return instructions only (shouldn't break anything, but leaves some
    extra two-byte instructions working)...

    In any way, I have to admit that the GPF handler stuff is a bit less secure
    than totally disabling stack execution for some processes. This means that
    a flag for total execution permission disabling, that you could put on a
    binary, might still be useful...

    > Solar (sorry I don't know your real name), have you checked to make sure
    > none of the trampoline codes call subroutines from stack code? If so,

    Of course they do, that is what they're for.

    > then a user-space return instruction back into trampoline code might
    > erroneously cause your GPF handler to conclude that a buffer exploit was
    > in progress.

    It won't, since I enable stack execution permission for the rest of the
    entire process as soon as one trampoline is detected.

    > Another problem --- if a signal handler doesn't actually return, but
    > instead uses longjmp(), the CS register won't be reset, and you will no
    > longer be protected against executable code on the stack (since now the
    > CS segment register will be HUGE_CS which does allow stack execution).

    True.

    > Short of futzing with the longjmp()/setjmp() implementaiton, though,
    > there probably isn't much you can do about this.

    Well, I think this can be fixed in libc, by saving/restoring the CS value
    also. Another fix might be to keep a flag for each task, whether it should
    have its stack executable or not, and another flag, if it's inside a signal
    handler or not, both are quite easy to implement, and set the CS on return
    from every syscall accordingly. That way the program will become protected
    again after a longjmp() as soon as it makes any syscall. A really weird
    solution though, and is probably too complicated for a not so significant
    problem. I don't like it myself, it's just an example of what can be fixed
    by means of the kernel.

    Signed,
    Solar Designer

    \
     
     \ /
      Last update: 2005-03-22 13:39    [from the cache]
    ©2003-2014 Jasper Spaans. Advertise on this site