2nd Linux kernel patch to remove stack exec

solar@sun1.ideal.ru
Sun, 13 Apr 1997 16:06:38 -0300 (GMT)


Hello!

I include an improved version of my patch in this message. The main
difference from the old one is that no programs at all should be broken
by using it (this includes GCC trampolines). If some programs still get
broken, please let me know.

I'd like to thank everyone who replied to my previous message pointing
out the problem, I'll now answer the common stuff at once.

About GCC trampolines -- yes, there is a problem, but in reality it turns
out to be quite easy to solve; also, nested functions, and especially
those which address gets passed somewhere else, are not common in real
world applications -- one of the reasons is that it's a GNU C extension.
Since most programs will never use the trampolines, it makes sense to run
them with non-executable stack, and enable stack execution permission for
those that really need it. This can be done automatically, by modifying
the GPF handler to switch back to the huge code segment (which covers the
stack) and re-executing the instruction, unless it was a RET. Since most
buffer overflows can only be exploited by overwriting the return address,
this will still make them unexploitable (RET has to be the instruction to
pass the control onto the stack), while C programs will normally only use
CALL, and it is extremely unlikely that some code will use RET for that
purpose (this can never happen for pure C programs compiled with GCC).
Note that such emulation won't make the things run any slower since only
one GPF per entire process life may get generated (after that the stack
remains executable for this entire process).

About me breaking the entire signal handling -- wrong, I handle this case
specially from the very beginning, by temporary switching to the huge code
segment for the time of signal handler execution. This leaves potential
buffer overflows in signal handlers exploitable, but there seems to be no
other simple way for the kernel to put the necessary return code in user
program's address space (remember, signal handlers have to return with a
plain RET, but they need to return to the kernel, so some extra code in the
user space is required, which would get jumped to by the RET, and jump into
the kernel).

About [not] including the patch in Linux kernel release -- the patch was
not intended to be included in standard kernel distribution, at least not
right now, when it hasn't been tested widely enough. Anyway, it might be
reasonable to include it there after some testing is done, as a configurable
experimental feature.

A possible new question -- why can't an exploit be made such a way, so the
GPF handler would enable execution permission on the stack? This is due to
most buffer overflow vulnerabilities allowing to only overwrite the function
return address, and not some other pointer which would get jumped to. No
matter if the custom code would contain a CALL, since it has to be put onto
the stack, and GPF would happen when attempting to execute the RET, before
the control has a chance to get to the CALL. However, I admit there're some
rare buffer overflow cases which will remain exploitable -- these are when
the vulnerable function uses function pointers, and keeps them on its stack.
I only know one such example -- SuperProbe. Also, in some cases the custom
code may be put somewhere else in user program's address space, not on the
stack, or the program may already have some suitable code in it (I already
mentioned that in my previous message). Anyway, I believe my patch makes
most buffer overflows (well, at least some of them for sure, which is enough
to be worth using) unexploitable.

Another possible new question -- what if the GPF is caused by some bug in a
program? Well, in that case my patched handler will still switch to the huge
code segment, and attempt to re-execute the instruction, which will cause
the GPF again. This time the handler will do what it used to do earlier --
terminate the program with a SIGSEGV. I actually tested that, seems to work
fine (exactly the same as it did without my patch), including the case when
running under gdb. As usual, any bug reports are welcome.

Finally, someone might wonder if the patch is still useful, when it got that
fallback in the GPF handler. While using libc5, it is unlikely the fallback
will ever happen (even if it does, only that single process will be running
with the stack being executable), so the patch prevents many overflows from
being exploitable (I actually ensured that many overflow exploits stopped
working, well, except for my SuperProbe exploit that I mentioned above), so
the patch is useful. However, things are likely to change with glibc...

To enable/disable execution permission on the stack in your programs (who
would need that, with such a GPF handler?), the following can be used:

