PTRACE_POKEDATA consolidation
[safe/jmp/linux-2.6] / arch / i386 / kernel / ptrace.c
index 3196ba5..1c075f5 100644 (file)
@@ -9,7 +9,6 @@
 #include <linux/sched.h>
 #include <linux/mm.h>
 #include <linux/smp.h>
-#include <linux/smp_lock.h>
 #include <linux/errno.h>
 #include <linux/ptrace.h>
 #include <linux/user.h>
  * in exit.c or in signal.c.
  */
 
-/* determines which flags the user has access to. */
-/* 1 = access 0 = no access */
-#define FLAG_MASK 0x00044dd5
+/*
+ * Determines which flags the user has access to [1 = access, 0 = no access].
+ * Prohibits changing ID(21), VIP(20), VIF(19), VM(17), NT(14), IOPL(12-13), IF(9).
+ * Also masks reserved bits (31-22, 15, 5, 3, 1).
+ */
+#define FLAG_MASK 0x00050dd5
 
 /* set's the trap flag. */
 #define TRAP_FLAG 0x100
@@ -42,7 +44,7 @@
 /*
  * Offset of eflags on child stack..
  */
-#define EFL_OFFSET ((EFL-2)*4-sizeof(struct pt_regs))
+#define EFL_OFFSET offsetof(struct pt_regs, eflags)
 
 static inline struct pt_regs *get_child_regs(struct task_struct *task)
 {
@@ -51,24 +53,24 @@ static inline struct pt_regs *get_child_regs(struct task_struct *task)
 }
 
 /*
- * this routine will get a word off of the processes privileged stack. 
- * the offset is how far from the base addr as stored in the TSS.  
- * this routine assumes that all the privileged stacks are in our
+ * This routine will get a word off of the processes privileged stack.
+ * the offset is bytes into the pt_regs structure on the stack.
+ * This routine assumes that all the privileged stacks are in our
  * data space.
  */   
 static inline int get_stack_long(struct task_struct *task, int offset)
 {
        unsigned char *stack;
 
-       stack = (unsigned char *)task->thread.esp0;
+       stack = (unsigned char *)task->thread.esp0 - sizeof(struct pt_regs);
        stack += offset;
        return (*((int *)stack));
 }
 
 /*
- * this routine will put a word on the processes privileged stack. 
- * the offset is how far from the base addr as stored in the TSS.  
- * this routine assumes that all the privileged stacks are in our
+ * This routine will put a word on the processes privileged stack.
+ * the offset is bytes into the pt_regs structure on the stack.
+ * This routine assumes that all the privileged stacks are in our
  * data space.
  */
 static inline int put_stack_long(struct task_struct *task, int offset,
@@ -76,7 +78,7 @@ static inline int put_stack_long(struct task_struct *task, int offset,
 {
        unsigned char * stack;
 
-       stack = (unsigned char *) task->thread.esp0;
+       stack = (unsigned char *)task->thread.esp0 - sizeof(struct pt_regs);
        stack += offset;
        *(unsigned long *) stack = data;
        return 0;
@@ -86,11 +88,6 @@ static int putreg(struct task_struct *child,
        unsigned long regno, unsigned long value)
 {
        switch (regno >> 2) {
-               case FS:
-                       if (value && (value & 3) != 3)
-                               return -EIO;
-                       child->thread.fs = value;
-                       return 0;
                case GS:
                        if (value && (value & 3) != 3)
                                return -EIO;
@@ -98,6 +95,7 @@ static int putreg(struct task_struct *child,
                        return 0;
                case DS:
                case ES:
+               case FS:
                        if (value && (value & 3) != 3)
                                return -EIO;
                        value &= 0xffff;
@@ -113,9 +111,9 @@ static int putreg(struct task_struct *child,
                        value |= get_stack_long(child, EFL_OFFSET) & ~FLAG_MASK;
                        break;
        }
-       if (regno > GS*4)
-               regno -= 2*4;
-       put_stack_long(child, regno - sizeof(struct pt_regs), value);
+       if (regno > FS*4)
+               regno -= 1*4;
+       put_stack_long(child, regno, value);
        return 0;
 }
 
@@ -125,22 +123,19 @@ static unsigned long getreg(struct task_struct *child,
        unsigned long retval = ~0UL;
 
        switch (regno >> 2) {
-               case FS:
-                       retval = child->thread.fs;
-                       break;
                case GS:
                        retval = child->thread.gs;
                        break;
                case DS:
                case ES:
+               case FS:
                case SS:
                case CS:
                        retval = 0xffff;
                        /* fall through */
                default:
-                       if (regno > GS*4)
-                               regno -= 2*4;
-                       regno = regno - sizeof(struct pt_regs);
+                       if (regno > FS*4)
+                               regno -= 1*4;
                        retval &= get_stack_long(child, regno);
        }
        return retval;
@@ -182,17 +177,17 @@ static unsigned long convert_eip_to_linear(struct task_struct *child, struct pt_
        return addr;
 }
 
-static inline int is_at_popf(struct task_struct *child, struct pt_regs *regs)
+static inline int is_setting_trap_flag(struct task_struct *child, struct pt_regs *regs)
 {
        int i, copied;
-       unsigned char opcode[16];
+       unsigned char opcode[15];
        unsigned long addr = convert_eip_to_linear(child, regs);
 
        copied = access_process_vm(child, addr, opcode, sizeof(opcode), 0);
        for (i = 0; i < copied; i++) {
                switch (opcode[i]) {
-               /* popf */
-               case 0x9d:
+               /* popf and iret */
+               case 0x9d: case 0xcf:
                        return 1;
                /* opcode and address size prefixes */
                case 0x66: case 0x67:
@@ -244,7 +239,7 @@ static void set_singlestep(struct task_struct *child)
         * don't mark it as being "us" that set it, so that we
         * won't clear it by hand later.
         */
-       if (is_at_popf(child, regs))
+       if (is_setting_trap_flag(child, regs))
                return;
        
        child->ptrace |= PT_DTRACE;
@@ -271,6 +266,8 @@ static void clear_singlestep(struct task_struct *child)
 void ptrace_disable(struct task_struct *child)
 { 
        clear_singlestep(child);
+       clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
+       clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
 }
 
 /*
@@ -352,63 +349,18 @@ ptrace_set_thread_area(struct task_struct *child,
        return 0;
 }
 
-asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
+long arch_ptrace(struct task_struct *child, long request, long addr, long data)
 {
-       struct task_struct *child;
        struct user * dummy = NULL;
        int i, ret;
        unsigned long __user *datap = (unsigned long __user *)data;
 
-       lock_kernel();
-       ret = -EPERM;
-       if (request == PTRACE_TRACEME) {
-               /* are we already being traced? */
-               if (current->ptrace & PT_PTRACED)
-                       goto out;
-               ret = security_ptrace(current->parent, current);
-               if (ret)
-                       goto out;
-               /* set the ptrace bit in the process flags. */
-               current->ptrace |= PT_PTRACED;
-               ret = 0;
-               goto out;
-       }
-       ret = -ESRCH;
-       read_lock(&tasklist_lock);
-       child = find_task_by_pid(pid);
-       if (child)
-               get_task_struct(child);
-       read_unlock(&tasklist_lock);
-       if (!child)
-               goto out;
-
-       ret = -EPERM;
-       if (pid == 1)           /* you may not mess with init */
-               goto out_tsk;
-
-       if (request == PTRACE_ATTACH) {
-               ret = ptrace_attach(child);
-               goto out_tsk;
-       }
-
-       ret = ptrace_check_attach(child, request == PTRACE_KILL);
-       if (ret < 0)
-               goto out_tsk;
-
        switch (request) {
        /* when I and D space are separate, these will need to be fixed. */
        case PTRACE_PEEKTEXT: /* read word at location addr. */ 
-       case PTRACE_PEEKDATA: {
-               unsigned long tmp;
-               int copied;
-
-               copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
-               ret = -EIO;
-               if (copied != sizeof(tmp))
-                       break;
-               ret = put_user(tmp, datap);
+       case PTRACE_PEEKDATA:
+               ret = generic_ptrace_peekdata(child, addr, data);
                break;
-       }
 
        /* read the word at location addr in the USER area. */
        case PTRACE_PEEKUSR: {
@@ -435,10 +387,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
        /* when I and D space are separate, this will have to be fixed. */
        case PTRACE_POKETEXT: /* write the word at location addr. */
        case PTRACE_POKEDATA:
-               ret = 0;
-               if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data))
-                       break;
-               ret = -EIO;
+               ret = generic_ptrace_pokedata(child, addr, data);
                break;
 
        case PTRACE_POKEUSR: /* write the word at location addr in the USER area */
@@ -500,8 +449,11 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
                                  for(i=0; i<4; i++)
                                          if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1)
                                                  goto out_tsk;
+                                 if (data)
+                                         set_tsk_thread_flag(child, TIF_DEBUG);
+                                 else
+                                         clear_tsk_thread_flag(child, TIF_DEBUG);
                          }
-
                          addr -= (long) &dummy->u_debugreg;
                          addr = addr >> 2;
                          child->thread.debugreg[addr] = data;
@@ -661,10 +613,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
                ret = ptrace_request(child, request, addr, data);
                break;
        }
-out_tsk:
-       put_task_struct(child);
-out:
-       unlock_kernel();
+ out_tsk:
        return ret;
 }
 
@@ -692,15 +641,35 @@ void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs, int error_code)
 __attribute__((regparm(3)))
 int do_syscall_trace(struct pt_regs *regs, int entryexit)
 {
-       int is_sysemu = test_thread_flag(TIF_SYSCALL_EMU), ret = 0;
-       /* With TIF_SYSCALL_EMU set we want to ignore TIF_SINGLESTEP */
+       int is_sysemu = test_thread_flag(TIF_SYSCALL_EMU);
+       /*
+        * With TIF_SYSCALL_EMU set we want to ignore TIF_SINGLESTEP for syscall
+        * interception
+        */
        int is_singlestep = !is_sysemu && test_thread_flag(TIF_SINGLESTEP);
+       int ret = 0;
 
        /* do the secure computing check first */
-       secure_computing(regs->orig_eax);
-
-       if (unlikely(current->audit_context) && entryexit)
-               audit_syscall_exit(current, AUDITSC_RESULT(regs->eax), regs->eax);
+       if (!entryexit)
+               secure_computing(regs->orig_eax);
+
+       if (unlikely(current->audit_context)) {
+               if (entryexit)
+                       audit_syscall_exit(AUDITSC_RESULT(regs->eax),
+                                               regs->eax);
+               /* Debug traps, when using PTRACE_SINGLESTEP, must be sent only
+                * on the syscall exit path. Normally, when TIF_SYSCALL_AUDIT is
+                * not used, entry.S will call us only on syscall exit, not
+                * entry; so when TIF_SYSCALL_AUDIT is used we must avoid
+                * calling send_sigtrap() on syscall entry.
+                *
+                * Note that when PTRACE_SYSEMU_SINGLESTEP is used,
+                * is_singlestep is false, despite his name, so we will still do
+                * the correct thing.
+                */
+               else if (is_singlestep)
+                       goto out;
+       }
 
        if (!(current->ptrace & PT_PTRACED))
                goto out;
@@ -721,7 +690,7 @@ int do_syscall_trace(struct pt_regs *regs, int entryexit)
        /* the 0x80 provides a way for the tracing parent to distinguish
           between a syscall stop and SIGTRAP delivery */
        /* Note that the debugger could change the result of test_thread_flag!*/
-       ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) ? 0x80 : 0));
+       ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) ? 0x80:0));
 
        /*
         * this isn't the same as continuing with a signal, but it will do
@@ -733,15 +702,15 @@ int do_syscall_trace(struct pt_regs *regs, int entryexit)
                current->exit_code = 0;
        }
        ret = is_sysemu;
- out:
+out:
        if (unlikely(current->audit_context) && !entryexit)
-               audit_syscall_entry(current, AUDIT_ARCH_I386, regs->orig_eax,
+               audit_syscall_entry(AUDIT_ARCH_I386, regs->orig_eax,
                                    regs->ebx, regs->ecx, regs->edx, regs->esi);
        if (ret == 0)
                return 0;
 
        regs->orig_eax = -1; /* force skip of syscall restarting */
        if (unlikely(current->audit_context))
-               audit_syscall_exit(current, AUDITSC_RESULT(regs->eax), regs->eax);
+               audit_syscall_exit(AUDITSC_RESULT(regs->eax), regs->eax);
        return 1;
 }