|
32 | 32 | #include <linux/compat.h>
|
33 | 33 | #include <linux/sched/signal.h>
|
34 | 34 |
|
| 35 | +#include <asm/syscall.h> /* for syscall_get_* */ |
| 36 | + |
35 | 37 | /*
|
36 | 38 | * Access another process' address space via ptrace.
|
37 | 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 | 899 | * to ensure no machine forgets it.
|
898 | 900 | */
|
899 | 901 | EXPORT_SYMBOL_GPL(task_user_regset_view);
|
900 |
| -#endif |
| 902 | + |
| 903 | +static unsigned long |
| 904 | +ptrace_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 | + |
| 920 | +static unsigned long |
| 921 | +ptrace_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 | + |
| 939 | +static unsigned long |
| 940 | +ptrace_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 | + |
| 953 | +static int |
| 954 | +ptrace_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 |
|
902 | 997 | int ptrace_request(struct task_struct *child, long request,
|
903 | 998 | unsigned long addr, unsigned long data)
|
@@ -1114,6 +1209,10 @@ int ptrace_request(struct task_struct *child, long request,
|
1114 | 1209 | ret = __put_user(kiov.iov_len, &uiov->iov_len);
|
1115 | 1210 | break;
|
1116 | 1211 | }
|
| 1212 | + |
| 1213 | + case PTRACE_GET_SYSCALL_INFO: |
| 1214 | + ret = ptrace_get_syscall_info(child, addr, datavp); |
| 1215 | + break; |
1117 | 1216 | #endif
|
1118 | 1217 |
|
1119 | 1218 | case PTRACE_SECCOMP_GET_FILTER:
|
|
0 commit comments