* [PATCH] arm64: compat: Keep alignment address arithmetic 32-bit
@ 2026-08-17 0:02 Karl Mehltretter
2026-08-17 9:15 ` Arnd Bergmann
0 siblings, 1 reply; 4+ messages 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] 4+ messages in thread* Re: [PATCH] arm64: compat: Keep alignment address arithmetic 32-bit
2026-08-17 0:02 [PATCH] arm64: compat: Keep alignment address arithmetic 32-bit Karl Mehltretter
@ 2026-08-17 9:15 ` Arnd Bergmann
2026-08-18 3:22 ` Karl Mehltretter
0 siblings, 1 reply; 4+ messages in thread
From: Arnd Bergmann @ 2026-08-17 9:15 UTC (permalink / raw)
To: Karl Mehltretter, Catalin Marinas, Will Deacon
Cc: Mark Rutland, Ard Biesheuvel, linux-arm-kernel, linux-kernel
On Mon, Aug 17, 2026, at 02:02, Karl Mehltretter wrote:
> 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.
Hi Karl,
Nice find! How did you come across this?
Your patch looks correct to me, but it took me a bit to understand
it, as I found the use of compat_ptr() and changing the addressing
to 32-bit a little confusing at first.
> unsigned int rd, rn, nr_regs, regbits;
> - unsigned long eaddr, newaddr;
> + u32 eaddr, newaddr;
> unsigned int val;
As I understand it, the underlying problem here is the
32-bit overflow of nr_regs. Wouldn't it be sufficient
to just turn nr_regs into an 'unsigned long' or 'size_t'
in both instances?
> - if (get_user(val, (u32 __user *)eaddr))
> + if (get_user(val,
> + (u32 __user *)compat_ptr(eaddr)))
The individual compat_ptr() in each access looks like it would
have been sufficient as well, by avoiding the effect of the
overflow, and it also makes the address wrap back to zero
at the end of the address space. What's a bit confusing here
is that accessing an unaligned set of words at the end of the
address space will still read a couple of bytes beyond the
end of the 32-bit space.
Again, none of this is wrong, just wondering whether a simpler
change would make this easier to understand and keep the code
closer to the original arm32 version.
Arnd
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] arm64: compat: Keep alignment address arithmetic 32-bit
2026-08-17 9:15 ` Arnd Bergmann
@ 2026-08-18 3:22 ` Karl Mehltretter
2026-08-18 7:31 ` Arnd Bergmann
0 siblings, 1 reply; 4+ messages in thread
From: Karl Mehltretter @ 2026-08-18 3:22 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Catalin Marinas, Will Deacon, Mark Rutland, Ard Biesheuvel,
linux-arm-kernel, linux-kernel
On Mon, Aug 17, 2026 at 11:15:33AM +0100, Arnd Bergmann wrote:
> Nice find! How did you come across this?
Hi Arnd,
This didn't start as a real world bug hunt. I only recently became
aware of the compat alignment fixups and wanted to see how the
implementation differed from the arm32 one.
This is the only issue I found that was a regression from the arm32
implementation, and it looks like this can happen in real usage.
> As I understand it, the underlying problem here is the
> 32-bit overflow of nr_regs. Wouldn't it be sufficient
> to just turn nr_regs into an 'unsigned long' or 'size_t'
You are right that simply widening nr_regs fixes the incorrect address
arithmetic for decrementing LDM/STM transfers.
The 4 GiB wraparound case is possible with a rather unusual arm64
kernel configuration, and correctly handling an individual word
crossing the boundary would require byte accesses. That seems too
contrived to justify the extra complexity here.
Just to be sure by "both instances" do you mean the nr_regs
declarations in both arch/arm64/kernel/compat_alignment.c and
arch/arm/mm/alignment.c?
Changing nr_regs in arm32 alignment.c would not change the generated
code and I guess probably not be backported, so that would create a
divergence with stable kernels.
Would you prefer changing both files, or only the arm64 implementation?
Thanks,
Karl
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] arm64: compat: Keep alignment address arithmetic 32-bit
2026-08-18 3:22 ` Karl Mehltretter
@ 2026-08-18 7:31 ` Arnd Bergmann
0 siblings, 0 replies; 4+ messages in thread
From: Arnd Bergmann @ 2026-08-18 7:31 UTC (permalink / raw)
To: Karl Mehltretter
Cc: Catalin Marinas, Will Deacon, Mark Rutland, Ard Biesheuvel,
linux-arm-kernel, linux-kernel
On Tue, Aug 18, 2026, at 05:22, Karl Mehltretter wrote:
> On Mon, Aug 17, 2026 at 11:15:33AM +0100, Arnd Bergmann wrote:
<
> The 4 GiB wraparound case is possible with a rather unusual arm64
> kernel configuration, and correctly handling an individual word
> crossing the boundary would require byte accesses. That seems too
> contrived to justify the extra complexity here.
I don't understand, what is special about the configuration?
Isn't this exactly the case you were trying to address with
the compat_ptr() hack?
> Just to be sure by "both instances" do you mean the nr_regs
> declarations in both arch/arm64/kernel/compat_alignment.c and
> arch/arm/mm/alignment.c?
Yes
> Changing nr_regs in arm32 alignment.c would not change the generated
> code and I guess probably not be backported, so that would create a
> divergence with stable kernels.
>
> Would you prefer changing both files, or only the arm64 implementation?
I would suggest doing only a small change to arm64.
Arnd
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-18 7:32 UTC | newest]
Thread overview: 4+ messages (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
2026-08-17 9:15 ` Arnd Bergmann
2026-08-18 3:22 ` Karl Mehltretter
2026-08-18 7:31 ` Arnd Bergmann
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox