[PATCH] alignment:fetch pc-instr before irq_enable

From: xiaoqian
Date: Wed Jun 05 2019 - 22:32:09 EST


When the instruction code under PC address is read through
_probe_kernel_read in do_alignment,if the pte page corresponding
to the code segment of PC address is reclaimed exactly at this time,
the address mapping cannot be reconstructed because page fault_disable()
is executed in _probe_kernel_read function,and the failure to obtain
the instruction code of PC finally results in the unsuccessful repair
operation.
Thus we can modify the implementation of reading user-mode PC instruction
before local_irq_enable to avoid the above risk.
At the same time, adjust the sequence of code processing and optimize the
process.

Signed-off-by: xiaoqian <xiaoqian9@xxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
arch/arm/mm/alignment.c | 81 +++++++++++++++++++++++++++++++++----------------
1 file changed, 55 insertions(+), 26 deletions(-)

diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c
index e376883ab35b..4124b9ce3c70 100644
--- a/arch/arm/mm/alignment.c
+++ b/arch/arm/mm/alignment.c
@@ -76,6 +76,11 @@
#define IS_T32(hi16) \
(((hi16) & 0xe000) == 0xe000 && ((hi16) & 0x1800))

+#define INVALID_INSTR_MODE 0
+#define ARM_INSTR_MODE 1
+#define THUMB_INSTR_MODE 2
+#define THUMB2_INSTR_MODE 3
+
static unsigned long ai_user;
static unsigned long ai_sys;
static void *ai_sys_last_pc;
@@ -705,6 +710,48 @@ thumb2arm(u16 tinstr)
}
}

+static unsigned int
+fetch_usr_pc_instr(struct pt_regs *regs, unsigned long *pc_instrptr)
+{
+ unsigned int fault;
+ unsigned long instrptr;
+ unsigned long instr_mode = INVALID_INSTR_MODE;
+
+ instrptr = instruction_pointer(regs);
+
+ if (thumb_mode(regs)) {
+ u16 tinstr = 0;
+ u16 *ptr = (u16 *)(instrptr & ~1);
+
+ fault = probe_kernel_address(ptr, tinstr);
+ if (!fault) {
+ tinstr = __mem_to_opcode_thumb16(tinstr);
+ if (cpu_architecture() >= CPU_ARCH_ARMv7 &&
+ IS_T32(tinstr)) {
+ /* Thumb-2 32-bit */
+ u16 tinstr2 = 0;
+
+ fault = probe_kernel_address(ptr + 1, tinstr2);
+ if (!fault) {
+ tinstr2 = __mem_to_opcode_thumb16(tinstr2);
+ *pc_instrptr = __opcode_thumb32_compose(tinstr, tinstr2);
+ instr_mode = THUMB2_INSTR_MODE;
+ }
+ } else {
+ *pc_instrptr = thumb2arm(tinstr);
+ instr_mode = THUMB_INSTR_MODE;
+ }
+ }
+ } else {
+ fault = probe_kernel_address((void *)instrptr, *pc_instrptr);
+ if (!fault) {
+ *pc_instrptr = __mem_to_opcode_arm(*pc_instrptr);
+ instr_mode = ARM_INSTR_MODE;
+ }
+ }
+ return instr_mode;
+}
+
/*
* Convert Thumb-2 32 bit LDM, STM, LDRD, STRD to equivalent instruction
* handlable by ARM alignment handler, also find the corresponding handler,
@@ -775,42 +822,24 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
unsigned long instr = 0, instrptr;
int (*handler)(unsigned long addr, unsigned long instr, struct pt_regs *regs);
unsigned int type;
- unsigned int fault;
u16 tinstr = 0;
int isize = 4;
int thumb2_32b = 0;
+ unsigned long pc_instr_mode;
+
+ pc_instr_mode = fetch_usr_pc_instr(regs, &instr);

if (interrupts_enabled(regs))
local_irq_enable();

instrptr = instruction_pointer(regs);
-
- if (thumb_mode(regs)) {
- u16 *ptr = (u16 *)(instrptr & ~1);
- fault = probe_kernel_address(ptr, tinstr);
- tinstr = __mem_to_opcode_thumb16(tinstr);
- if (!fault) {
- if (cpu_architecture() >= CPU_ARCH_ARMv7 &&
- IS_T32(tinstr)) {
- /* Thumb-2 32-bit */
- u16 tinst2 = 0;
- fault = probe_kernel_address(ptr + 1, tinst2);
- tinst2 = __mem_to_opcode_thumb16(tinst2);
- instr = __opcode_thumb32_compose(tinstr, tinst2);
- thumb2_32b = 1;
- } else {
- isize = 2;
- instr = thumb2arm(tinstr);
- }
- }
- } else {
- fault = probe_kernel_address((void *)instrptr, instr);
- instr = __mem_to_opcode_arm(instr);
- }
-
- if (fault) {
+ if (pc_instr_mode == INVALID_INSTR_MODE) {
type = TYPE_FAULT;
goto bad_or_fault;
+ } else if (pc_instr_mode == THUMB_INSTR_MODE) {
+ isize = 2;
+ } else if (pc_instr_mode == THUMB2_INSTR_MODE) {
+ thumb2_32b = 1;
}

if (user_mode(regs))
--
2.12.3