diff options
Diffstat (limited to 'debian/patches/features/arm64/arm64-compat-Implement-misalignment-fixups-for-multi.patch')
-rw-r--r-- | debian/patches/features/arm64/arm64-compat-Implement-misalignment-fixups-for-multi.patch | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/debian/patches/features/arm64/arm64-compat-Implement-misalignment-fixups-for-multi.patch b/debian/patches/features/arm64/arm64-compat-Implement-misalignment-fixups-for-multi.patch new file mode 100644 index 000000000..303e69f83 --- /dev/null +++ b/debian/patches/features/arm64/arm64-compat-Implement-misalignment-fixups-for-multi.patch @@ -0,0 +1,480 @@ +From: Ard Biesheuvel <ardb@kernel.org> +Date: Fri, 1 Jul 2022 15:53:22 +0200 +Subject: arm64: compat: Implement misalignment fixups for multiword loads +Origin: https://git.kernel.org/linus/3fc24ef32d3b9368f4c103dcd21d6a3f959b4870 + +The 32-bit ARM kernel implements fixups on behalf of user space when +using LDM/STM or LDRD/STRD instructions on addresses that are not 32-bit +aligned. This is not something that is supported by the architecture, +but was done anyway to increase compatibility with user space software, +which mostly targeted x86 at the time and did not care about aligned +accesses. + +This feature is one of the remaining impediments to being able to switch +to 64-bit kernels on 64-bit capable hardware running 32-bit user space, +so let's implement it for the arm64 compat layer as well. + +Note that the intent is to implement the exact same handling of +misaligned multi-word loads and stores as the 32-bit kernel does, +including what appears to be missing support for user space programs +that rely on SETEND to switch to a different byte order and back. Also, +like the 32-bit ARM version, we rely on the faulting address reported by +the CPU to infer the memory address, instead of decoding the instruction +fully to obtain this information. + +This implementation is taken from the 32-bit ARM tree, with all pieces +removed that deal with instructions other than LDRD/STRD and LDM/STM, or +that deal with alignment exceptions taken in kernel mode. + +Cc: debian-arm@lists.debian.org +Cc: Vagrant Cascadian <vagrant@debian.org> +Cc: Riku Voipio <riku.voipio@iki.fi> +Cc: Steve McIntyre <steve@einval.com> +Signed-off-by: Ard Biesheuvel <ardb@kernel.org> +Reviewed-by: Arnd Bergmann <arnd@arndb.de> +Link: https://lore.kernel.org/r/20220701135322.3025321-1-ardb@kernel.org +[catalin.marinas@arm.com: change the option to 'default n'] +Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> +--- + arch/arm64/Kconfig | 3 + + arch/arm64/include/asm/exception.h | 1 + + arch/arm64/kernel/Makefile | 1 + + arch/arm64/kernel/compat_alignment.c | 387 +++++++++++++++++++++++++++ + arch/arm64/mm/fault.c | 3 + + 5 files changed, 395 insertions(+) + create mode 100644 arch/arm64/kernel/compat_alignment.c + +--- a/arch/arm64/Kconfig ++++ b/arch/arm64/Kconfig +@@ -1328,6 +1328,9 @@ config THUMB2_COMPAT_VDSO + Compile the compat vDSO with '-mthumb -fomit-frame-pointer' if y, + otherwise with '-marm'. + ++config COMPAT_ALIGNMENT_FIXUPS ++ bool "Fix up misaligned multi-word loads and stores in user space" ++ + menuconfig ARMV8_DEPRECATED + bool "Emulate deprecated/obsolete ARMv8 instructions" + depends on SYSCTL +--- a/arch/arm64/include/asm/exception.h ++++ b/arch/arm64/include/asm/exception.h +@@ -47,6 +47,7 @@ void do_el0_sys(unsigned long esr, struc + void do_sp_pc_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs); + void bad_el0_sync(struct pt_regs *regs, int reason, unsigned int esr); + void do_el0_cp15(unsigned long esr, struct pt_regs *regs); ++int do_compat_alignment_fixup(unsigned long addr, struct pt_regs *regs); + void do_el0_svc(struct pt_regs *regs); + void do_el0_svc_compat(struct pt_regs *regs); + void do_el0_fpac(struct pt_regs *regs, unsigned long esr); +--- a/arch/arm64/kernel/Makefile ++++ b/arch/arm64/kernel/Makefile +@@ -28,6 +28,7 @@ $(obj)/%.stub.o: $(obj)/%.o FORCE + obj-$(CONFIG_COMPAT) += sys32.o signal32.o \ + sys_compat.o + obj-$(CONFIG_COMPAT) += sigreturn32.o ++obj-$(CONFIG_COMPAT_ALIGNMENT_FIXUPS) += compat_alignment.o + obj-$(CONFIG_KUSER_HELPERS) += kuser32.o + obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o entry-ftrace.o + obj-$(CONFIG_MODULES) += module.o +--- /dev/null ++++ b/arch/arm64/kernel/compat_alignment.c +@@ -0,0 +1,387 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// based on arch/arm/mm/alignment.c ++ ++#include <linux/compiler.h> ++#include <linux/errno.h> ++#include <linux/kernel.h> ++#include <linux/init.h> ++#include <linux/perf_event.h> ++#include <linux/uaccess.h> ++ ++#include <asm/exception.h> ++#include <asm/ptrace.h> ++#include <asm/traps.h> ++ ++/* ++ * 32-bit misaligned trap handler (c) 1998 San Mehat (CCC) -July 1998 ++ * ++ * Speed optimisations and better fault handling by Russell King. ++ */ ++#define CODING_BITS(i) (i & 0x0e000000) ++ ++#define LDST_P_BIT(i) (i & (1 << 24)) /* Preindex */ ++#define LDST_U_BIT(i) (i & (1 << 23)) /* Add offset */ ++#define LDST_W_BIT(i) (i & (1 << 21)) /* Writeback */ ++#define LDST_L_BIT(i) (i & (1 << 20)) /* Load */ ++ ++#define LDST_P_EQ_U(i) ((((i) ^ ((i) >> 1)) & (1 << 23)) == 0) ++ ++#define LDSTHD_I_BIT(i) (i & (1 << 22)) /* double/half-word immed */ ++ ++#define RN_BITS(i) ((i >> 16) & 15) /* Rn */ ++#define RD_BITS(i) ((i >> 12) & 15) /* Rd */ ++#define RM_BITS(i) (i & 15) /* Rm */ ++ ++#define REGMASK_BITS(i) (i & 0xffff) ++ ++#define BAD_INSTR 0xdeadc0de ++ ++/* Thumb-2 32 bit format per ARMv7 DDI0406A A6.3, either f800h,e800h,f800h */ ++#define IS_T32(hi16) \ ++ (((hi16) & 0xe000) == 0xe000 && ((hi16) & 0x1800)) ++ ++union offset_union { ++ unsigned long un; ++ signed long sn; ++}; ++ ++#define TYPE_ERROR 0 ++#define TYPE_FAULT 1 ++#define TYPE_LDST 2 ++#define TYPE_DONE 3 ++ ++static void ++do_alignment_finish_ldst(unsigned long addr, u32 instr, struct pt_regs *regs, ++ union offset_union offset) ++{ ++ if (!LDST_U_BIT(instr)) ++ offset.un = -offset.un; ++ ++ if (!LDST_P_BIT(instr)) ++ addr += offset.un; ++ ++ if (!LDST_P_BIT(instr) || LDST_W_BIT(instr)) ++ regs->regs[RN_BITS(instr)] = addr; ++} ++ ++static int ++do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs) ++{ ++ unsigned int rd = RD_BITS(instr); ++ unsigned int rd2; ++ int load; ++ ++ if ((instr & 0xfe000000) == 0xe8000000) { ++ /* ARMv7 Thumb-2 32-bit LDRD/STRD */ ++ rd2 = (instr >> 8) & 0xf; ++ load = !!(LDST_L_BIT(instr)); ++ } else if (((rd & 1) == 1) || (rd == 14)) { ++ return TYPE_ERROR; ++ } else { ++ load = ((instr & 0xf0) == 0xd0); ++ rd2 = rd + 1; ++ } ++ ++ if (load) { ++ unsigned int val, val2; ++ ++ if (get_user(val, (u32 __user *)addr) || ++ get_user(val2, (u32 __user *)(addr + 4))) ++ return TYPE_FAULT; ++ regs->regs[rd] = val; ++ regs->regs[rd2] = val2; ++ } else { ++ if (put_user(regs->regs[rd], (u32 __user *)addr) || ++ put_user(regs->regs[rd2], (u32 __user *)(addr + 4))) ++ return TYPE_FAULT; ++ } ++ return TYPE_LDST; ++} ++ ++/* ++ * LDM/STM alignment handler. ++ * ++ * There are 4 variants of this instruction: ++ * ++ * B = rn pointer before instruction, A = rn pointer after instruction ++ * ------ increasing address -----> ++ * | | r0 | r1 | ... | rx | | ++ * PU = 01 B A ++ * PU = 11 B A ++ * PU = 00 A B ++ * PU = 10 A B ++ */ ++static int ++do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs) ++{ ++ unsigned int rd, rn, nr_regs, regbits; ++ unsigned long eaddr, newaddr; ++ unsigned int val; ++ ++ /* count the number of registers in the mask to be transferred */ ++ nr_regs = hweight16(REGMASK_BITS(instr)) * 4; ++ ++ rn = RN_BITS(instr); ++ newaddr = eaddr = regs->regs[rn]; ++ ++ if (!LDST_U_BIT(instr)) ++ nr_regs = -nr_regs; ++ newaddr += nr_regs; ++ if (!LDST_U_BIT(instr)) ++ eaddr = newaddr; ++ ++ if (LDST_P_EQ_U(instr)) /* U = P */ ++ eaddr += 4; ++ ++ for (regbits = REGMASK_BITS(instr), rd = 0; regbits; ++ regbits >>= 1, rd += 1) ++ if (regbits & 1) { ++ if (LDST_L_BIT(instr)) { ++ if (get_user(val, (u32 __user *)eaddr)) ++ return TYPE_FAULT; ++ if (rd < 15) ++ regs->regs[rd] = val; ++ else ++ regs->pc = val; ++ } else { ++ /* ++ * The PC register has a bias of +8 in ARM mode ++ * and +4 in Thumb mode. This means that a read ++ * of the value of PC should account for this. ++ * Since Thumb does not permit STM instructions ++ * to refer to PC, just add 8 here. ++ */ ++ val = (rd < 15) ? regs->regs[rd] : regs->pc + 8; ++ if (put_user(val, (u32 __user *)eaddr)) ++ return TYPE_FAULT; ++ } ++ eaddr += 4; ++ } ++ ++ if (LDST_W_BIT(instr)) ++ regs->regs[rn] = newaddr; ++ ++ return TYPE_DONE; ++} ++ ++/* ++ * Convert Thumb multi-word load/store instruction forms to equivalent ARM ++ * instructions so we can reuse ARM userland alignment fault fixups for Thumb. ++ * ++ * This implementation was initially based on the algorithm found in ++ * gdb/sim/arm/thumbemu.c. It is basically just a code reduction of same ++ * to convert only Thumb ld/st instruction forms to equivalent ARM forms. ++ * ++ * NOTES: ++ * 1. Comments below refer to ARM ARM DDI0100E Thumb Instruction sections. ++ * 2. If for some reason we're passed an non-ld/st Thumb instruction to ++ * decode, we return 0xdeadc0de. This should never happen under normal ++ * circumstances but if it does, we've got other problems to deal with ++ * elsewhere and we obviously can't fix those problems here. ++ */ ++ ++static unsigned long thumb2arm(u16 tinstr) ++{ ++ u32 L = (tinstr & (1<<11)) >> 11; ++ ++ switch ((tinstr & 0xf800) >> 11) { ++ /* 6.6.1 Format 1: */ ++ case 0xc000 >> 11: /* 7.1.51 STMIA */ ++ case 0xc800 >> 11: /* 7.1.25 LDMIA */ ++ { ++ u32 Rn = (tinstr & (7<<8)) >> 8; ++ u32 W = ((L<<Rn) & (tinstr&255)) ? 0 : 1<<21; ++ ++ return 0xe8800000 | W | (L<<20) | (Rn<<16) | ++ (tinstr&255); ++ } ++ ++ /* 6.6.1 Format 2: */ ++ case 0xb000 >> 11: /* 7.1.48 PUSH */ ++ case 0xb800 >> 11: /* 7.1.47 POP */ ++ if ((tinstr & (3 << 9)) == 0x0400) { ++ static const u32 subset[4] = { ++ 0xe92d0000, /* STMDB sp!,{registers} */ ++ 0xe92d4000, /* STMDB sp!,{registers,lr} */ ++ 0xe8bd0000, /* LDMIA sp!,{registers} */ ++ 0xe8bd8000 /* LDMIA sp!,{registers,pc} */ ++ }; ++ return subset[(L<<1) | ((tinstr & (1<<8)) >> 8)] | ++ (tinstr & 255); /* register_list */ ++ } ++ fallthrough; /* for illegal instruction case */ ++ ++ default: ++ return BAD_INSTR; ++ } ++} ++ ++/* ++ * Convert Thumb-2 32 bit LDM, STM, LDRD, STRD to equivalent instruction ++ * handlable by ARM alignment handler, also find the corresponding handler, ++ * so that we can reuse ARM userland alignment fault fixups for Thumb. ++ * ++ * @pinstr: original Thumb-2 instruction; returns new handlable instruction ++ * @regs: register context. ++ * @poffset: return offset from faulted addr for later writeback ++ * ++ * NOTES: ++ * 1. Comments below refer to ARMv7 DDI0406A Thumb Instruction sections. ++ * 2. Register name Rt from ARMv7 is same as Rd from ARMv6 (Rd is Rt) ++ */ ++static void * ++do_alignment_t32_to_handler(u32 *pinstr, struct pt_regs *regs, ++ union offset_union *poffset) ++{ ++ u32 instr = *pinstr; ++ u16 tinst1 = (instr >> 16) & 0xffff; ++ u16 tinst2 = instr & 0xffff; ++ ++ switch (tinst1 & 0xffe0) { ++ /* A6.3.5 Load/Store multiple */ ++ case 0xe880: /* STM/STMIA/STMEA,LDM/LDMIA, PUSH/POP T2 */ ++ case 0xe8a0: /* ...above writeback version */ ++ case 0xe900: /* STMDB/STMFD, LDMDB/LDMEA */ ++ case 0xe920: /* ...above writeback version */ ++ /* no need offset decision since handler calculates it */ ++ return do_alignment_ldmstm; ++ ++ case 0xf840: /* POP/PUSH T3 (single register) */ ++ if (RN_BITS(instr) == 13 && (tinst2 & 0x09ff) == 0x0904) { ++ u32 L = !!(LDST_L_BIT(instr)); ++ const u32 subset[2] = { ++ 0xe92d0000, /* STMDB sp!,{registers} */ ++ 0xe8bd0000, /* LDMIA sp!,{registers} */ ++ }; ++ *pinstr = subset[L] | (1<<RD_BITS(instr)); ++ return do_alignment_ldmstm; ++ } ++ /* Else fall through for illegal instruction case */ ++ break; ++ ++ /* A6.3.6 Load/store double, STRD/LDRD(immed, lit, reg) */ ++ case 0xe860: ++ case 0xe960: ++ case 0xe8e0: ++ case 0xe9e0: ++ poffset->un = (tinst2 & 0xff) << 2; ++ fallthrough; ++ ++ case 0xe940: ++ case 0xe9c0: ++ return do_alignment_ldrdstrd; ++ ++ /* ++ * No need to handle load/store instructions up to word size ++ * since ARMv6 and later CPUs can perform unaligned accesses. ++ */ ++ default: ++ break; ++ } ++ return NULL; ++} ++ ++static int alignment_get_arm(struct pt_regs *regs, __le32 __user *ip, u32 *inst) ++{ ++ __le32 instr = 0; ++ int fault; ++ ++ fault = get_user(instr, ip); ++ if (fault) ++ return fault; ++ ++ *inst = __le32_to_cpu(instr); ++ return 0; ++} ++ ++static int alignment_get_thumb(struct pt_regs *regs, __le16 __user *ip, u16 *inst) ++{ ++ __le16 instr = 0; ++ int fault; ++ ++ fault = get_user(instr, ip); ++ if (fault) ++ return fault; ++ ++ *inst = __le16_to_cpu(instr); ++ return 0; ++} ++ ++int do_compat_alignment_fixup(unsigned long addr, struct pt_regs *regs) ++{ ++ union offset_union offset; ++ unsigned long instrptr; ++ int (*handler)(unsigned long addr, u32 instr, struct pt_regs *regs); ++ unsigned int type; ++ u32 instr = 0; ++ u16 tinstr = 0; ++ int isize = 4; ++ int thumb2_32b = 0; ++ int fault; ++ ++ instrptr = instruction_pointer(regs); ++ ++ if (compat_thumb_mode(regs)) { ++ __le16 __user *ptr = (__le16 __user *)(instrptr & ~1); ++ ++ fault = alignment_get_thumb(regs, ptr, &tinstr); ++ if (!fault) { ++ if (IS_T32(tinstr)) { ++ /* Thumb-2 32-bit */ ++ u16 tinst2; ++ fault = alignment_get_thumb(regs, ptr + 1, &tinst2); ++ instr = ((u32)tinstr << 16) | tinst2; ++ thumb2_32b = 1; ++ } else { ++ isize = 2; ++ instr = thumb2arm(tinstr); ++ } ++ } ++ } else { ++ fault = alignment_get_arm(regs, (__le32 __user *)instrptr, &instr); ++ } ++ ++ if (fault) ++ return 1; ++ ++ switch (CODING_BITS(instr)) { ++ case 0x00000000: /* 3.13.4 load/store instruction extensions */ ++ if (LDSTHD_I_BIT(instr)) ++ offset.un = (instr & 0xf00) >> 4 | (instr & 15); ++ else ++ offset.un = regs->regs[RM_BITS(instr)]; ++ ++ if ((instr & 0x001000f0) == 0x000000d0 || /* LDRD */ ++ (instr & 0x001000f0) == 0x000000f0) /* STRD */ ++ handler = do_alignment_ldrdstrd; ++ else ++ return 1; ++ break; ++ ++ case 0x08000000: /* ldm or stm, or thumb-2 32bit instruction */ ++ if (thumb2_32b) { ++ offset.un = 0; ++ handler = do_alignment_t32_to_handler(&instr, regs, &offset); ++ } else { ++ offset.un = 0; ++ handler = do_alignment_ldmstm; ++ } ++ break; ++ ++ default: ++ return 1; ++ } ++ ++ type = handler(addr, instr, regs); ++ ++ if (type == TYPE_ERROR || type == TYPE_FAULT) ++ return 1; ++ ++ if (type == TYPE_LDST) ++ do_alignment_finish_ldst(addr, instr, regs, offset); ++ ++ perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, 1, regs, regs->pc); ++ arm64_skip_faulting_instruction(regs, isize); ++ ++ return 0; ++} +--- a/arch/arm64/mm/fault.c ++++ b/arch/arm64/mm/fault.c +@@ -610,6 +610,9 @@ static int __kprobes do_translation_faul + static int do_alignment_fault(unsigned long addr, unsigned int esr, + struct pt_regs *regs) + { ++ if (IS_ENABLED(CONFIG_COMPAT_ALIGNMENT_FIXUPS) && ++ compat_user_mode(regs)) ++ return do_compat_alignment_fixup(addr, regs); + do_bad_area(addr, esr, regs); + return 0; + } |