Re: 2nd Linux kernel patch to remove stack exec
Mon, 14 Apr 1997 18:19:06 -0300 (GMT)


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


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

Solar Designer