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
7474 };
7575
7676 #define PTRACE_GET_SYSCALL_INFO 0x420e
77+#define PTRACE_SET_SYSCALL_INFO 0x4212
7778 #define PTRACE_SYSCALL_INFO_NONE 0
7879 #define PTRACE_SYSCALL_INFO_ENTRY 1
7980 #define PTRACE_SYSCALL_INFO_EXIT 2
@@ -81,7 +82,8 @@ struct seccomp_metadata { · 1 unmodified line
8182
8283 struct ptrace_syscall_info {
8384 __u8 op; /* PTRACE_SYSCALL_INFO_* */
84− __u8 pad[3];
85+ __u8 reserved;
86+ __u16 flags;
8587 __u32 arch;
8688 __u64 instruction_pointer;
8789 __u64 stack_pointer;
@@ -98,6 +100,7 @@ struct ptrace_syscall_info { · 10 unmodified lines
98100 __u64 nr;
99101 __u64 args[6];
100102 __u32 ret_data;
103+ __u32 reserved2;
101104 } seccomp;
102105 };
103106 };
@@ -142,6 +145,8 @@ struct ptrace_sud_config { · 38 unmodified lines
142145 __u64 len;
143146 };
144147
148+/* 0x4212 is PTRACE_SET_SYSCALL_INFO */
149+
145150 /*
146151 * These values are stored in task->ptrace_message
147152 * 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
944944 ptrace_get_syscall_info_entry(child, regs, info);
945945 info->seccomp.ret_data = child->ptrace_message;
946946
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+ */
948951 return offsetofend(struct ptrace_syscall_info, seccomp.ret_data);
949952 }
950953
@@ -1016,6 +1019,118 @@ ptrace_get_syscall_info(struct task_struct *child, unsigned long user_size, · 65 unmodified lines
10161019 write_size = min(actual_size, user_size);
10171020 return copy_to_user(datavp, &info, write_size) ? -EFAULT : actual_size;
10181021 }
1022+
1023+static int
1024+ptrace_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+
1063+static int
1064+ptrace_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+
1074+static int
1075+ptrace_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+
1096+static int
1097+ptrace_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+}
10191134 #endif /* CONFIG_HAVE_ARCH_TRACEHOOK */
10201135
10211136 int ptrace_request(struct task_struct *child, long request,
@@ -1234,6 +1349,10 @@ int ptrace_request(struct task_struct *child, long request, · 212 unmodified lines
12341349 case PTRACE_GET_SYSCALL_INFO:
12351350 ret = ptrace_get_syscall_info(child, addr, datavp);
12361351 break;
1352+
1353+ case PTRACE_SET_SYSCALL_INFO:
1354+ ret = ptrace_set_syscall_info(child, addr, datavp);
1355+ break;
12371356 #endif
12381357
12391358 case PTRACE_SECCOMP_GET_FILTER: