Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] arm64: compat: Keep alignment address arithmetic 32-bit
@ 2026-08-17  0:02 Karl Mehltretter
  0 siblings, 0 replies; only message in thread
From: Karl Mehltretter @ 2026-08-17  0:02 UTC (permalink / raw)
  To: Catalin Marinas, Will Deacon
  Cc: Karl Mehltretter, Mark Rutland, Arnd Bergmann, Ard Biesheuvel,
	linux-arm-kernel, linux-kernel

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 <kmehltretter@gmail.com>
---
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



^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-08-17  0:03 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17  0:02 [PATCH] arm64: compat: Keep alignment address arithmetic 32-bit Karl Mehltretter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox