History

ptrace: introduce PTRACE_SET_SYSCALL_INFO request

PTRACE_SET_SYSCALL_INFO is a generic ptrace API that complements PTRACE_GET_SYSCALL_INFO by letting the ptracer modify details of system calls the tracee is blocked in. This API allows ptracers to obtain and modify system call details in a straightforward and architecture-agnostic way, providing a consistent way of manipulating the system call number and arguments across architectures. As in case of PTRACE_GET_SYSCALL_INFO, PTRACE_SET_SYSCALL_INFO also does not aim to address numerous architecture-specific system call ABI peculiarities, like differences in the number of system call arguments for such system calls as pread64 and preadv. The current implementation supports changing only those bits of system call information that are used by strace system call tampering, namely, syscall number, syscall arguments, and syscall return value. Support of changing additional details returned by PTRACE_GET_SYSCALL_INFO, such as instruction pointer and stack pointer, could be added later if needed, by using struct ptrace_syscall_info.flags to specify the additional details that should be set. Currently, "flags" and "reserved" fields of struct ptrace_syscall_info must be initialized with zeroes; "arch", "instruction_pointer", and "stack_pointer" fields are currently ignored. PTRACE_SET_SYSCALL_INFO currently supports only PTRACE_SYSCALL_INFO_ENTRY, PTRACE_SYSCALL_INFO_EXIT, and PTRACE_SYSCALL_INFO_SECCOMP operations. Other operations could be added later if needed. Ideally, PTRACE_SET_SYSCALL_INFO should have been introduced along with PTRACE_GET_SYSCALL_INFO, but it didn't happen. The last straw that convinced me to implement PTRACE_SET_SYSCALL_INFO was apparent failure to provide an API of changing the first system call argument on riscv architecture. ptrace(2) man page: long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); ... PTRACE_SET_SYSCALL_INFO Modify information about the system call that caused the stop. The "data" argument is a pointer to struct ptrace_syscall_info that specifies the system call information to be set. The "addr" argument should be set to sizeof(struct ptrace_syscall_info)).

Dmitry V. Levin committed 1 year ago

Commit 26bb3271 parent c354ec92 files · +126 −2Browse files

Unified · Split

2 files
  1. include/uapi/linux/ptrace.h +6 −1
  2. kernel/ptrace.c +120 −1
