[PATCH] arm64: compat: Keep alignment address arithmetic 32-bit

Karl Mehltretter <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.ports.arm.kernel
Message-ID <[email protected]>
The compat alignment emulator inherited unsigned long data addresses
from the 32-bit ARM implementation. On arm64, negating the unsigned int
transfer size wraps it at 32 bits before it is added to a 64-bit
address. A decrementing LDM or STM therefore adds nearly 4 GiB instead
of subtracting its transfer size. The resulting address lies outside
the compat task's address space, so the access fails and the process
gets a spurious SIGBUS instead of the fixup.

Using 64-bit addresses also prevents transfer and writeback arithmetic
from wrapping at the AArch32 address-space boundary.

Use 32-bit types for emulated data addresses and offsets, and convert
them with compat_ptr() at the uaccess boundary. This preserves AArch32
modulo-2^32 address generation for every supported transfer form.

Fixes: 3fc24ef32d3b ("arm64: compat: Implement misalignment fixups for multiword loads")
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <[email protected]>
---
Tested:
  Built a static AArch32 alignment test. It deliberately executes unaligned
  multiword instructions. A 64-bit BusyBox initramfs runs it on arm64 QEMU
  and Pi 400, exercising compat_alignment.c.

  On arm64 QEMU and Pi 400, the old kernel gets SIGBUS for ARM LDMDA,
  STMDA, LDMDB, STMDB, Thumb-2 LDMDB/STMDB, and Thumb-1 PUSH. With this
  change, all 18 cases pass.

  The LDRD/STRD register-offset wrap test also passes on both arm64
  targets.

  Native ARM32 QEMU: all runnable cases pass. The high-address wrap probes
  are skipped because of the 3G/1G user/kernel split.

 arch/arm64/kernel/compat_alignment.c | 30 ++++++++++++++++------------
 1 file changed, 17 insertions(+), 13 deletions(-)

diff --git a/arch/arm64/kernel/compat_alignment.c b/arch/arm64/kernel/compat_alignment.c
index b68e1d328d4cb..c926041ee97b6 100644
--- a/arch/arm64/kernel/compat_alignment.c
+++ b/arch/arm64/kernel/compat_alignment.c
@@ -2,6 +2,7 @@
 // based on arch/arm/mm/alignment.c
 
 #include <linux/compiler.h>
+#include <linux/compat.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
@@ -41,8 +42,8 @@
 	(((hi16) & 0xe000) == 0xe000 && ((hi16) & 0x1800))
 
 union offset_union {
-	unsigned long un;
-	  signed long sn;
+	u32 un;
+	s32 sn;
 };
 
 #define TYPE_ERROR	0
@@ -51,7 +52,7 @@ union offset_union {
 #define TYPE_DONE	3
 
 static void
-do_alignment_finish_ldst(unsigned long addr, u32 instr, struct pt_regs *regs,
+do_alignment_finish_ldst(u32 addr, u32 instr, struct pt_regs *regs,
 			 union offset_union offset)
 {
 	if (!LDST_U_BIT(instr))
@@ -65,7 +66,7 @@ do_alignment_finish_ldst(unsigned long addr, u32 instr, struct pt_regs *regs,
 }
 
 static int
-do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs)
+do_alignment_ldrdstrd(u32 addr, u32 instr, struct pt_regs *regs)
 {
 	unsigned int rd = RD_BITS(instr);
 	unsigned int rd2;
@@ -85,14 +86,15 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs)
 	if (load) {
 		unsigned int val, val2;
 
-		if (get_user(val, (u32 __user *)addr) ||
-		    get_user(val2, (u32 __user *)(addr + 4)))
+		if (get_user(val, (u32 __user *)compat_ptr(addr)) ||
+		    get_user(val2, (u32 __user *)compat_ptr(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)))
+		if (put_user(regs->regs[rd], (u32 __user *)compat_ptr(addr)) ||
+		    put_user(regs->regs[rd2],
+			     (u32 __user *)compat_ptr(addr + 4)))
 			return TYPE_FAULT;
 	}
 	return TYPE_LDST;
@@ -112,10 +114,10 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs)
  * PU = 10             A                    B
  */
 static int
-do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs)
+do_alignment_ldmstm(u32 addr, u32 instr, struct pt_regs *regs)
 {
 	unsigned int rd, rn, nr_regs, regbits;
-	unsigned long eaddr, newaddr;
+	u32 eaddr, newaddr;
 	unsigned int val;
 
 	/* count the number of registers in the mask to be transferred */
@@ -137,7 +139,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs)
 	     regbits >>= 1, rd += 1)
 		if (regbits & 1) {
 			if (LDST_L_BIT(instr)) {
-				if (get_user(val, (u32 __user *)eaddr))
+				if (get_user(val,
+					     (u32 __user *)compat_ptr(eaddr)))
 					return TYPE_FAULT;
 				if (rd < 15)
 					regs->regs[rd] = val;
@@ -152,7 +155,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs)
 				 * to refer to PC, just add 8 here.
 				 */
 				val = (rd < 15) ? regs->regs[rd] : regs->pc + 8;
-				if (put_user(val, (u32 __user *)eaddr))
+				if (put_user(val,
+					     (u32 __user *)compat_ptr(eaddr)))
 					return TYPE_FAULT;
 			}
 			eaddr += 4;
@@ -311,7 +315,7 @@ 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);
+	int (*handler)(u32 addr, u32 instr, struct pt_regs *regs);
 	unsigned int type;
 	u32 instr = 0;
 	int isize = 4;
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.