Commit | Line | Data |
---|---|---|
1446e1df GKB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2020 Collabora Ltd. | |
4 | */ | |
5 | #include <linux/sched.h> | |
6 | #include <linux/prctl.h> | |
7 | #include <linux/syscall_user_dispatch.h> | |
8 | #include <linux/uaccess.h> | |
9 | #include <linux/signal.h> | |
10 | #include <linux/elf.h> | |
11 | ||
12 | #include <linux/sched/signal.h> | |
13 | #include <linux/sched/task_stack.h> | |
14 | ||
15 | #include <asm/syscall.h> | |
16 | ||
17 | #include "common.h" | |
18 | ||
19 | static void trigger_sigsys(struct pt_regs *regs) | |
20 | { | |
21 | struct kernel_siginfo info; | |
22 | ||
23 | clear_siginfo(&info); | |
24 | info.si_signo = SIGSYS; | |
25 | info.si_code = SYS_USER_DISPATCH; | |
26 | info.si_call_addr = (void __user *)KSTK_EIP(current); | |
27 | info.si_errno = 0; | |
28 | info.si_arch = syscall_get_arch(current); | |
29 | info.si_syscall = syscall_get_nr(current, regs); | |
30 | ||
31 | force_sig_info(&info); | |
32 | } | |
33 | ||
34 | bool syscall_user_dispatch(struct pt_regs *regs) | |
35 | { | |
36 | struct syscall_user_dispatch *sd = ¤t->syscall_dispatch; | |
37 | char state; | |
38 | ||
39 | if (likely(instruction_pointer(regs) - sd->offset < sd->len)) | |
40 | return false; | |
41 | ||
42 | if (unlikely(arch_syscall_is_vdso_sigreturn(regs))) | |
43 | return false; | |
44 | ||
45 | if (likely(sd->selector)) { | |
46 | /* | |
47 | * access_ok() is performed once, at prctl time, when | |
48 | * the selector is loaded by userspace. | |
49 | */ | |
50 | if (unlikely(__get_user(state, sd->selector))) | |
51 | do_exit(SIGSEGV); | |
52 | ||
53 | if (likely(state == PR_SYS_DISPATCH_OFF)) | |
54 | return false; | |
55 | ||
56 | if (state != PR_SYS_DISPATCH_ON) | |
57 | do_exit(SIGSYS); | |
58 | } | |
59 | ||
60 | sd->on_dispatch = true; | |
61 | syscall_rollback(current, regs); | |
62 | trigger_sigsys(regs); | |
63 | ||
64 | return true; | |
65 | } | |
66 | ||
67 | int set_syscall_user_dispatch(unsigned long mode, unsigned long offset, | |
68 | unsigned long len, char __user *selector) | |
69 | { | |
70 | switch (mode) { | |
71 | case PR_SYS_DISPATCH_OFF: | |
72 | if (offset || len || selector) | |
73 | return -EINVAL; | |
74 | break; | |
75 | case PR_SYS_DISPATCH_ON: | |
76 | /* | |
77 | * Validate the direct dispatcher region just for basic | |
78 | * sanity against overflow and a 0-sized dispatcher | |
79 | * region. If the user is able to submit a syscall from | |
80 | * an address, that address is obviously valid. | |
81 | */ | |
82 | if (offset && offset + len <= offset) | |
83 | return -EINVAL; | |
84 | ||
85 | if (selector && !access_ok(selector, sizeof(*selector))) | |
86 | return -EFAULT; | |
87 | ||
88 | break; | |
89 | default: | |
90 | return -EINVAL; | |
91 | } | |
92 | ||
93 | current->syscall_dispatch.selector = selector; | |
94 | current->syscall_dispatch.offset = offset; | |
95 | current->syscall_dispatch.len = len; | |
96 | current->syscall_dispatch.on_dispatch = false; | |
97 | ||
98 | if (mode == PR_SYS_DISPATCH_ON) | |
99 | set_syscall_work(SYSCALL_USER_DISPATCH); | |
100 | else | |
101 | clear_syscall_work(SYSCALL_USER_DISPATCH); | |
102 | ||
103 | return 0; | |
104 | } |