From 43360686328b1f4b7d9dcc883ee9243ad7c16fae Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Fri, 7 Apr 2023 13:18:31 -0400 Subject: syscall_user_dispatch: Split up set_syscall_user_dispatch() syscall user dispatch configuration is not covered by checkpoint/restore. To prepare for ptrace access to the syscall user dispatch configuration, move the inner working of set_syscall_user_dispatch() into a helper function. Make the helper function task pointer based and let set_syscall_user_dispatch() invoke it with task=current. No functional change. Signed-off-by: Gregory Price Signed-off-by: Thomas Gleixner Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20230407171834.3558-2-gregory.price@memverge.com --- kernel/entry/syscall_user_dispatch.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'kernel/entry/syscall_user_dispatch.c') diff --git a/kernel/entry/syscall_user_dispatch.c b/kernel/entry/syscall_user_dispatch.c index 0b6379adff6b..22396b234854 100644 --- a/kernel/entry/syscall_user_dispatch.c +++ b/kernel/entry/syscall_user_dispatch.c @@ -68,8 +68,9 @@ bool syscall_user_dispatch(struct pt_regs *regs) return true; } -int set_syscall_user_dispatch(unsigned long mode, unsigned long offset, - unsigned long len, char __user *selector) +static int task_set_syscall_user_dispatch(struct task_struct *task, unsigned long mode, + unsigned long offset, unsigned long len, + char __user *selector) { switch (mode) { case PR_SYS_DISPATCH_OFF: @@ -94,15 +95,21 @@ int set_syscall_user_dispatch(unsigned long mode, unsigned long offset, return -EINVAL; } - current->syscall_dispatch.selector = selector; - current->syscall_dispatch.offset = offset; - current->syscall_dispatch.len = len; - current->syscall_dispatch.on_dispatch = false; + task->syscall_dispatch.selector = selector; + task->syscall_dispatch.offset = offset; + task->syscall_dispatch.len = len; + task->syscall_dispatch.on_dispatch = false; if (mode == PR_SYS_DISPATCH_ON) - set_syscall_work(SYSCALL_USER_DISPATCH); + set_task_syscall_work(task, SYSCALL_USER_DISPATCH); else - clear_syscall_work(SYSCALL_USER_DISPATCH); + clear_task_syscall_work(task, SYSCALL_USER_DISPATCH); return 0; } + +int set_syscall_user_dispatch(unsigned long mode, unsigned long offset, + unsigned long len, char __user *selector) +{ + return task_set_syscall_user_dispatch(current, mode, offset, len, selector); +} -- cgit From 463b7715e7ce367fce89769c5d85e31595715ee1 Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Fri, 7 Apr 2023 13:18:32 -0400 Subject: syscall_user_dispatch: Untag selector address before access_ok() To support checkpoint/restart, ptrace must be able to set the selector of the tracee. The selector is a user pointer that may be subject to memory tagging extensions on some architectures (namely ARM MTE). access_ok() clears memory tags for tagged addresses if the current task has memory tagging enabled. This obviously fails when ptrace modifies the selector of a tracee when tracer and tracee do not have the same memory tagging enabled state. Solve this by untagging the selector address before handing it to access_ok(), like other ptrace functions which modify tracee pointers do. Obviously a tracer can set an invalid selector address for the tracee, but that's independent of tagging and a general capability of the tracer. Suggested-by: Catalin Marinas Signed-off-by: Gregory Price Signed-off-by: Thomas Gleixner Reviewed-by: Catalin Marinas Acked-by: Oleg Nesterov Link: https://lore.kernel.org/all/ZCWXE04nLZ4pXEtM@arm.com/ Link: https://lore.kernel.org/r/20230407171834.3558-3-gregory.price@memverge.com --- kernel/entry/syscall_user_dispatch.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'kernel/entry/syscall_user_dispatch.c') diff --git a/kernel/entry/syscall_user_dispatch.c b/kernel/entry/syscall_user_dispatch.c index 22396b234854..7f2add43672d 100644 --- a/kernel/entry/syscall_user_dispatch.c +++ b/kernel/entry/syscall_user_dispatch.c @@ -87,7 +87,16 @@ static int task_set_syscall_user_dispatch(struct task_struct *task, unsigned lon if (offset && offset + len <= offset) return -EINVAL; - if (selector && !access_ok(selector, sizeof(*selector))) + /* + * access_ok() will clear memory tags for tagged addresses + * if current has memory tagging enabled. + + * To enable a tracer to set a tracees selector the + * selector address must be untagged for access_ok(), + * otherwise an untagged tracer will always fail to set a + * tagged tracees selector. + */ + if (selector && !access_ok(untagged_addr(selector), sizeof(*selector))) return -EFAULT; break; -- cgit From 3f67987cdc09778e75098f9f5168832f8f8e1f1c Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Fri, 7 Apr 2023 13:18:33 -0400 Subject: ptrace: Provide set/get interface for syscall user dispatch 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 Signed-off-by: Thomas Gleixner Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20230407171834.3558-4-gregory.price@memverge.com --- kernel/entry/syscall_user_dispatch.c | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'kernel/entry/syscall_user_dispatch.c') diff --git a/kernel/entry/syscall_user_dispatch.c b/kernel/entry/syscall_user_dispatch.c index 7f2add43672d..5340c5aa89e7 100644 --- a/kernel/entry/syscall_user_dispatch.c +++ b/kernel/entry/syscall_user_dispatch.c @@ -4,6 +4,7 @@ */ #include #include +#include #include #include #include @@ -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); +} -- cgit