#include <asm/segment.h>
[...]
/* Switch to huge code segment => executable stack */
asm("ljmp %0,$1f\n1:\n" : : "i" (USER_HUGE_CS));
/* Switch to truncated code segment => non-executable stack */
asm("ljmp %0,$1f\n1:\n" : : "i" (USER_CS));

If someone really uses these, it might be reasonable to make such macros in
asm/segment.h, so the stuff looks more readable.

And now the patch... (to make it work with 2.1.x, change "cs" to "xcs" for
signal.c and traps.c).

diff -u --recursive /extra/linux-2.0.30/arch/i386/kernel/head.S linux/arch/i386/kernel/head.S
--- /extra/linux-2.0.30/arch/i386/kernel/head.S Sat Apr 12 10:41:59 1997
+++ linux/arch/i386/kernel/head.S Sat Apr 12 10:44:58 1997
@@ -402,7 +402,7 @@
.quad 0xc0c392000000ffff /* 0x18 kernel 1GB data at 0xC0000000 */
.quad 0x00cbfa000000ffff /* 0x23 user 3GB code at 0x00000000 */
.quad 0x00cbf2000000ffff /* 0x2b user 3GB data at 0x00000000 */
- .quad 0x0000000000000000 /* not used */
+ .quad 0x00cafa000000ffff /* 0x33 user 2.75GB code */
.quad 0x0000000000000000 /* not used */
.fill 2*NR_TASKS,8,0 /* space for LDT's and TSS's etc */
#ifdef CONFIG_APM
diff -u --recursive /extra/linux-2.0.30/arch/i386/kernel/signal.c linux/arch/i386/kernel/signal.c
--- /extra/linux-2.0.30/arch/i386/kernel/signal.c Sat Apr 12 10:41:59 1997
+++ linux/arch/i386/kernel/signal.c Sat Apr 12 10:44:58 1997
@@ -214,7 +214,7 @@
/* Set up registers for signal handler */
regs->esp = (unsigned long) frame;
regs->eip = (unsigned long) sa->sa_handler;
- regs->cs = USER_CS; regs->ss = USER_DS;
+ regs->cs = USER_HUGE_CS; regs->ss = USER_DS;
regs->ds = USER_DS; regs->es = USER_DS;
regs->gs = USER_DS; regs->fs = USER_DS;
regs->eflags &= ~TF_MASK;
diff -u --recursive /extra/linux-2.0.30/arch/i386/kernel/traps.c linux/arch/i386/kernel/traps.c
--- /extra/linux-2.0.30/arch/i386/kernel/traps.c Sat Apr 12 10:41:59 1997
+++ linux/arch/i386/kernel/traps.c Sun Apr 13 07:22:44 1997
@@ -198,6 +198,14 @@
return;
}
die_if_kernel("general protection",regs,error_code);
+ if (regs->cs == USER_CS && get_seg_byte(USER_DS, (char *)regs->eip) != 0xC3) {
+/*
+ * Switch to the original huge code segment (and allow code execution on the
+ * stack for this entire process), unless the faulty instruction is a RET.
+ */
+ regs->cs = USER_HUGE_CS;
+ return;
+ }
current->tss.error_code = error_code;
current->tss.trap_no = 13;
force_sig(SIGSEGV, current);
diff -u --recursive /extra/linux-2.0.30/include/asm-i386/segment.h linux/include/asm-i386/segment.h
--- /extra/linux-2.0.30/include/asm-i386/segment.h Sat Apr 12 10:41:37 1997
+++ linux/include/asm-i386/segment.h Sat Apr 12 10:44:58 1997
@@ -4,7 +4,8 @@
#define KERNEL_CS 0x10
#define KERNEL_DS 0x18

-#define USER_CS 0x23
+#define USER_HUGE_CS 0x23
+#define USER_CS 0x33
#define USER_DS 0x2B

#ifndef __ASSEMBLY__

Signed,
Solar Designer