1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Based on arch/arm/mm/extable.c
4  */
5 
6 #include <linux/bitfield.h>
7 #include <linux/extable.h>
8 #include <linux/uaccess.h>
9 
10 #include <asm/asm-extable.h>
11 #include <asm/esr.h>
12 #include <asm/ptrace.h>
13 
cpy_faulted_on_uaccess(const struct exception_table_entry * ex,unsigned long esr)14 static bool cpy_faulted_on_uaccess(const struct exception_table_entry *ex,
15 				   unsigned long esr)
16 {
17 	bool uaccess_is_write = FIELD_GET(EX_DATA_UACCESS_WRITE, ex->data);
18 	bool fault_on_write = esr & ESR_ELx_WNR;
19 
20 	return uaccess_is_write == fault_on_write;
21 }
22 
insn_may_access_user(unsigned long addr,unsigned long esr)23 bool insn_may_access_user(unsigned long addr, unsigned long esr)
24 {
25 	const struct exception_table_entry *ex = search_exception_tables(addr);
26 
27 	if (!ex)
28 		return false;
29 
30 	switch (ex->type) {
31 	case EX_TYPE_UACCESS_CPY:
32 		return cpy_faulted_on_uaccess(ex, esr);
33 	default:
34 		return true;
35 	}
36 }
37 
38 static inline unsigned long
get_ex_fixup(const struct exception_table_entry * ex)39 get_ex_fixup(const struct exception_table_entry *ex)
40 {
41 	return ((unsigned long)&ex->fixup + ex->fixup);
42 }
43 
ex_handler_uaccess_err_zero(const struct exception_table_entry * ex,struct pt_regs * regs)44 static bool ex_handler_uaccess_err_zero(const struct exception_table_entry *ex,
45 					struct pt_regs *regs)
46 {
47 	int reg_err = FIELD_GET(EX_DATA_REG_ERR, ex->data);
48 	int reg_zero = FIELD_GET(EX_DATA_REG_ZERO, ex->data);
49 
50 	pt_regs_write_reg(regs, reg_err, -EFAULT);
51 	pt_regs_write_reg(regs, reg_zero, 0);
52 
53 	regs->pc = get_ex_fixup(ex);
54 	return true;
55 }
56 
ex_handler_uaccess_cpy(const struct exception_table_entry * ex,struct pt_regs * regs,unsigned long esr)57 static bool ex_handler_uaccess_cpy(const struct exception_table_entry *ex,
58 				   struct pt_regs *regs, unsigned long esr)
59 {
60 	/* Do not fix up faults on kernel memory accesses */
61 	if (!cpy_faulted_on_uaccess(ex, esr))
62 		return false;
63 
64 	regs->pc = get_ex_fixup(ex);
65 	return true;
66 }
67 
68 static bool
ex_handler_load_unaligned_zeropad(const struct exception_table_entry * ex,struct pt_regs * regs)69 ex_handler_load_unaligned_zeropad(const struct exception_table_entry *ex,
70 				  struct pt_regs *regs)
71 {
72 	int reg_data = FIELD_GET(EX_DATA_REG_DATA, ex->data);
73 	int reg_addr = FIELD_GET(EX_DATA_REG_ADDR, ex->data);
74 	unsigned long data, addr, offset;
75 
76 	addr = pt_regs_read_reg(regs, reg_addr);
77 
78 	offset = addr & 0x7UL;
79 	addr &= ~0x7UL;
80 
81 	data = *(unsigned long*)addr;
82 
83 #ifndef __AARCH64EB__
84 	data >>= 8 * offset;
85 #else
86 	data <<= 8 * offset;
87 #endif
88 
89 	pt_regs_write_reg(regs, reg_data, data);
90 
91 	regs->pc = get_ex_fixup(ex);
92 	return true;
93 }
94 
fixup_exception(struct pt_regs * regs,unsigned long esr)95 bool fixup_exception(struct pt_regs *regs, unsigned long esr)
96 {
97 	const struct exception_table_entry *ex;
98 
99 	ex = search_exception_tables(instruction_pointer(regs));
100 	if (!ex)
101 		return false;
102 
103 	switch (ex->type) {
104 	case EX_TYPE_BPF:
105 		return ex_handler_bpf(ex, regs);
106 	case EX_TYPE_UACCESS_ERR_ZERO:
107 	case EX_TYPE_KACCESS_ERR_ZERO:
108 		return ex_handler_uaccess_err_zero(ex, regs);
109 	case EX_TYPE_UACCESS_CPY:
110 		return ex_handler_uaccess_cpy(ex, regs, esr);
111 	case EX_TYPE_LOAD_UNALIGNED_ZEROPAD:
112 		return ex_handler_load_unaligned_zeropad(ex, regs);
113 	}
114 
115 	BUG();
116 }
117