1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * arch/arm64/kernel/probes/simulate-insn.c
4 *
5 * Copyright (C) 2013 Linaro Limited.
6 */
7
8 #include <linux/bitops.h>
9 #include <linux/kernel.h>
10 #include <linux/kprobes.h>
11
12 #include <asm/ptrace.h>
13 #include <asm/traps.h>
14
15 #include "simulate-insn.h"
16 #include "asm/gcs.h"
17
18 #define bbl_displacement(insn) \
19 sign_extend32(((insn) & 0x3ffffff) << 2, 27)
20
21 #define bcond_displacement(insn) \
22 sign_extend32(((insn >> 5) & 0x7ffff) << 2, 20)
23
24 #define cbz_displacement(insn) \
25 sign_extend32(((insn >> 5) & 0x7ffff) << 2, 20)
26
27 #define tbz_displacement(insn) \
28 sign_extend32(((insn >> 5) & 0x3fff) << 2, 15)
29
30 #define ldr_displacement(insn) \
31 sign_extend32(((insn >> 5) & 0x7ffff) << 2, 20)
32
set_x_reg(struct pt_regs * regs,int reg,u64 val)33 static inline void set_x_reg(struct pt_regs *regs, int reg, u64 val)
34 {
35 pt_regs_write_reg(regs, reg, val);
36 }
37
set_w_reg(struct pt_regs * regs,int reg,u64 val)38 static inline void set_w_reg(struct pt_regs *regs, int reg, u64 val)
39 {
40 pt_regs_write_reg(regs, reg, lower_32_bits(val));
41 }
42
get_x_reg(struct pt_regs * regs,int reg)43 static inline u64 get_x_reg(struct pt_regs *regs, int reg)
44 {
45 return pt_regs_read_reg(regs, reg);
46 }
47
get_w_reg(struct pt_regs * regs,int reg)48 static inline u32 get_w_reg(struct pt_regs *regs, int reg)
49 {
50 return lower_32_bits(pt_regs_read_reg(regs, reg));
51 }
52
update_lr(struct pt_regs * regs,long addr)53 static inline int update_lr(struct pt_regs *regs, long addr)
54 {
55 int err = 0;
56
57 if (user_mode(regs) && task_gcs_el0_enabled(current)) {
58 push_user_gcs(addr, &err);
59 if (err) {
60 force_sig(SIGSEGV);
61 return err;
62 }
63 }
64 procedure_link_pointer_set(regs, addr);
65 return err;
66 }
67
check_cbz(u32 opcode,struct pt_regs * regs)68 static bool __kprobes check_cbz(u32 opcode, struct pt_regs *regs)
69 {
70 int xn = opcode & 0x1f;
71
72 return (opcode & (1 << 31)) ?
73 (get_x_reg(regs, xn) == 0) : (get_w_reg(regs, xn) == 0);
74 }
75
check_cbnz(u32 opcode,struct pt_regs * regs)76 static bool __kprobes check_cbnz(u32 opcode, struct pt_regs *regs)
77 {
78 int xn = opcode & 0x1f;
79
80 return (opcode & (1 << 31)) ?
81 (get_x_reg(regs, xn) != 0) : (get_w_reg(regs, xn) != 0);
82 }
83
check_tbz(u32 opcode,struct pt_regs * regs)84 static bool __kprobes check_tbz(u32 opcode, struct pt_regs *regs)
85 {
86 int xn = opcode & 0x1f;
87 int bit_pos = ((opcode & (1 << 31)) >> 26) | ((opcode >> 19) & 0x1f);
88
89 return ((get_x_reg(regs, xn) >> bit_pos) & 0x1) == 0;
90 }
91
check_tbnz(u32 opcode,struct pt_regs * regs)92 static bool __kprobes check_tbnz(u32 opcode, struct pt_regs *regs)
93 {
94 int xn = opcode & 0x1f;
95 int bit_pos = ((opcode & (1 << 31)) >> 26) | ((opcode >> 19) & 0x1f);
96
97 return ((get_x_reg(regs, xn) >> bit_pos) & 0x1) != 0;
98 }
99
100 /*
101 * instruction simulation functions
102 */
103 void __kprobes
simulate_adr_adrp(u32 opcode,long addr,struct pt_regs * regs)104 simulate_adr_adrp(u32 opcode, long addr, struct pt_regs *regs)
105 {
106 long imm, xn, val;
107
108 xn = opcode & 0x1f;
109 imm = ((opcode >> 3) & 0x1ffffc) | ((opcode >> 29) & 0x3);
110 imm = sign_extend64(imm, 20);
111 if (opcode & 0x80000000)
112 val = (imm<<12) + (addr & 0xfffffffffffff000);
113 else
114 val = imm + addr;
115
116 set_x_reg(regs, xn, val);
117
118 instruction_pointer_set(regs, instruction_pointer(regs) + 4);
119 }
120
121 void __kprobes
simulate_b_bl(u32 opcode,long addr,struct pt_regs * regs)122 simulate_b_bl(u32 opcode, long addr, struct pt_regs *regs)
123 {
124 int disp = bbl_displacement(opcode);
125
126 if (opcode & (1 << 31))
127 if (update_lr(regs, addr + 4))
128 return;
129
130 instruction_pointer_set(regs, addr + disp);
131 }
132
133 void __kprobes
simulate_b_cond(u32 opcode,long addr,struct pt_regs * regs)134 simulate_b_cond(u32 opcode, long addr, struct pt_regs *regs)
135 {
136 int disp = 4;
137
138 if (aarch32_opcode_cond_checks[opcode & 0xf](regs->pstate & 0xffffffff))
139 disp = bcond_displacement(opcode);
140
141 instruction_pointer_set(regs, addr + disp);
142 }
143
144 void __kprobes
simulate_br_blr(u32 opcode,long addr,struct pt_regs * regs)145 simulate_br_blr(u32 opcode, long addr, struct pt_regs *regs)
146 {
147 int xn = (opcode >> 5) & 0x1f;
148 u64 b_target = get_x_reg(regs, xn);
149
150 if (((opcode >> 21) & 0x3) == 1)
151 if (update_lr(regs, addr + 4))
152 return;
153
154 instruction_pointer_set(regs, b_target);
155 }
156
157 void __kprobes
simulate_ret(u32 opcode,long addr,struct pt_regs * regs)158 simulate_ret(u32 opcode, long addr, struct pt_regs *regs)
159 {
160 u64 ret_addr;
161 int err = 0;
162 int xn = (opcode >> 5) & 0x1f;
163 u64 r_target = get_x_reg(regs, xn);
164
165 if (user_mode(regs) && task_gcs_el0_enabled(current)) {
166 ret_addr = pop_user_gcs(&err);
167 if (err || ret_addr != r_target) {
168 force_sig(SIGSEGV);
169 return;
170 }
171 }
172 instruction_pointer_set(regs, r_target);
173 }
174
175 void __kprobes
simulate_cbz_cbnz(u32 opcode,long addr,struct pt_regs * regs)176 simulate_cbz_cbnz(u32 opcode, long addr, struct pt_regs *regs)
177 {
178 int disp = 4;
179
180 if (opcode & (1 << 24)) {
181 if (check_cbnz(opcode, regs))
182 disp = cbz_displacement(opcode);
183 } else {
184 if (check_cbz(opcode, regs))
185 disp = cbz_displacement(opcode);
186 }
187 instruction_pointer_set(regs, addr + disp);
188 }
189
190 void __kprobes
simulate_tbz_tbnz(u32 opcode,long addr,struct pt_regs * regs)191 simulate_tbz_tbnz(u32 opcode, long addr, struct pt_regs *regs)
192 {
193 int disp = 4;
194
195 if (opcode & (1 << 24)) {
196 if (check_tbnz(opcode, regs))
197 disp = tbz_displacement(opcode);
198 } else {
199 if (check_tbz(opcode, regs))
200 disp = tbz_displacement(opcode);
201 }
202 instruction_pointer_set(regs, addr + disp);
203 }
204
205 void __kprobes
simulate_ldr_literal(u32 opcode,long addr,struct pt_regs * regs)206 simulate_ldr_literal(u32 opcode, long addr, struct pt_regs *regs)
207 {
208 unsigned long load_addr;
209 int xn = opcode & 0x1f;
210
211 load_addr = addr + ldr_displacement(opcode);
212
213 if (opcode & (1 << 30)) /* x0-x30 */
214 set_x_reg(regs, xn, READ_ONCE(*(u64 *)load_addr));
215 else /* w0-w30 */
216 set_w_reg(regs, xn, READ_ONCE(*(u32 *)load_addr));
217
218 instruction_pointer_set(regs, instruction_pointer(regs) + 4);
219 }
220
221 void __kprobes
simulate_ldrsw_literal(u32 opcode,long addr,struct pt_regs * regs)222 simulate_ldrsw_literal(u32 opcode, long addr, struct pt_regs *regs)
223 {
224 unsigned long load_addr;
225 int xn = opcode & 0x1f;
226
227 load_addr = addr + ldr_displacement(opcode);
228
229 set_x_reg(regs, xn, READ_ONCE(*(s32 *)load_addr));
230
231 instruction_pointer_set(regs, instruction_pointer(regs) + 4);
232 }
233
234 void __kprobes
simulate_nop(u32 opcode,long addr,struct pt_regs * regs)235 simulate_nop(u32 opcode, long addr, struct pt_regs *regs)
236 {
237 arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);
238 }
239