[PATCH] ARM: alignment: restore uaccess state on emulation fault

From: Karl Mehltretter

Date: Tue Jul 28 2026 - 17:13:03 EST


The alignment handlers enable access to user memory while emulating
unaligned instructions. If a checked access faults, its exception table
fixup jumps to the handler's fault label and bypasses uaccess_restore().
This leaves DOMAIN_USER as DOMAIN_CLIENT with SW domain PAN, or leaves
TTBR0 page table walks enabled with TTBR0 PAN, while the fault is handled.

An ARM926 QEMU test using LDMIA across an unmapped page boundary reports
DOMAIN_USER=1 at TYPE_FAULT before this change and DOMAIN_USER=0 after it.
SW domain PAN is the default on ARMv5, ARMv6, and non-LPAE ARMv7 builds.

Keep the saved uaccess state at function scope and track whether it was
saved. Restore it at the fault label only when needed. Paths that did not
enable user access continue to skip the restore.

Fixes: 274e91b81ed2 ("ARM: alignment: fix alignment handling for uaccess changes")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter@xxxxxxxxx>
---
arch/arm/mm/alignment.c | 51 ++++++++++++++++++++++++++++++-----------
1 file changed, 37 insertions(+), 14 deletions(-)

diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c
index 812380f30ae3..2b470a419dda 100644
--- a/arch/arm/mm/alignment.c
+++ b/arch/arm/mm/alignment.c
@@ -341,6 +341,8 @@ static int
do_alignment_ldrhstrh(unsigned long addr, u32 instr, struct pt_regs *regs)
{
unsigned int rd = RD_BITS(instr);
+ unsigned int ua_flags;
+ bool ua_saved = false;

ai_half += 1;

@@ -364,10 +366,11 @@ do_alignment_ldrhstrh(unsigned long addr, u32 instr, struct pt_regs *regs)
user:
if (LDST_L_BIT(instr)) {
unsigned long val;
- unsigned int __ua_flags = uaccess_save_and_enable();

+ ua_flags = uaccess_save_and_enable();
+ ua_saved = true;
get16t_unaligned_check(val, addr);
- uaccess_restore(__ua_flags);
+ uaccess_restore(ua_flags);

/* signed half-word? */
if (instr & 0x40)
@@ -375,14 +378,17 @@ do_alignment_ldrhstrh(unsigned long addr, u32 instr, struct pt_regs *regs)

regs->uregs[rd] = val;
} else {
- unsigned int __ua_flags = uaccess_save_and_enable();
+ ua_flags = uaccess_save_and_enable();
+ ua_saved = true;
put16t_unaligned_check(regs->uregs[rd], addr);
- uaccess_restore(__ua_flags);
+ uaccess_restore(ua_flags);
}

return TYPE_LDST;

fault:
+ if (ua_saved)
+ uaccess_restore(ua_flags);
return TYPE_FAULT;
}

@@ -391,6 +397,8 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs)
{
unsigned int rd = RD_BITS(instr);
unsigned int rd2;
+ unsigned int ua_flags;
+ bool ua_saved = false;
int load;

if ((instr & 0xfe000000) == 0xe8000000) {
@@ -425,26 +433,30 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs)
user:
if (load) {
unsigned long val, val2;
- unsigned int __ua_flags = uaccess_save_and_enable();

+ ua_flags = uaccess_save_and_enable();
+ ua_saved = true;
get32t_unaligned_check(val, addr);
get32t_unaligned_check(val2, addr + 4);

- uaccess_restore(__ua_flags);
+ uaccess_restore(ua_flags);

regs->uregs[rd] = val;
regs->uregs[rd2] = val2;
} else {
- unsigned int __ua_flags = uaccess_save_and_enable();
+ ua_flags = uaccess_save_and_enable();
+ ua_saved = true;
put32t_unaligned_check(regs->uregs[rd], addr);
put32t_unaligned_check(regs->uregs[rd2], addr + 4);
- uaccess_restore(__ua_flags);
+ uaccess_restore(ua_flags);
}

return TYPE_LDST;
bad:
return TYPE_ERROR;
fault:
+ if (ua_saved)
+ uaccess_restore(ua_flags);
return TYPE_FAULT;
}

@@ -452,6 +464,8 @@ static int
do_alignment_ldrstr(unsigned long addr, u32 instr, struct pt_regs *regs)
{
unsigned int rd = RD_BITS(instr);
+ unsigned int ua_flags;
+ bool ua_saved = false;

ai_word += 1;

@@ -469,18 +483,22 @@ do_alignment_ldrstr(unsigned long addr, u32 instr, struct pt_regs *regs)
trans:
if (LDST_L_BIT(instr)) {
unsigned int val;
- unsigned int __ua_flags = uaccess_save_and_enable();
+ ua_flags = uaccess_save_and_enable();
+ ua_saved = true;
get32t_unaligned_check(val, addr);
- uaccess_restore(__ua_flags);
+ uaccess_restore(ua_flags);
regs->uregs[rd] = val;
} else {
- unsigned int __ua_flags = uaccess_save_and_enable();
+ ua_flags = uaccess_save_and_enable();
+ ua_saved = true;
put32t_unaligned_check(regs->uregs[rd], addr);
- uaccess_restore(__ua_flags);
+ uaccess_restore(ua_flags);
}
return TYPE_LDST;

fault:
+ if (ua_saved)
+ uaccess_restore(ua_flags);
return TYPE_FAULT;
}

@@ -502,6 +520,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs)
{
unsigned int rd, rn, correction, nr_regs, regbits;
unsigned long eaddr, newaddr;
+ unsigned int ua_flags;
+ bool ua_saved = false;

if (LDM_S_BIT(instr))
goto bad;
@@ -547,7 +567,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs)
#endif

if (user_mode(regs)) {
- unsigned int __ua_flags = uaccess_save_and_enable();
+ ua_flags = uaccess_save_and_enable();
+ ua_saved = true;
for (regbits = REGMASK_BITS(instr), rd = 0; regbits;
regbits >>= 1, rd += 1)
if (regbits & 1) {
@@ -559,7 +580,7 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs)
put32t_unaligned_check(regs->uregs[rd], eaddr);
eaddr += 4;
}
- uaccess_restore(__ua_flags);
+ uaccess_restore(ua_flags);
} else {
for (regbits = REGMASK_BITS(instr), rd = 0; regbits;
regbits >>= 1, rd += 1)
@@ -581,6 +602,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs)
return TYPE_DONE;

fault:
+ if (ua_saved)
+ uaccess_restore(ua_flags);
regs->ARM_pc -= correction;
return TYPE_FAULT;

--
2.53.0