summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorElvira Khabirova <lineprinter@altlinux.org>2019-07-16 19:29:42 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2019-07-16 22:23:24 -0400
commit201766a20e30f982ccfe36bebfad9602c3ff574a (patch)
tree4312a07e1a359c84fb5e97150fcd1e73577ee5be
parentf296f1df6e0e5b17654709c05b1821a1b58d329f (diff)
ptrace: add PTRACE_GET_SYSCALL_INFO request
PTRACE_GET_SYSCALL_INFO is a generic ptrace API that lets ptracer obtain details of the syscall the tracee is blocked in. There are two reasons for a special syscall-related ptrace request. Firstly, with the current ptrace API there are cases when ptracer cannot retrieve necessary information about syscalls. Some examples include: * The notorious int-0x80-from-64-bit-task issue. See [1] for details. In short, if a 64-bit task performs a syscall through int 0x80, its tracer has no reliable means to find out that the syscall was, in fact, a compat syscall, and misidentifies it. * Syscall-enter-stop and syscall-exit-stop look the same for the tracer. Common practice is to keep track of the sequence of ptrace-stops in order not to mix the two syscall-stops up. But it is not as simple as it looks; for example, strace had a (just recently fixed) long-standing bug where attaching strace to a tracee that is performing the execve system call led to the tracer identifying the following syscall-exit-stop as syscall-enter-stop, which messed up all the state tracking. * Since the introduction of commit 84d77d3f06e7 ("ptrace: Don't allow accessing an undumpable mm"), both PTRACE_PEEKDATA and process_vm_readv become unavailable when the process dumpable flag is cleared. On such architectures as ia64 this results in all syscall arguments being unavailable for the tracer. Secondly, ptracers also have to support a lot of arch-specific code for obtaining information about the tracee. For some architectures, this requires a ptrace(PTRACE_PEEKUSER, ...) invocation for every syscall argument and return value. ptrace(2) man page: long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); ... PTRACE_GET_SYSCALL_INFO Retrieve information about the syscall that caused the stop. The information is placed into the buffer pointed by "data" argument, which should be a pointer to a buffer of type "struct ptrace_syscall_info". The "addr" argument contains the size of the buffer pointed to by "data" argument (i.e., sizeof(struct ptrace_syscall_info)). The return value contains the number of bytes available to be written by the kernel. If the size of data to be written by the kernel exceeds the size specified by "addr" argument, the output is truncated. [ldv@altlinux.org: selftests/seccomp/seccomp_bpf: update for PTRACE_GET_SYSCALL_INFO] Link: http://lkml.kernel.org/r/20190708182904.GA12332@altlinux.org Link: http://lkml.kernel.org/r/20190510152842.GF28558@altlinux.org Signed-off-by: Elvira Khabirova <lineprinter@altlinux.org> Co-developed-by: Dmitry V. Levin <ldv@altlinux.org> Signed-off-by: Dmitry V. Levin <ldv@altlinux.org> Reviewed-by: Oleg Nesterov <oleg@redhat.com> Reviewed-by: Kees Cook <keescook@chromium.org> Reviewed-by: Andy Lutomirski <luto@kernel.org> Cc: Eugene Syromyatnikov <esyr@redhat.com> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Greentime Hu <greentime@andestech.com> Cc: Helge Deller <deller@gmx.de> [parisc] Cc: James E.J. Bottomley <jejb@parisc-linux.org> Cc: James Hogan <jhogan@kernel.org> Cc: kbuild test robot <lkp@intel.com> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Paul Burton <paul.burton@mips.com> Cc: Paul Mackerras <paulus@samba.org> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: Richard Kuo <rkuo@codeaurora.org> Cc: Shuah Khan <shuah@kernel.org> Cc: Vincent Chen <deanbo422@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--include/linux/tracehook.h9
-rw-r--r--include/uapi/linux/ptrace.h35
-rw-r--r--kernel/ptrace.c101
-rw-r--r--tools/testing/selftests/seccomp/seccomp_bpf.c13
4 files changed, 150 insertions, 8 deletions
diff --git a/include/linux/tracehook.h b/include/linux/tracehook.h
index 8446573cc682..36fb3bbed6b2 100644
--- a/include/linux/tracehook.h
+++ b/include/linux/tracehook.h
@@ -54,13 +54,15 @@ struct linux_binprm;
54/* 54/*
55 * ptrace report for syscall entry and exit looks identical. 55 * ptrace report for syscall entry and exit looks identical.
56 */ 56 */
57static inline int ptrace_report_syscall(struct pt_regs *regs) 57static inline int ptrace_report_syscall(struct pt_regs *regs,
58 unsigned long message)
58{ 59{
59 int ptrace = current->ptrace; 60 int ptrace = current->ptrace;
60 61
61 if (!(ptrace & PT_PTRACED)) 62 if (!(ptrace & PT_PTRACED))
62 return 0; 63 return 0;
63 64
65 current->ptrace_message = message;
64 ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0)); 66 ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0));
65 67
66 /* 68 /*
@@ -73,6 +75,7 @@ static inline int ptrace_report_syscall(struct pt_regs *regs)
73 current->exit_code = 0; 75 current->exit_code = 0;
74 } 76 }
75 77
78 current->ptrace_message = 0;
76 return fatal_signal_pending(current); 79 return fatal_signal_pending(current);
77} 80}
78 81
@@ -98,7 +101,7 @@ static inline int ptrace_report_syscall(struct pt_regs *regs)
98static inline __must_check int tracehook_report_syscall_entry( 101static inline __must_check int tracehook_report_syscall_entry(
99 struct pt_regs *regs) 102 struct pt_regs *regs)
100{ 103{
101 return ptrace_report_syscall(regs); 104 return ptrace_report_syscall(regs, PTRACE_EVENTMSG_SYSCALL_ENTRY);
102} 105}
103 106
104/** 107/**
@@ -123,7 +126,7 @@ static inline void tracehook_report_syscall_exit(struct pt_regs *regs, int step)
123 if (step) 126 if (step)
124 user_single_step_report(regs); 127 user_single_step_report(regs);
125 else 128 else
126 ptrace_report_syscall(regs); 129 ptrace_report_syscall(regs, PTRACE_EVENTMSG_SYSCALL_EXIT);
127} 130}
128 131
129/** 132/**
diff --git a/include/uapi/linux/ptrace.h b/include/uapi/linux/ptrace.h
index d5a1b8a492b9..a71b6e3b03eb 100644
--- a/include/uapi/linux/ptrace.h
+++ b/include/uapi/linux/ptrace.h
@@ -73,6 +73,41 @@ struct seccomp_metadata {
73 __u64 flags; /* Output: filter's flags */ 73 __u64 flags; /* Output: filter's flags */
74}; 74};
75 75
76#define PTRACE_GET_SYSCALL_INFO 0x420e
77#define PTRACE_SYSCALL_INFO_NONE 0
78#define PTRACE_SYSCALL_INFO_ENTRY 1
79#define PTRACE_SYSCALL_INFO_EXIT 2
80#define PTRACE_SYSCALL_INFO_SECCOMP 3
81
82struct ptrace_syscall_info {
83 __u8 op; /* PTRACE_SYSCALL_INFO_* */
84 __u32 arch __attribute__((__aligned__(sizeof(__u32))));
85 __u64 instruction_pointer;
86 __u64 stack_pointer;
87 union {
88 struct {
89 __u64 nr;
90 __u64 args[6];
91 } entry;
92 struct {
93 __s64 rval;
94 __u8 is_error;
95 } exit;
96 struct {
97 __u64 nr;
98 __u64 args[6];
99 __u32 ret_data;
100 } seccomp;
101 };
102};
103
104/*
105 * These values are stored in task->ptrace_message
106 * by tracehook_report_syscall_* to describe the current syscall-stop.
107 */
108#define PTRACE_EVENTMSG_SYSCALL_ENTRY 1
109#define PTRACE_EVENTMSG_SYSCALL_EXIT 2
110
76/* Read signals from a shared (process wide) queue */ 111/* Read signals from a shared (process wide) queue */
77#define PTRACE_PEEKSIGINFO_SHARED (1 << 0) 112#define PTRACE_PEEKSIGINFO_SHARED (1 << 0)
78 113
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 83a531cea2f3..cb9ddcc08119 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -32,6 +32,8 @@
32#include <linux/compat.h> 32#include <linux/compat.h>
33#include <linux/sched/signal.h> 33#include <linux/sched/signal.h>
34 34
35#include <asm/syscall.h> /* for syscall_get_* */
36
35/* 37/*
36 * Access another process' address space via ptrace. 38 * Access another process' address space via ptrace.
37 * Source/target buffer must be kernel space, 39 * Source/target buffer must be kernel space,
@@ -897,7 +899,100 @@ static int ptrace_regset(struct task_struct *task, int req, unsigned int type,
897 * to ensure no machine forgets it. 899 * to ensure no machine forgets it.
898 */ 900 */
899EXPORT_SYMBOL_GPL(task_user_regset_view); 901EXPORT_SYMBOL_GPL(task_user_regset_view);
900#endif 902
903static unsigned long
904ptrace_get_syscall_info_entry(struct task_struct *child, struct pt_regs *regs,
905 struct ptrace_syscall_info *info)
906{
907 unsigned long args[ARRAY_SIZE(info->entry.args)];
908 int i;
909
910 info->op = PTRACE_SYSCALL_INFO_ENTRY;
911 info->entry.nr = syscall_get_nr(child, regs);
912 syscall_get_arguments(child, regs, args);
913 for (i = 0; i < ARRAY_SIZE(args); i++)
914 info->entry.args[i] = args[i];
915
916 /* args is the last field in struct ptrace_syscall_info.entry */
917 return offsetofend(struct ptrace_syscall_info, entry.args);
918}
919
920static unsigned long
921ptrace_get_syscall_info_seccomp(struct task_struct *child, struct pt_regs *regs,
922 struct ptrace_syscall_info *info)
923{
924 /*
925 * As struct ptrace_syscall_info.entry is currently a subset
926 * of struct ptrace_syscall_info.seccomp, it makes sense to
927 * initialize that subset using ptrace_get_syscall_info_entry().
928 * This can be reconsidered in the future if these structures
929 * diverge significantly enough.
930 */
931 ptrace_get_syscall_info_entry(child, regs, info);
932 info->op = PTRACE_SYSCALL_INFO_SECCOMP;
933 info->seccomp.ret_data = child->ptrace_message;
934
935 /* ret_data is the last field in struct ptrace_syscall_info.seccomp */
936 return offsetofend(struct ptrace_syscall_info, seccomp.ret_data);
937}
938
939static unsigned long
940ptrace_get_syscall_info_exit(struct task_struct *child, struct pt_regs *regs,
941 struct ptrace_syscall_info *info)
942{
943 info->op = PTRACE_SYSCALL_INFO_EXIT;
944 info->exit.rval = syscall_get_error(child, regs);
945 info->exit.is_error = !!info->exit.rval;
946 if (!info->exit.is_error)
947 info->exit.rval = syscall_get_return_value(child, regs);
948
949 /* is_error is the last field in struct ptrace_syscall_info.exit */
950 return offsetofend(struct ptrace_syscall_info, exit.is_error);
951}
952
953static int
954ptrace_get_syscall_info(struct task_struct *child, unsigned long user_size,
955 void __user *datavp)
956{
957 struct pt_regs *regs = task_pt_regs(child);
958 struct ptrace_syscall_info info = {
959 .op = PTRACE_SYSCALL_INFO_NONE,
960 .arch = syscall_get_arch(child),
961 .instruction_pointer = instruction_pointer(regs),
962 .stack_pointer = user_stack_pointer(regs),
963 };
964 unsigned long actual_size = offsetof(struct ptrace_syscall_info, entry);
965 unsigned long write_size;
966
967 /*
968 * This does not need lock_task_sighand() to access
969 * child->last_siginfo because ptrace_freeze_traced()
970 * called earlier by ptrace_check_attach() ensures that
971 * the tracee cannot go away and clear its last_siginfo.
972 */
973 switch (child->last_siginfo ? child->last_siginfo->si_code : 0) {
974 case SIGTRAP | 0x80:
975 switch (child->ptrace_message) {
976 case PTRACE_EVENTMSG_SYSCALL_ENTRY:
977 actual_size = ptrace_get_syscall_info_entry(child, regs,
978 &info);
979 break;
980 case PTRACE_EVENTMSG_SYSCALL_EXIT:
981 actual_size = ptrace_get_syscall_info_exit(child, regs,
982 &info);
983 break;
984 }
985 break;
986 case SIGTRAP | (PTRACE_EVENT_SECCOMP << 8):
987 actual_size = ptrace_get_syscall_info_seccomp(child, regs,
988 &info);
989 break;
990 }
991
992 write_size = min(actual_size, user_size);
993 return copy_to_user(datavp, &info, write_size) ? -EFAULT : actual_size;
994}
995#endif /* CONFIG_HAVE_ARCH_TRACEHOOK */
901 996
902int ptrace_request(struct task_struct *child, long request, 997int ptrace_request(struct task_struct *child, long request,
903 unsigned long addr, unsigned long data) 998 unsigned long addr, unsigned long data)
@@ -1114,6 +1209,10 @@ int ptrace_request(struct task_struct *child, long request,
1114 ret = __put_user(kiov.iov_len, &uiov->iov_len); 1209 ret = __put_user(kiov.iov_len, &uiov->iov_len);
1115 break; 1210 break;
1116 } 1211 }
1212
1213 case PTRACE_GET_SYSCALL_INFO:
1214 ret = ptrace_get_syscall_info(child, addr, datavp);
1215 break;
1117#endif 1216#endif
1118 1217
1119 case PTRACE_SECCOMP_GET_FILTER: 1218 case PTRACE_SECCOMP_GET_FILTER:
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index dc66fe852768..6ef7f16c4cf5 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -1775,13 +1775,18 @@ void tracer_ptrace(struct __test_metadata *_metadata, pid_t tracee,
1775 unsigned long msg; 1775 unsigned long msg;
1776 static bool entry; 1776 static bool entry;
1777 1777
1778 /* Make sure we got an empty message. */ 1778 /*
1779 * The traditional way to tell PTRACE_SYSCALL entry/exit
1780 * is by counting.
1781 */
1782 entry = !entry;
1783
1784 /* Make sure we got an appropriate message. */
1779 ret = ptrace(PTRACE_GETEVENTMSG, tracee, NULL, &msg); 1785 ret = ptrace(PTRACE_GETEVENTMSG, tracee, NULL, &msg);
1780 EXPECT_EQ(0, ret); 1786 EXPECT_EQ(0, ret);
1781 EXPECT_EQ(0, msg); 1787 EXPECT_EQ(entry ? PTRACE_EVENTMSG_SYSCALL_ENTRY
1788 : PTRACE_EVENTMSG_SYSCALL_EXIT, msg);
1782 1789
1783 /* The only way to tell PTRACE_SYSCALL entry/exit is by counting. */
1784 entry = !entry;
1785 if (!entry) 1790 if (!entry)
1786 return; 1791 return;
1787 1792