include/uapi/linux/ptrace.h+6−1
@@ -74,6 +74,7 @@ struct seccomp_metadata { · 73 unmodified lines
74};74};
75 75
76#define PTRACE_GET_SYSCALL_INFO 0x420e76#define PTRACE_GET_SYSCALL_INFO 0x420e
77#define PTRACE_SET_SYSCALL_INFO 0x4212
77#define PTRACE_SYSCALL_INFO_NONE 078#define PTRACE_SYSCALL_INFO_NONE 0
78#define PTRACE_SYSCALL_INFO_ENTRY 179#define PTRACE_SYSCALL_INFO_ENTRY 1
79#define PTRACE_SYSCALL_INFO_EXIT 280#define PTRACE_SYSCALL_INFO_EXIT 2
@@ -81,7 +82,8 @@ struct seccomp_metadata { · 1 unmodified line
81 82
82struct ptrace_syscall_info {83struct ptrace_syscall_info {
83 __u8 op; /* PTRACE_SYSCALL_INFO_* */84 __u8 op; /* PTRACE_SYSCALL_INFO_* */
84 __u8 pad[3];85 __u8 reserved;
86 __u16 flags;
85 __u32 arch;87 __u32 arch;
86 __u64 instruction_pointer;88 __u64 instruction_pointer;
87 __u64 stack_pointer;89 __u64 stack_pointer;
@@ -98,6 +100,7 @@ struct ptrace_syscall_info { · 10 unmodified lines
98 __u64 nr;100 __u64 nr;
99 __u64 args[6];101 __u64 args[6];
100 __u32 ret_data;102 __u32 ret_data;
103 __u32 reserved2;
101 } seccomp;104 } seccomp;
102 };105 };
103};106};
@@ -142,6 +145,8 @@ struct ptrace_sud_config { · 38 unmodified lines
142 __u64 len;145 __u64 len;
143};146};
144 147
148/* 0x4212 is PTRACE_SET_SYSCALL_INFO */
149
145/*150/*
146 * These values are stored in task->ptrace_message151 * These values are stored in task->ptrace_message
147 * by ptrace_stop to describe the current syscall-stop.152 * by ptrace_stop to describe the current syscall-stop.
kernel/ptrace.c+120−1
@@ -944,7 +944,10 @@ ptrace_get_syscall_info_seccomp(struct task_struct *child, struct pt_regs *regs, · 943 unmodified lines
944 ptrace_get_syscall_info_entry(child, regs, info);944 ptrace_get_syscall_info_entry(child, regs, info);
945 info->seccomp.ret_data = child->ptrace_message;945 info->seccomp.ret_data = child->ptrace_message;
946 946
947 /* ret_data is the last field in struct ptrace_syscall_info.seccomp */947 /*
948 * ret_data is the last non-reserved field
949 * in struct ptrace_syscall_info.seccomp
950 */
948 return offsetofend(struct ptrace_syscall_info, seccomp.ret_data);951 return offsetofend(struct ptrace_syscall_info, seccomp.ret_data);
949}952}
950 953
@@ -1016,6 +1019,118 @@ ptrace_get_syscall_info(struct task_struct *child, unsigned long user_size, · 65 unmodified lines
1016 write_size = min(actual_size, user_size);1019 write_size = min(actual_size, user_size);
1017 return copy_to_user(datavp, &info, write_size) ? -EFAULT : actual_size;1020 return copy_to_user(datavp, &info, write_size) ? -EFAULT : actual_size;
1018}1021}
1022
1023static int
1024ptrace_set_syscall_info_entry(struct task_struct *child, struct pt_regs *regs,
1025 struct ptrace_syscall_info *info)
1026{
1027 unsigned long args[ARRAY_SIZE(info->entry.args)];
1028 int nr = info->entry.nr;
1029 int i;
1030
1031 /*
1032 * Check that the syscall number specified in info->entry.nr
1033 * is either a value of type "int" or a sign-extended value
1034 * of type "int".
1035 */
1036 if (nr != info->entry.nr)
1037 return -ERANGE;
1038
1039 for (i = 0; i < ARRAY_SIZE(args); i++) {
1040 args[i] = info->entry.args[i];
1041 /*
1042 * Check that the syscall argument specified in
1043 * info->entry.args[i] is either a value of type
1044 * "unsigned long" or a sign-extended value of type "long".
1045 */
1046 if (args[i] != info->entry.args[i])
1047 return -ERANGE;
1048 }
1049
1050 syscall_set_nr(child, regs, nr);
1051 /*
1052 * If the syscall number is set to -1, setting syscall arguments is not
1053 * just pointless, it would also clobber the syscall return value on
1054 * those architectures that share the same register both for the first
1055 * argument of syscall and its return value.
1056 */
1057 if (nr != -1)
1058 syscall_set_arguments(child, regs, args);
1059
1060 return 0;
1061}
1062
1063static int
1064ptrace_set_syscall_info_seccomp(struct task_struct *child, struct pt_regs *regs,
1065 struct ptrace_syscall_info *info)
1066{
1067 /*
1068 * info->entry is currently a subset of info->seccomp,
1069 * info->seccomp.ret_data is currently ignored.
1070 */
1071 return ptrace_set_syscall_info_entry(child, regs, info);
1072}
1073
1074static int
1075ptrace_set_syscall_info_exit(struct task_struct *child, struct pt_regs *regs,
1076 struct ptrace_syscall_info *info)
1077{
1078 long rval = info->exit.rval;
1079
1080 /*
1081 * Check that the return value specified in info->exit.rval
1082 * is either a value of type "long" or a sign-extended value
1083 * of type "long".
1084 */
1085 if (rval != info->exit.rval)
1086 return -ERANGE;
1087
1088 if (info->exit.is_error)
1089 syscall_set_return_value(child, regs, rval, 0);
1090 else
1091 syscall_set_return_value(child, regs, 0, rval);
1092
1093 return 0;
1094}
1095
1096static int
1097ptrace_set_syscall_info(struct task_struct *child, unsigned long user_size,
1098 const void __user *datavp)
1099{
1100 struct pt_regs *regs = task_pt_regs(child);
1101 struct ptrace_syscall_info info;
1102
1103 if (user_size < sizeof(info))
1104 return -EINVAL;
1105
1106 /*
1107 * The compatibility is tracked by info.op and info.flags: if user-space
1108 * does not instruct us to use unknown extra bits from future versions
1109 * of ptrace_syscall_info, we are not going to read them either.
1110 */
1111 if (copy_from_user(&info, datavp, sizeof(info)))
1112 return -EFAULT;
1113
1114 /* Reserved for future use. */
1115 if (info.flags || info.reserved)
1116 return -EINVAL;
1117
1118 /* Changing the type of the system call stop is not supported yet. */
1119 if (ptrace_get_syscall_info_op(child) != info.op)
1120 return -EINVAL;
1121
1122 switch (info.op) {
1123 case PTRACE_SYSCALL_INFO_ENTRY:
1124 return ptrace_set_syscall_info_entry(child, regs, &info);
1125 case PTRACE_SYSCALL_INFO_EXIT:
1126 return ptrace_set_syscall_info_exit(child, regs, &info);
1127 case PTRACE_SYSCALL_INFO_SECCOMP:
1128 return ptrace_set_syscall_info_seccomp(child, regs, &info);
1129 default:
1130 /* Other types of system call stops are not supported yet. */
1131 return -EINVAL;
1132 }
1133}
1019#endif /* CONFIG_HAVE_ARCH_TRACEHOOK */1134#endif /* CONFIG_HAVE_ARCH_TRACEHOOK */
1020 1135
1021int ptrace_request(struct task_struct *child, long request,1136int ptrace_request(struct task_struct *child, long request,
@@ -1234,6 +1349,10 @@ int ptrace_request(struct task_struct *child, long request, · 212 unmodified lines
1234 case PTRACE_GET_SYSCALL_INFO:1349 case PTRACE_GET_SYSCALL_INFO:
1235 ret = ptrace_get_syscall_info(child, addr, datavp);1350 ret = ptrace_get_syscall_info(child, addr, datavp);
1236 break;1351 break;
1352
1353 case PTRACE_SET_SYSCALL_INFO:
1354 ret = ptrace_set_syscall_info(child, addr, datavp);
1355 break;
1237#endif1356#endif
1238 1357
1239 case PTRACE_SECCOMP_GET_FILTER:1358 case PTRACE_SECCOMP_GET_FILTER: