mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] x86_64: handle iret faults better
@ 2005-04-24  0:50 Roland McGrath
  2005-04-25 15:33 ` Andi Kleen
  0 siblings, 1 reply; 14+ messages in thread
From: Roland McGrath @ 2005-04-24  0:50 UTC (permalink / raw)
  To: Andrew Morton, Linus Torvalds, Andi Kleen; +Cc: linux-kernel

This is the x86_64 variant of the i386 fix I just submitted.  I think
iret can only produce these faults when returning to user mode in a
32-bit process.  The failure mode is even more mysterious on x86_64,
because it exits with -9999&0x7f instead of 11 (SIGSEGV), so it says
"Unknown signal 113 (core dumped)" when it exits without actually
trying to dump a core file.


Thanks,
Roland


Signed-off-by: Roland McGrath <roland@redhat.com>

--- linux-2.6/arch/x86_64/kernel/entry.S
+++ linux-2.6/arch/x86_64/kernel/entry.S
@@ -471,14 +470,6 @@ iret_label:	
 	.section __ex_table,"a"
 	.quad iret_label,bad_iret	
 	.previous
-	.section .fixup,"ax"
-	/* force a signal here? this matches i386 behaviour */
-	/* running with kernel gs */
-bad_iret:
-	movq $-9999,%rdi	/* better code? */
-	jmp do_exit			
-	.previous	
-	
 	/* edi: workmask, edx: work */	
 retint_careful:
 	bt    $TIF_NEED_RESCHED,%edx
@@ -522,6 +513,31 @@ retint_kernel:	
 #endif	
 	CFI_ENDPROC
 	
+	/*
+	 * Traps in iret mean that userland tried to restore a bogus
+	 * cs, eip, ss, esp, or eflags.  Some kinds of bogosity just cause
+	 * a trap after the iret returns, but some will cause a trap in
+	 * iret itself.  We want to treat those as if the restored user
+	 * state is what caused that trap, i.e. produce the appropriate signal.
+	 * Since normal .fixup code doesn't have access to the trap info,
+	 * traps.c has a special case for iret.  It's already generated the
+	 * signal before we resume at bad_iret.  Now we just need to recover
+	 * the whole frame we were trying to restore, so it can be seen on
+	 * our stack by the debugger.
+	 */
+	.section .fixup,"ax"
+	/* running with kernel gs */
+ENTRY(bad_iret)
+	CFI_STARTPROC	simple
+	CFI_DEF_CFA	rsp,(SS+8-RIP)
+	CFI_REL_OFFSET	rip,0
+	CFI_REL_OFFSET	rsp,(RSP-RIP)
+	SAVE_ARGS 8
+	jmp exit_intr
+	CFI_ENDPROC
+	.previous
+
+	    
 /*
  * APIC interrupts.
  */		
@@ -826,7 +842,10 @@ paranoid_swapgs:	
 	swapgs
 paranoid_restore:	
 	RESTORE_ALL 8
-	iretq
+1:	iretq
+	.section __ex_table,"a"
+	.quad 1b,bad_iret
+	.previous
 paranoid_userspace:	
 	cli
 	GET_THREAD_INFO(%rcx)
--- linux-2.6/arch/x86_64/kernel/traps.c
+++ linux-2.6/arch/x86_64/kernel/traps.c
@@ -404,6 +404,44 @@ void die_nmi(char *str, struct pt_regs *
 	do_exit(SIGSEGV);
 }
 
+
+/*
+ * When we get an exception in the iret instructions in entry.S, whatever
+ * fault it is really belongs to the user state we are restoring.  We want
+ * to turn it into a signal.  To make that signal's info exactly match what
+ * this same kind of fault in a user instruction would show, the fixup
+ * needs to know the trapno and error code.  But those are lost when we get
+ * back to the fixup entrypoint.  So we have a special case for the iret
+ * fixups, and generate the signal here like a normal user trap would.
+ * Then the fixup code restores the pt_regs on the base of the stack to
+ * the bogus user state it was trying to return to, before handling the signal.
+ */
+extern void bad_iret(void);	/* entry.S label for code in .fixup */
+static inline int is_iret(struct pt_regs *regs)
+{
+	return (regs->rip == (unsigned long)&bad_iret);
+}
+
+/*
+ * If the iret was actually trying to return to kernel mode,
+ * that should be an oops.
+ */
+static inline int iret_to_user(struct pt_regs *regs)
+{
+	/*
+	 * The frame being restored was all popped off and restored except
+	 * the last five words that iret pops.  Instead of popping, it
+	 * pushed another trap frame, clobbering the part of the old one
+	 * that we had already restored.  So the restored registers are now
+	 * all back in the new trap frame, but the rip et al show the
+	 * in-kernel state at the iret instruction.  The bad state we tried
+	 * to restore with iret is still on the old stack below.
+	 */
+	struct pt_regs *oregs = container_of((unsigned long *) regs->rsp,
+					     struct pt_regs, rip);
+
+	return likely((oregs->cs & 3) == 3);
+}
 static void do_trap(int trapnr, int signr, char *str, 
 			   struct pt_regs * regs, long error_code, siginfo_t *info)
 {
@@ -423,7 +461,9 @@ static void do_trap(int trapnr, int sign
 #endif
 
 	if ((regs->cs & 3)  != 0) { 
-		struct task_struct *tsk = current;
+		struct task_struct *tsk;
+	user_trap:
+		tsk = current;
 
 		if (exception_trace && unhandled_signal(tsk, signr))
 			printk(KERN_INFO
@@ -447,7 +487,13 @@ static void do_trap(int trapnr, int sign
 		fixup = search_exception_tables(regs->rip);
 		if (fixup) {
 			regs->rip = fixup->fixup;
-		} else	
-			die(str, regs, error_code);
+			if (!is_iret(regs))
+				return;
+			if (iret_to_user(regs)) {
+				local_irq_enable();
+				goto user_trap;
+			}
+		}
+		die(str, regs, error_code);
 		return;
 	}
@@ -523,8 +569,10 @@ asmlinkage void do_general_protection(st
        }
 #endif
 
-	if ((regs->cs & 3)!=0) { 
-		struct task_struct *tsk = current;
+	if ((regs->cs & 3) != 0) {
+		struct task_struct *tsk;
+	user_gp:
+		tsk = current;
 
 		if (exception_trace && unhandled_signal(tsk, SIGSEGV))
 			printk(KERN_INFO
@@ -544,7 +592,12 @@ asmlinkage void do_general_protection(st
 		fixup = search_exception_tables(regs->rip);
 		if (fixup) {
 			regs->rip = fixup->fixup;
-			return;
+			if (!is_iret(regs))
+				return;
+			if (iret_to_user(regs)) {
+				local_irq_enable();
+				goto user_gp;
+			}
 		}
 		if (notify_die(DIE_GPF, "general protection fault", regs,
 					error_code, 13, SIGSEGV) == NOTIFY_STOP)

^ permalink raw reply	[flat|nested] 14+ messages in thread
* [PATCH] i386: handle iret faults better
@ 2005-04-24  0:42 Roland McGrath
  0 siblings, 0 replies; 14+ messages in thread
From: Roland McGrath @ 2005-04-24  0:42 UTC (permalink / raw)
  To: Andrew Morton, Linus Torvalds; +Cc: linux-kernel

A user process can cause a kernel-mode fault in the iret instruction, by
setting an invalid %cs and such, via ptrace or sigreturn.  The fixup code
now calls do_exit(11), so a process will die with SIGSEGV but never
generate a core dump.  This is vastly more confusing in a multithreaded
program, because do_exit just kills that one thread and so it appears to
mysteriously disappear when it calls sigreturn.  

This patch makes faults in iret produce the normal signals that would
result from the same errors when executing some user-mode instruction.  
It requires some special case hair rather than just better fixup code,
because the full trap details that go into the signal info are not
available to the fixup code.


Thanks,
Roland


Signed-off-by: Roland McGrath <roland@redhat.com>

--- linux-2.6/arch/i386/kernel/entry.S
+++ linux-2.6/arch/i386/kernel/entry.S
@@ -254,14 +253,23 @@ restore_nocheck:
 	RESTORE_REGS
 	addl $4, %esp
 1:	iret
+	/*
+	 * Traps in iret mean that userland tried to restore a bogus
+	 * cs, eip, ss, esp, or eflags.  Some kinds of bogosity just cause
+	 * a trap after the iret returns, but some will cause a trap in
+	 * iret itself.  We want to treat those as if the restored user
+	 * state is what caused that trap, i.e. produce the appropriate signal.
+	 * Since normal .fixup code doesn't have access to the trap info,
+	 * traps.c has a special case for iret.  It's already generated the
+	 * signal before we resume at iret_exc.  Now we just need to recover
+	 * the whole frame we were trying to restore, so it can be seen on
+	 * our stack by the debugger.
+	 */
 .section .fixup,"ax"
-iret_exc:
-	sti
-	movl $__USER_DS, %edx
-	movl %edx, %ds
-	movl %edx, %es
-	movl $11,%eax
-	call do_exit
+ENTRY(iret_exc)
+	pushl $0			# orig_eax was lost
+	SAVE_ALL
+	jmp ret_from_exception
 .previous
 .section __ex_table,"a"
 	.align 4
--- linux-2.6/arch/i386/kernel/traps.c
+++ linux-2.6/arch/i386/kernel/traps.c
@@ -357,6 +357,46 @@ static inline void die_if_kernel(const c
 		die(str, regs, err);
 }
 
+/*
+ * When we get an exception in the iret instructions in entry.S, whatever
+ * fault it is really belongs to the user state we are restoring.  We want
+ * to turn it into a signal.  To make that signal's info exactly match what
+ * this same kind of fault in a user instruction would show, the fixup
+ * needs to know the trapno and error code.  But those are lost when we get
+ * back to the fixup entrypoint.  So we have a special case for the iret
+ * fixups, and generate the signal here like a normal user trap would.
+ * Then the fixup code restores the pt_regs on the base of the stack to
+ * the bogus user state it was trying to return to, before handling the signal.
+ */
+extern void iret_exc(void);	/* entry.S label for code in .fixup */
+static inline int is_iret(struct pt_regs *regs)
+{
+	return (regs->eip == (unsigned long)&iret_exc);
+}
+
+/*
+ * If the iret was actually trying to return to kernel mode,
+ * that should be an oops.
+ */
+static inline int iret_to_user(struct pt_regs *regs)
+{
+	/*
+	 * The frame being restored was all popped off and restored except
+	 * the last five words that iret pops.  Instead of popping, it
+	 * pushed another trap frame, clobbering the part of the old one
+	 * that we had already restored.  So the restored registers are now
+	 * all back in the new trap frame, but the eip et al show the
+	 * in-kernel state at the iret instruction.  The bad state we tried
+	 * to restore with iret is still on the stack, right below our
+	 * current trap frame.  The current trap frame was for an in-kernel
+	 * trap, and so doesn't include the esp and ss words--so &regs->esp
+	 * is where %esp was before the iret.
+	 */
+	struct pt_regs *oregs = container_of(&regs->esp, struct pt_regs, eip);
+
+	return likely((oregs->xcs & 3) == 3);
+}
+
 static void do_trap(int trapnr, int signr, char *str, int vm86,
 			   struct pt_regs * regs, long error_code, siginfo_t *info)
 {
@@ -381,8 +421,16 @@ static void do_trap(int trapnr, int sign
 	}
 
 	kernel_trap: {
-		if (!fixup_exception(regs))
+		if (!fixup_exception(regs)) {
+		die:
 			die(str, regs, error_code);
+		}
+		else if (is_iret(regs)) {
+			if (!iret_to_user(regs))
+				goto die;
+			local_irq_enable();
+			goto trap_signal;
+		}
 		return;
 	}
 
@@ -490,6 +538,7 @@ fastcall void do_general_protection(stru
 	if (!(regs->xcs & 3))
 		goto gp_in_kernel;
 
+gp_in_user:
 	current->thread.error_code = error_code;
 	current->thread.trap_no = 13;
 	force_sig(SIGSEGV, current);
@@ -502,11 +551,18 @@ gp_in_vm86:
 
 gp_in_kernel:
 	if (!fixup_exception(regs)) {
+	die:
 		if (notify_die(DIE_GPF, "general protection fault", regs,
 				error_code, 13, SIGSEGV) == NOTIFY_STOP)
 			return;
 		die("general protection fault", regs, error_code);
 	}
+	else if (is_iret(regs)) {
+		if (!iret_to_user(regs))
+			goto die;
+		local_irq_enable();
+		goto gp_in_user;
+	}
 }
 
 static void mem_parity_error(unsigned char reason, struct pt_regs * regs)

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2005-05-02 19:56 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-04-24  0:50 [PATCH] x86_64: handle iret faults better Roland McGrath
2005-04-25 15:33 ` Andi Kleen
2005-04-25 15:55   ` Linus Torvalds
2005-04-25 22:31     ` Roland McGrath
2005-04-25 22:51       ` Linus Torvalds
2005-04-26  1:57         ` Roland McGrath
2005-04-26  4:06           ` Linus Torvalds
2005-04-29  3:40             ` [PATCH] i386: " Roland McGrath
2005-04-29 15:01               ` Andi Kleen
2005-04-29 16:44               ` Linus Torvalds
2005-05-02 19:56                 ` Andi Kleen
2005-04-29  3:45             ` [PATCH] x86_64: " Roland McGrath
2005-04-29 15:02               ` Andi Kleen
  -- strict thread matches above, loose matches on Subject: below --
2005-04-24  0:42 [PATCH] i386: " Roland McGrath

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®