upgrade to fedora-2.6.12-1.1398.FC4 + vserver 2.0.rc7
[linux-2.6.git] / arch / i386 / kernel / ptrace.c
index eaa6c8f..6bfb314 100644 (file)
@@ -15,6 +15,8 @@
 #include <linux/user.h>
 #include <linux/security.h>
 #include <linux/audit.h>
+#include <linux/seccomp.h>
+#include <linux/signal.h>
 
 #include <asm/uaccess.h>
 #include <asm/pgtable.h>
  */
 #define EFL_OFFSET ((EFL-2)*4-sizeof(struct pt_regs))
 
+static inline struct pt_regs *get_child_regs(struct task_struct *task)
+{
+       void *stack_top = (void *)task->thread.esp0;
+       return stack_top - sizeof(struct pt_regs);
+}
+
 /*
  * 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.  
@@ -138,24 +146,119 @@ static unsigned long getreg(struct task_struct *child,
        return retval;
 }
 
+#define LDT_SEGMENT 4
+
+static unsigned long convert_eip_to_linear(struct task_struct *child, struct pt_regs *regs)
+{
+       unsigned long addr, seg;
+
+       addr = regs->eip;
+       seg = regs->xcs & 0xffff;
+       if (regs->eflags & VM_MASK) {
+               addr = (addr & 0xffff) + (seg << 4);
+               return addr;
+       }
+
+       /*
+        * We'll assume that the code segments in the GDT
+        * are all zero-based. That is largely true: the
+        * TLS segments are used for data, and the PNPBIOS
+        * and APM bios ones we just ignore here.
+        */
+       if (seg & LDT_SEGMENT) {
+               u32 *desc;
+               unsigned long base;
+
+               down(&child->mm->context.sem);
+               desc = child->mm->context.ldt + (seg & ~7);
+               base = (desc[0] >> 16) | ((desc[1] & 0xff) << 16) | (desc[1] & 0xff000000);
+
+               /* 16-bit code segment? */
+               if (!((desc[1] >> 22) & 1))
+                       addr &= 0xffff;
+               addr += base;
+               up(&child->mm->context.sem);
+       }
+       return addr;
+}
+
+static inline int is_at_popf(struct task_struct *child, struct pt_regs *regs)
+{
+       int i, copied;
+       unsigned char opcode[16];
+       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:
+                       return 1;
+               /* opcode and address size prefixes */
+               case 0x66: case 0x67:
+                       continue;
+               /* irrelevant prefixes (segment overrides and repeats) */
+               case 0x26: case 0x2e:
+               case 0x36: case 0x3e:
+               case 0x64: case 0x65:
+               case 0xf0: case 0xf2: case 0xf3:
+                       continue;
+
+               /*
+                * pushf: NOTE! We should probably not let
+                * the user see the TF bit being set. But
+                * it's more pain than it's worth to avoid
+                * it, and a debugger could emulate this
+                * all in user space if it _really_ cares.
+                */
+               case 0x9c:
+               default:
+                       return 0;
+               }
+       }
+       return 0;
+}
+
 static void set_singlestep(struct task_struct *child)
 {
-       long eflags;
+       struct pt_regs *regs = get_child_regs(child);
 
+       /*
+        * Always set TIF_SINGLESTEP - this guarantees that 
+        * we single-step system calls etc..  This will also
+        * cause us to set TF when returning to user mode.
+        */
        set_tsk_thread_flag(child, TIF_SINGLESTEP);
-       eflags = get_stack_long(child, EFL_OFFSET);
-       put_stack_long(child, EFL_OFFSET, eflags | TRAP_FLAG);
+
+       /*
+        * If TF was already set, don't do anything else
+        */
+       if (regs->eflags & TRAP_FLAG)
+               return;
+
+       /* Set TF on the kernel stack.. */
+       regs->eflags |= TRAP_FLAG;
+
+       /*
+        * ..but if TF is changed by the instruction we will trace,
+        * 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))
+               return;
+       
        child->ptrace |= PT_DTRACE;
 }
 
 static void clear_singlestep(struct task_struct *child)
 {
-       if (child->ptrace & PT_DTRACE) {
-               long eflags;
+       /* Always clear TIF_SINGLESTEP... */
+       clear_tsk_thread_flag(child, TIF_SINGLESTEP);
 
-               clear_tsk_thread_flag(child, TIF_SINGLESTEP);
-               eflags = get_stack_long(child, EFL_OFFSET);
-               put_stack_long(child, EFL_OFFSET, eflags & ~TRAP_FLAG);
+       /* But touch TF only if it was set by us.. */
+       if (child->ptrace & PT_DTRACE) {
+               struct pt_regs *regs = get_child_regs(child);
+               regs->eflags &= ~TRAP_FLAG;
                child->ptrace &= ~PT_DTRACE;
        }
 }
