[PATCH] s390: ptrace peek and poke
authorMartin Schwidefsky <schwidefsky@de.ibm.com>
Sat, 4 Jun 2005 22:43:30 +0000 (15:43 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Sun, 5 Jun 2005 00:13:00 +0000 (17:13 -0700)
The special cases of peek and poke on acrs[15] and the fpc register are not
handled correctly.  A poke on acrs[15] will clobber the 4 bytes after the
access registers in the thread_info structure.  That happens to be the kernel
stack pointer.  A poke on the fpc with an invalid value is not caught by the
validity check.  On the next context switch the broken fpc value will cause a
program check in the kernel.  Improving the checks in peek and poke fixes
this.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
arch/s390/kernel/ptrace.c

index 26889366929a1ac3d9162a2ba357dd837a143877..329d9391c83d2d3b9084dcbda16caf45054027bc 100644 (file)
@@ -40,6 +40,7 @@
 #include <asm/pgalloc.h>
 #include <asm/system.h>
 #include <asm/uaccess.h>
+#include <asm/unistd.h>
 
 #ifdef CONFIG_S390_SUPPORT
 #include "compat_ptrace.h"
@@ -130,13 +131,19 @@ static int
 peek_user(struct task_struct *child, addr_t addr, addr_t data)
 {
        struct user *dummy = NULL;
-       addr_t offset, tmp;
+       addr_t offset, tmp, mask;
 
        /*
         * Stupid gdb peeks/pokes the access registers in 64 bit with
         * an alignment of 4. Programmers from hell...
         */
-       if ((addr & 3) || addr > sizeof(struct user) - __ADDR_MASK)
+       mask = __ADDR_MASK;
+#ifdef CONFIG_ARCH_S390X
+       if (addr >= (addr_t) &dummy->regs.acrs &&
+           addr < (addr_t) &dummy->regs.orig_gpr2)
+               mask = 3;
+#endif
+       if ((addr & mask) || addr > sizeof(struct user) - __ADDR_MASK)
                return -EIO;
 
        if (addr < (addr_t) &dummy->regs.acrs) {
@@ -153,6 +160,16 @@ peek_user(struct task_struct *child, addr_t addr, addr_t data)
                 * access registers are stored in the thread structure
                 */
                offset = addr - (addr_t) &dummy->regs.acrs;
+#ifdef CONFIG_ARCH_S390X
+               /*
+                * Very special case: old & broken 64 bit gdb reading
+                * from acrs[15]. Result is a 64 bit value. Read the
+                * 32 bit acrs[15] value and shift it by 32. Sick...
+                */
+               if (addr == (addr_t) &dummy->regs.acrs[15])
+                       tmp = ((unsigned long) child->thread.acrs[15]) << 32;
+               else
+#endif
                tmp = *(addr_t *)((addr_t) &child->thread.acrs + offset);
 
        } else if (addr == (addr_t) &dummy->regs.orig_gpr2) {
@@ -167,6 +184,9 @@ peek_user(struct task_struct *child, addr_t addr, addr_t data)
                 */
                offset = addr - (addr_t) &dummy->regs.fp_regs;
                tmp = *(addr_t *)((addr_t) &child->thread.fp_regs + offset);
+               if (addr == (addr_t) &dummy->regs.fp_regs.fpc)
+                       tmp &= (unsigned long) FPC_VALID_MASK
+                               << (BITS_PER_LONG - 32);
 
        } else if (addr < (addr_t) (&dummy->regs.per_info + 1)) {
                /*
@@ -191,13 +211,19 @@ static int
 poke_user(struct task_struct *child, addr_t addr, addr_t data)
 {
        struct user *dummy = NULL;
-       addr_t offset;
+       addr_t offset, mask;
 
        /*
         * Stupid gdb peeks/pokes the access registers in 64 bit with
         * an alignment of 4. Programmers from hell indeed...
         */
-       if ((addr & 3) || addr > sizeof(struct user) - __ADDR_MASK)
+       mask = __ADDR_MASK;
+#ifdef CONFIG_ARCH_S390X
+       if (addr >= (addr_t) &dummy->regs.acrs &&
+           addr < (addr_t) &dummy->regs.orig_gpr2)
+               mask = 3;
+#endif
+       if ((addr & mask) || addr > sizeof(struct user) - __ADDR_MASK)
                return -EIO;
 
        if (addr < (addr_t) &dummy->regs.acrs) {
@@ -224,6 +250,17 @@ poke_user(struct task_struct *child, addr_t addr, addr_t data)
                 * access registers are stored in the thread structure
                 */
                offset = addr - (addr_t) &dummy->regs.acrs;
+#ifdef CONFIG_ARCH_S390X
+               /*
+                * Very special case: old & broken 64 bit gdb writing
+                * to acrs[15] with a 64 bit value. Ignore the lower
+                * half of the value and write the upper 32 bit to
+                * acrs[15]. Sick...
+                */
+               if (addr == (addr_t) &dummy->regs.acrs[15])
+                       child->thread.acrs[15] = (unsigned int) (data >> 32);
+               else
+#endif
                *(addr_t *)((addr_t) &child->thread.acrs + offset) = data;
 
        } else if (addr == (addr_t) &dummy->regs.orig_gpr2) {
@@ -237,7 +274,8 @@ poke_user(struct task_struct *child, addr_t addr, addr_t data)
                 * floating point regs. are stored in the thread structure
                 */
                if (addr == (addr_t) &dummy->regs.fp_regs.fpc &&
-                   (data & ~FPC_VALID_MASK) != 0)
+                   (data & ~((unsigned long) FPC_VALID_MASK
+                             << (BITS_PER_LONG - 32))) != 0)
                        return -EINVAL;
                offset = addr - (addr_t) &dummy->regs.fp_regs;
                *(addr_t *)((addr_t) &child->thread.fp_regs + offset) = data;