Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

uml: fix FP register corruption

Commit ee3d9bd4de1ed93d2a7ee41c331ed30a1c7b8acd ("uml: simplify SIGSEGV
handling"), while greatly simplifying the kernel SIGSEGV handler that
runs in the process address space, introduced a bug which corrupts FP
state in the process.

Previously, the SIGSEGV handler called the sigreturn system call by hand - it
couldn't return through the restorer provided to it because that could try to
call the libc restorer which likely wouldn't exist in the process address
space. So, it blocked off some signals, including SIGUSR1, on entry to the
SIGSEGV handler, queued a SIGUSR1 to itself, and invoked sigreturn. The
SIGUSR1 was delivered, and was visible to the UML kernel after sigreturn
finished.

The commit eliminated the signal masking and the call to sigreturn. The
handler simply hits itself with a SIGTRAP to let the UML kernel know that it
is finished. UML then restores the process registers, which effectively
longjmps the process out of the signal handler, skipping sigreturn's restoring
of register state and the signal mask.

The bug is that the host apparently sets used_fp to 0 when it saves the
process FP state in the sigcontext on the process signal stack. Thus, when
the process is longjmped out of the handler, its FP state is corrupt because
it wasn't saved on the context switch to the UML kernel.

This manifested itself as sleep hanging. For some reason, sleep uses floating
point in order to calculate the sleep interval. When a page fault corrupts
its FP state, it is faked into essentially sleeping forever.

This patch saves the FP state before entering the SIGSEGV handler and restores
it afterwards.

Signed-off-by: Jeff Dike <jdike@linux.intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Jeff Dike and committed by
Linus Torvalds
2f56debd e4d06b3f

+49
+2
arch/um/include/registers.h
··· 18 18 extern int init_registers(int pid); 19 19 extern void get_safe_registers(unsigned long *regs); 20 20 extern unsigned long get_thread_reg(int reg, jmp_buf *buf); 21 + extern int get_fp_registers(int pid, unsigned long *regs); 22 + extern int put_fp_registers(int pid, unsigned long *regs); 21 23 22 24 #endif
+3
arch/um/include/sysdep-i386/ptrace_user.h
··· 9 9 #include <sys/ptrace.h> 10 10 #include <linux/ptrace.h> 11 11 #include <asm/ptrace.h> 12 + #include "user_constants.h" 12 13 13 14 #define PT_OFFSET(r) ((r) * sizeof(long)) 14 15 ··· 40 39 #define PT_IP(regs) ((regs)[EIP]) 41 40 #define PT_SP_OFFSET PT_OFFSET(UESP) 42 41 #define PT_SP(regs) ((regs)[UESP]) 42 + 43 + #define FP_SIZE ((HOST_XFP_SIZE > HOST_FP_SIZE) ? HOST_XFP_SIZE : HOST_FP_SIZE) 43 44 44 45 #ifndef FRAME_SIZE 45 46 #define FRAME_SIZE (17)
+3
arch/um/include/sysdep-x86_64/ptrace_user.h
··· 12 12 #include <linux/ptrace.h> 13 13 #include <asm/ptrace.h> 14 14 #undef __FRAME_OFFSETS 15 + #include "user_constants.h" 15 16 16 17 #define PT_INDEX(off) ((off) / sizeof(unsigned long)) 17 18 ··· 69 68 70 69 #define REGS_IP_INDEX PT_INDEX(RIP) 71 70 #define REGS_SP_INDEX PT_INDEX(RSP) 71 + 72 + #define FP_SIZE (HOST_FP_SIZE) 72 73 73 74 #endif 74 75
+15
arch/um/os-Linux/skas/process.c
··· 115 115 sizeof(struct ptrace_faultinfo)); 116 116 } 117 117 else { 118 + unsigned long fpregs[FP_SIZE]; 119 + 120 + err = get_fp_registers(pid, fpregs); 121 + if (err < 0) { 122 + printk(UM_KERN_ERR "save_fp_registers returned %d\n", 123 + err); 124 + fatal_sigsegv(); 125 + } 118 126 err = ptrace(PTRACE_CONT, pid, 0, SIGSEGV); 119 127 if (err) { 120 128 printk(UM_KERN_ERR "Failed to continue stub, pid = %d, " ··· 136 128 * the stub stack page. We just have to copy it. 137 129 */ 138 130 memcpy(fi, (void *)current_stub_stack(), sizeof(*fi)); 131 + 132 + err = put_fp_registers(pid, fpregs); 133 + if (err < 0) { 134 + printk(UM_KERN_ERR "put_fp_registers returned %d\n", 135 + err); 136 + fatal_sigsegv(); 137 + } 139 138 } 140 139 } 141 140
+16
arch/um/os-Linux/sys-i386/registers.c
··· 56 56 57 57 int have_fpx_regs = 1; 58 58 59 + int get_fp_registers(int pid, unsigned long *regs) 60 + { 61 + if (have_fpx_regs) 62 + return save_fpx_registers(pid, regs); 63 + else 64 + return save_fp_registers(pid, regs); 65 + } 66 + 67 + int put_fp_registers(int pid, unsigned long *regs) 68 + { 69 + if (have_fpx_regs) 70 + return restore_fpx_registers(pid, regs); 71 + else 72 + return restore_fp_registers(pid, regs); 73 + } 74 + 59 75 void arch_init_registers(int pid) 60 76 { 61 77 unsigned long fpx_regs[HOST_XFP_SIZE];
+10
arch/um/os-Linux/sys-x86_64/registers.c
··· 40 40 return 0; 41 41 } 42 42 } 43 + 44 + int get_fp_registers(int pid, unsigned long *regs) 45 + { 46 + return save_fp_registers(pid, regs); 47 + } 48 + 49 + int put_fp_registers(int pid, unsigned long *regs) 50 + { 51 + return restore_fp_registers(pid, regs); 52 + }