mm: move MAP_SYNC to asm-generic/mman-common.h
[linux-2.6-block.git] / kernel / ptrace.c
index 83a531cea2f3789fd42320e4bb5d03670be0f92a..cb9ddcc08119added2355311507c65db3849b0c4 100644 (file)
@@ -32,6 +32,8 @@
 #include <linux/compat.h>
 #include <linux/sched/signal.h>
 
+#include <asm/syscall.h>       /* for syscall_get_* */
+
 /*
  * Access another process' address space via ptrace.
  * Source/target buffer must be kernel space,
@@ -897,7 +899,100 @@ static int ptrace_regset(struct task_struct *task, int req, unsigned int type,
  * to ensure no machine forgets it.
  */
 EXPORT_SYMBOL_GPL(task_user_regset_view);
-#endif
+
+static unsigned long
+ptrace_get_syscall_info_entry(struct task_struct *child, struct pt_regs *regs,
+                             struct ptrace_syscall_info *info)
+{
+       unsigned long args[ARRAY_SIZE(info->entry.args)];
+       int i;
+
+       info->op = PTRACE_SYSCALL_INFO_ENTRY;
+       info->entry.nr = syscall_get_nr(child, regs);
+       syscall_get_arguments(child, regs, args);
+       for (i = 0; i < ARRAY_SIZE(args); i++)
+               info->entry.args[i] = args[i];
+
+       /* args is the last field in struct ptrace_syscall_info.entry */
+       return offsetofend(struct ptrace_syscall_info, entry.args);
+}
+
+static unsigned long
+ptrace_get_syscall_info_seccomp(struct task_struct *child, struct pt_regs *regs,
+                               struct ptrace_syscall_info *info)
+{
+       /*
+        * As struct ptrace_syscall_info.entry is currently a subset
+        * of struct ptrace_syscall_info.seccomp, it makes sense to
+        * initialize that subset using ptrace_get_syscall_info_entry().
+        * This can be reconsidered in the future if these structures
+        * diverge significantly enough.
+        */
+       ptrace_get_syscall_info_entry(child, regs, info);
+       info->op = PTRACE_SYSCALL_INFO_SECCOMP;
+       info->seccomp.ret_data = child->ptrace_message;
+
+       /* ret_data is the last field in struct ptrace_syscall_info.seccomp */
+       return offsetofend(struct ptrace_syscall_info, seccomp.ret_data);
+}
+
+static unsigned long
+ptrace_get_syscall_info_exit(struct task_struct *child, struct pt_regs *regs,
+                            struct ptrace_syscall_info *info)
+{
+       info->op = PTRACE_SYSCALL_INFO_EXIT;
+       info->exit.rval = syscall_get_error(child, regs);
+       info->exit.is_error = !!info->exit.rval;
+       if (!info->exit.is_error)
+               info->exit.rval = syscall_get_return_value(child, regs);
+
+       /* is_error is the last field in struct ptrace_syscall_info.exit */
+       return offsetofend(struct ptrace_syscall_info, exit.is_error);
+}
+
+static int
+ptrace_get_syscall_info(struct task_struct *child, unsigned long user_size,
+                       void __user *datavp)
+{
+       struct pt_regs *regs = task_pt_regs(child);
+       struct ptrace_syscall_info info = {
+               .op = PTRACE_SYSCALL_INFO_NONE,
+               .arch = syscall_get_arch(child),
+               .instruction_pointer = instruction_pointer(regs),
+               .stack_pointer = user_stack_pointer(regs),
+       };
+       unsigned long actual_size = offsetof(struct ptrace_syscall_info, entry);
+       unsigned long write_size;
+
+       /*
+        * This does not need lock_task_sighand() to access
+        * child->last_siginfo because ptrace_freeze_traced()
+        * called earlier by ptrace_check_attach() ensures that
+        * the tracee cannot go away and clear its last_siginfo.
+        */
+       switch (child->last_siginfo ? child->last_siginfo->si_code : 0) {
+       case SIGTRAP | 0x80:
+               switch (child->ptrace_message) {
+               case PTRACE_EVENTMSG_SYSCALL_ENTRY:
+                       actual_size = ptrace_get_syscall_info_entry(child, regs,
+                                                                   &info);
+                       break;
+               case PTRACE_EVENTMSG_SYSCALL_EXIT:
+                       actual_size = ptrace_get_syscall_info_exit(child, regs,
+                                                                  &info);
+                       break;
+               }
+               break;
+       case SIGTRAP | (PTRACE_EVENT_SECCOMP << 8):
+               actual_size = ptrace_get_syscall_info_seccomp(child, regs,
+                                                             &info);
+               break;
+       }
+
+       write_size = min(actual_size, user_size);
+       return copy_to_user(datavp, &info, write_size) ? -EFAULT : actual_size;
+}
+#endif /* CONFIG_HAVE_ARCH_TRACEHOOK */
 
 int ptrace_request(struct task_struct *child, long request,
                   unsigned long addr, unsigned long data)
@@ -1114,6 +1209,10 @@ int ptrace_request(struct task_struct *child, long request,
                        ret = __put_user(kiov.iov_len, &uiov->iov_len);
                break;
        }
+
+       case PTRACE_GET_SYSCALL_INFO:
+               ret = ptrace_get_syscall_info(child, addr, datavp);
+               break;
 #endif
 
        case PTRACE_SECCOMP_GET_FILTER: