ptrace: Provide set/get interface for syscall user dispatch
authorGregory Price <gourry.memverge@gmail.com>
Fri, 7 Apr 2023 17:18:33 +0000 (13:18 -0400)
committerThomas Gleixner <tglx@linutronix.de>
Sun, 16 Apr 2023 12:23:07 +0000 (14:23 +0200)
The syscall user dispatch configuration can only be set by the task itself,
but lacks a ptrace set/get interface which makes it impossible to implement
checkpoint/restore for it.

Add the required ptrace requests and the get/set functions in the syscall
user dispatch code to make that possible.

Signed-off-by: Gregory Price <gregory.price@memverge.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Link: https://lore.kernel.org/r/20230407171834.3558-4-gregory.price@memverge.com
Documentation/admin-guide/syscall-user-dispatch.rst
include/linux/syscall_user_dispatch.h
include/uapi/linux/ptrace.h
kernel/entry/syscall_user_dispatch.c
kernel/ptrace.c

index 6031495..e3cfffe 100644 (file)
@@ -73,6 +73,10 @@ thread-wide, without the need to invoke the kernel directly.  selector
 can be set to SYSCALL_DISPATCH_FILTER_ALLOW or SYSCALL_DISPATCH_FILTER_BLOCK.
 Any other value should terminate the program with a SIGSYS.
 
+Additionally, a tasks syscall user dispatch configuration can be peeked
+and poked via the PTRACE_(GET|SET)_SYSCALL_USER_DISPATCH_CONFIG ptrace
+requests. This is useful for checkpoint/restart software.
+
 Security Notes
 --------------
 
index a0ae443..641ca88 100644 (file)
@@ -22,6 +22,12 @@ int set_syscall_user_dispatch(unsigned long mode, unsigned long offset,
 #define clear_syscall_work_syscall_user_dispatch(tsk) \
        clear_task_syscall_work(tsk, SYSCALL_USER_DISPATCH)
 
+int syscall_user_dispatch_get_config(struct task_struct *task, unsigned long size,
+                                    void __user *data);
+
+int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long size,
+                                    void __user *data);
+
 #else
 struct syscall_user_dispatch {};
 
@@ -35,6 +41,18 @@ static inline void clear_syscall_work_syscall_user_dispatch(struct task_struct *
 {
 }
 
+static inline int syscall_user_dispatch_get_config(struct task_struct *task,
+                                                  unsigned long size, void __user *data)
+{
+       return -EINVAL;
+}
+
+static inline int syscall_user_dispatch_set_config(struct task_struct *task,
+                                                  unsigned long size, void __user *data)
+{
+       return -EINVAL;
+}
+
 #endif /* CONFIG_GENERIC_ENTRY */
 
 #endif /* _SYSCALL_USER_DISPATCH_H */
index 195ae64..72c038f 100644 (file)
@@ -112,6 +112,36 @@ struct ptrace_rseq_configuration {
        __u32 pad;
 };
 
+#define PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG 0x4210
+#define PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG 0x4211
+
+/*
+ * struct ptrace_sud_config - Per-task configuration for Syscall User Dispatch
+ * @mode:      One of PR_SYS_DISPATCH_ON or PR_SYS_DISPATCH_OFF
+ * @selector:  Tracees user virtual address of SUD selector
+ * @offset:    SUD exclusion area (virtual address)
+ * @len:       Length of SUD exclusion area
+ *
+ * Used to get/set the syscall user dispatch configuration for a tracee.
+ * Selector is optional (may be NULL), and if invalid will produce
+ * a SIGSEGV in the tracee upon first access.
+ *
+ * If mode is PR_SYS_DISPATCH_ON, syscall dispatch will be enabled. If
+ * PR_SYS_DISPATCH_OFF, syscall dispatch will be disabled and all other
+ * parameters must be 0.  The value in *selector (if not null), also determines
+ * whether syscall dispatch will occur.
+ *
+ * The Syscall User Dispatch Exclusion area described by offset/len is the
+ * virtual address space from which syscalls will not produce a user
+ * dispatch.
+ */
+struct ptrace_sud_config {
+       __u64 mode;
+       __u64 selector;
+       __u64 offset;
+       __u64 len;
+};
+
 /*
  * These values are stored in task->ptrace_message
  * by ptrace_stop to describe the current syscall-stop.
index 7f2add4..5340c5a 100644 (file)
@@ -4,6 +4,7 @@
  */
 #include <linux/sched.h>
 #include <linux/prctl.h>
+#include <linux/ptrace.h>
 #include <linux/syscall_user_dispatch.h>
 #include <linux/uaccess.h>
 #include <linux/signal.h>
@@ -122,3 +123,42 @@ int set_syscall_user_dispatch(unsigned long mode, unsigned long offset,
 {
        return task_set_syscall_user_dispatch(current, mode, offset, len, selector);
 }
+
+int syscall_user_dispatch_get_config(struct task_struct *task, unsigned long size,
+                                    void __user *data)
+{
+       struct syscall_user_dispatch *sd = &task->syscall_dispatch;
+       struct ptrace_sud_config cfg;
+
+       if (size != sizeof(cfg))
+               return -EINVAL;
+
+       if (test_task_syscall_work(task, SYSCALL_USER_DISPATCH))
+               cfg.mode = PR_SYS_DISPATCH_ON;
+       else
+               cfg.mode = PR_SYS_DISPATCH_OFF;
+
+       cfg.offset = sd->offset;
+       cfg.len = sd->len;
+       cfg.selector = (__u64)(uintptr_t)sd->selector;
+
+       if (copy_to_user(data, &cfg, sizeof(cfg)))
+               return -EFAULT;
+
+       return 0;
+}
+
+int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long size,
+                                    void __user *data)
+{
+       struct ptrace_sud_config cfg;
+
+       if (size != sizeof(cfg))
+               return -EINVAL;
+
+       if (copy_from_user(&cfg, data, sizeof(cfg)))
+               return -EFAULT;
+
+       return task_set_syscall_user_dispatch(task, cfg.mode, cfg.offset, cfg.len,
+                                             (char __user *)(uintptr_t)cfg.selector);
+}
index 0786450..443057b 100644 (file)
@@ -32,6 +32,7 @@
 #include <linux/compat.h>
 #include <linux/sched/signal.h>
 #include <linux/minmax.h>
+#include <linux/syscall_user_dispatch.h>
 
 #include <asm/syscall.h>       /* for syscall_get_* */
 
@@ -1259,6 +1260,14 @@ int ptrace_request(struct task_struct *child, long request,
                break;
 #endif
 
+       case PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG:
+               ret = syscall_user_dispatch_set_config(child, addr, datavp);
+               break;
+
+       case PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG:
+               ret = syscall_user_dispatch_get_config(child, addr, datavp);
+               break;
+
        default:
                break;
        }