@@ -411,7 +514,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
        case PTRACE_SYSCALL:    /* continue and stop at next (return from) syscall */
        case PTRACE_CONT:       /* restart after signal. */
                ret = -EIO;
-               if ((unsigned long) data > _NSIG)
+               if (!valid_signal(data))
                        break;
                if (request == PTRACE_SYSCALL) {
                        set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
@@ -443,7 +546,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
 
        case PTRACE_SINGLESTEP: /* set the trap flag. */
                ret = -EIO;
-               if ((unsigned long) data > _NSIG)
+               if (!valid_signal(data))
                        break;
                clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
                set_singlestep(child);
@@ -493,7 +596,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
                        break;
                }
                ret = 0;
-               if (!child->used_math)
+               if (!tsk_used_math(child))
                        init_fpu(child);
                get_fpregs((struct user_i387_struct __user *)data, child);
                break;
@@ -505,7 +608,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
                        ret = -EIO;
                        break;
                }
-               child->used_math = 1;
+               set_stopped_child_used_math(child);
                set_fpregs(child, (struct user_i387_struct __user *)data);
                ret = 0;
                break;
@@ -517,7 +620,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
                        ret = -EIO;
                        break;
                }
-               if (!child->used_math)
+               if (!tsk_used_math(child))
                        init_fpu(child);
                ret = get_fpxregs((struct user_fxsr_struct __user *)data, child);
                break;
@@ -529,7 +632,7 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
                        ret = -EIO;
                        break;
                }
-               child->used_math = 1;
+               set_stopped_child_used_math(child);
                ret = set_fpxregs(child, (struct user_fxsr_struct __user *)data);
                break;
        }
@@ -555,30 +658,49 @@ out:
        return ret;
 }
 
+void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs, int error_code)
+{
+       struct siginfo info;
+
+       tsk->thread.trap_no = 1;
+       tsk->thread.error_code = error_code;
+
+       memset(&info, 0, sizeof(info));
+       info.si_signo = SIGTRAP;
+       info.si_code = TRAP_BRKPT;
+
+       /* User-mode eip? */
+       info.si_addr = user_mode(regs) ? (void __user *) regs->eip : NULL;
+
+       /* Send us the fakey SIGTRAP */
+       force_sig_info(SIGTRAP, &info, tsk);
+}
+
 /* notification of system call entry/exit
  * - triggered by current->work.syscall_trace
  */
 __attribute__((regparm(3)))
 void do_syscall_trace(struct pt_regs *regs, int entryexit)
 {
-       if (unlikely(current->audit_context)) {
-               if (!entryexit)
-                       audit_syscall_entry(current, regs->orig_eax,
-                                           regs->ebx, regs->ecx,
-                                           regs->edx, regs->esi);
-               else
-                       audit_syscall_exit(current, regs->eax);
-       }
+       /* 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 (!test_thread_flag(TIF_SYSCALL_TRACE) &&
-           !test_thread_flag(TIF_SINGLESTEP))
-               return;
        if (!(current->ptrace & PT_PTRACED))
-               return;
+               goto out;
+
+       /* Fake a debug trap */
+       if (test_thread_flag(TIF_SINGLESTEP))
+               send_sigtrap(current, regs, 0);
+
+       if (!test_thread_flag(TIF_SYSCALL_TRACE))
+               goto out;
+
        /* the 0x80 provides a way for the tracing parent to distinguish
           between a syscall stop and SIGTRAP delivery */
-       ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) &&
-                                !test_thread_flag(TIF_SINGLESTEP) ? 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
@@ -589,4 +711,9 @@ void do_syscall_trace(struct pt_regs *regs, int entryexit)
                send_sig(current->exit_code, current, 1);
                current->exit_code = 0;
        }
+ out:
+       if (unlikely(current->audit_context) && !entryexit)
+               audit_syscall_entry(current, AUDIT_ARCH_I386, regs->orig_eax,
+                                   regs->ebx, regs->ecx, regs->edx, regs->esi);
+
 }