* proposal to fix CVE-2026-23346 on 6.12 or older kernel
@ 2026-05-20 9:13 Xiangyu Chen
2026-05-20 9:13 ` [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot() Xiangyu Chen
0 siblings, 1 reply; 4+ messages in thread
From: Xiangyu Chen @ 2026-05-20 9:13 UTC (permalink / raw)
To: will; +Cc: catalin.marinas, stable, gregkh
Hi Will,
I am looking a CVE issue(CVE-2026-23346) on stable kernel. It was fixed by
commit:8f098037139b ("arm64: io: Extract user memory type in ioremap_prot()").
I have already reproduced it on 6.12 and 6.6 stable kernel[1], but
directly porting the upstream patch's macro changes inside <asm/io.h> creates
circular build dependencies due to the architecture-specific GENERIC_IOREMAP
refactoring introduced in the stable kernel lifecycle.
Since this issue was triggered by generic_access_phys() passes a 'pgprot_t'
value determined from the user mapping of the target 'pfn' being accessed by the kernel.
On arm64, this 'pgprot_t' contains all non-address bits from the pte, including user
permission controls (PTE_USER). When a process attempts to read the target memory via
cross-process subsystems (such as reading /proc/<pid>/mem or via ptrace), the kernel
re-maps this memory using ioremap_prot(). Since the PTE_USER bit is incorrectly preserved
in the temporary kernel-space mapping, it triggers a level 3 permission fault on systems
with PAN (Privileged Access Never) enabled, resulting in an immediate kernel panic.
To bypass header dependency traps safely, I have changed the backport code, this backport
confines the fix entirely inside the implementation layer of arch/arm64/mm/ioremap.c:
1. It uses pgprot_val() to safely unpack page properties into a pteval_t mask.
2. It introduces a targeted safety check (if (prot_val & PTE_USER)) to
selectively strip away volatile user permission parameters.
3. It maps the memory through pure kernel attributes, leaving standard
peripheral device drivers completely unaffected.
(see [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot() ).
The code I have verified on qemuarm64, after applied the fix, crash won't happen anymore.
So, could you help to review, if we can use this solution to fix this CVE on an older stable
kernel? Thanks.
[1] Test steps:
1.1 Code & script:
C code:
------
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
// Using QEMU RTC HW address
#define PHYSICAL_ADDR 0x09010000
#define MAP_SIZE 4096
int main() {
int i = 180;
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("failed to open /dev/mem");
return 1;
}
// Start map
void *map_base = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PHYSICAL_ADDR);
if (map_base == MAP_FAILED) {
perror("failed to mmap");
close(fd);
return 1;
}
printf("Put those info to script PID:%d, VADDR:%p\n", getpid(), map_base);
// Keep running for script trigger the issue.
while(i > 0) {
sleep(1);
i--;
}
munmap(map_base, MAP_SIZE);
close(fd);
return 0;
}
------ End of C code ------
Python code:
-----------
python3 -c '
pid = 506
vaddr = 0x7f9de3f000
f = open(f"/proc/{pid}/mem", "rb")
f.seek(vaddr)
f.read(4)
'
------End of Python code------
1.2 Usage:
Ensure kernel enabled the CONFIG_ARM64_PAN and CONFIG_DEVMEM
Start Qemuarm64 with -cpu cortex-a55 -M virt, e.g.:
qemu-system-aarch64 \
-cpu cortex-a55 \
-M virt \
-m 2G \
-smp 2 \
-kernel ./arch/arm64/boot/Image \
-append "console=ttyAMA0 root=/dev/vda rw earlycon" \
-drive if=none,file=rootfs.img,id=hd0,format=raw \
-device virtio-blk-device,drive=hd0 \
-nographic
After enter the qemu, using cat /proc/iomem to get a vaild MMIO
address, here is using RTC address (#define PHYSICAL_ADDR 0x09010000).
Build the C code, put the binary to target qemu system and run it, the
C reproducer would output the PID and mapped address, here example pid
is 506 and virtual address is 0x7f9de3f000.
Fill pid and virtual address to python code, without fix, kernel would crash
with "Unable to handle kernel read from unreadable memory".
Based on crash info, pstate: 20400005 (... +PAN ...) and FSC = 0x0f: level 3 permission fault
Call trace:
[ 678.563102] __memcpy_fromio+0x50/0x98
[ 678.563436] __access_remote_vm+0x294/0x3a8
[ 678.563901] access_remote_vm+0x18/0x30
[ 678.564308] mem_rw+0x1e0/0x370
[ 678.564534] mem_read+0x1c/0x30
[ 678.564754] vfs_read+0xcc/0x2d0
[ 678.564975] ksys_read+0x7c/0x120
[ 678.565192] __arm64_sys_read+0x24/0x38
[ 678.565450] invoke_syscall+0x5c/0x138
[ 678.565729] el0_svc_common.constprop.0+0x48/0xf0
[ 678.566038] do_el0_svc+0x24/0x38
[ 678.566264] el0_svc+0x38/0x108
[ 678.566514] el0t_64_sync_handler+0x120/0x130
[ 678.566823] el0t_64_sync+0x190/0x198
....
The behavior is the same as description of
commit:8f098037139b ("arm64: io: Extract user memory type in ioremap_prot()")
Thanks!
Br,
Xiangyu
Xiangyu Chen (1):
arm64: io: correct user memory type in ioremap_prot()
arch/arm64/mm/ioremap.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot()
2026-05-20 9:13 proposal to fix CVE-2026-23346 on 6.12 or older kernel Xiangyu Chen
@ 2026-05-20 9:13 ` Xiangyu Chen
2026-05-20 11:17 ` Greg KH
2026-05-20 17:01 ` Catalin Marinas
0 siblings, 2 replies; 4+ messages in thread
From: Xiangyu Chen @ 2026-05-20 9:13 UTC (permalink / raw)
To: will; +Cc: catalin.marinas, stable, gregkh
generic_access_phys() passes a 'pgprot_t' value determined from the
user mapping of the target 'pfn' being accessed by the kernel.
On arm64, this 'pgprot_t' contains all non-address bits from the pte,
including user permission controls (PTE_USER).
When a process attempts to read the target memory via cross-process
subsystems (such as reading /proc/<pid>/mem or via ptrace), the kernel
re-maps this memory using ioremap_prot(). Since the PTE_USER bit is
incorrectly preserved in the temporary kernel-space mapping, it triggers
a level 3 permission fault on systems with PAN (Privileged Access Never)
enabled, resulting in an immediate kernel panic.
Upstream already fixed this issue in
commit: 8f098037139b ("arm64: io: Extract user memory type in ioremap_prot()")
Directly porting the upstream patch's macro changes inside <asm/io.h>
creates circular build dependencies due to the architecture-specific
GENERIC_IOREMAP refactoring introduced in the stable kernel lifecycle.
To bypass header dependency traps safely, this backport confines the fix
entirely inside the implementation layer of arch/arm64/mm/ioremap.c:
1. It uses pgprot_val() to safely unpack page properties into a pteval_t mask.
2. It introduces a targeted safety check (if (prot_val & PTE_USER)) to
selectively strip away volatile user permission parameters.
3. It maps the memory through pure kernel attributes, leaving standard
peripheral device drivers completely unaffected.
Tested-by: QEMU ARM64 (Cortex-A55, CONFIG_ARM64_PAN=y, /proc/<pid>/mem read)
Fixes: 893dea9ccd08 ("arm64: Add HAVE_IOREMAP_PROT support")
Signed-off-by: Xiangyu Chen <xiangyu.chen@windriver.com>
---
arch/arm64/mm/ioremap.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/mm/ioremap.c b/arch/arm64/mm/ioremap.c
index 6cc0b7e7eb03..48a16a360b42 100644
--- a/arch/arm64/mm/ioremap.c
+++ b/arch/arm64/mm/ioremap.c
@@ -19,6 +19,7 @@ void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
{
unsigned long last_addr = phys_addr + size - 1;
pgprot_t pgprot = __pgprot(prot);
+ pteval_t prot_val = pgprot_val(pgprot);
/* Don't allow outside PHYS_MASK */
if (last_addr & ~PHYS_MASK)
@@ -27,7 +28,6 @@ void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
/* Don't allow RAM to be mapped. */
if (WARN_ON(pfn_is_map_memory(__phys_to_pfn(phys_addr))))
return NULL;
-
/*
* If a hook is registered (e.g. for confidential computing
* purposes), call that now and barf if it fails.
@@ -37,6 +37,15 @@ void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
return NULL;
}
+ /*
+ * If this is a user mapping (from generic_access_phys), extract
+ * only the memory type and drop permission bits to avoid PAN faults.
+ */
+ if (prot_val & PTE_USER) {
+ pgprot = __pgprot_modify(PAGE_KERNEL, PTE_ATTRINDX_MASK,
+ prot_val & PTE_ATTRINDX_MASK);
+ }
+
return generic_ioremap_prot(phys_addr, size, pgprot);
}
EXPORT_SYMBOL(ioremap_prot);
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot()
2026-05-20 9:13 ` [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot() Xiangyu Chen
@ 2026-05-20 11:17 ` Greg KH
2026-05-20 17:01 ` Catalin Marinas
1 sibling, 0 replies; 4+ messages in thread
From: Greg KH @ 2026-05-20 11:17 UTC (permalink / raw)
To: Xiangyu Chen; +Cc: will, catalin.marinas, stable
On Wed, May 20, 2026 at 05:13:37PM +0800, Xiangyu Chen wrote:
> @@ -27,7 +28,6 @@ void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
> /* Don't allow RAM to be mapped. */
> if (WARN_ON(pfn_is_map_memory(__phys_to_pfn(phys_addr))))
> return NULL;
> -
> /*
> * If a hook is registered (e.g. for confidential computing
> * purposes), call that now and barf if it fails.
Why remove this line?
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot()
2026-05-20 9:13 ` [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot() Xiangyu Chen
2026-05-20 11:17 ` Greg KH
@ 2026-05-20 17:01 ` Catalin Marinas
1 sibling, 0 replies; 4+ messages in thread
From: Catalin Marinas @ 2026-05-20 17:01 UTC (permalink / raw)
To: Xiangyu Chen; +Cc: will, stable, gregkh
On Wed, May 20, 2026 at 05:13:37PM +0800, Xiangyu Chen wrote:
> generic_access_phys() passes a 'pgprot_t' value determined from the
> user mapping of the target 'pfn' being accessed by the kernel.
> On arm64, this 'pgprot_t' contains all non-address bits from the pte,
> including user permission controls (PTE_USER).
>
> When a process attempts to read the target memory via cross-process
> subsystems (such as reading /proc/<pid>/mem or via ptrace), the kernel
> re-maps this memory using ioremap_prot(). Since the PTE_USER bit is
> incorrectly preserved in the temporary kernel-space mapping, it triggers
> a level 3 permission fault on systems with PAN (Privileged Access Never)
> enabled, resulting in an immediate kernel panic.
>
> Upstream already fixed this issue in
> commit: 8f098037139b ("arm64: io: Extract user memory type in ioremap_prot()")
>
> Directly porting the upstream patch's macro changes inside <asm/io.h>
> creates circular build dependencies due to the architecture-specific
> GENERIC_IOREMAP refactoring introduced in the stable kernel lifecycle.
>
> To bypass header dependency traps safely, this backport confines the fix
> entirely inside the implementation layer of arch/arm64/mm/ioremap.c:
> 1. It uses pgprot_val() to safely unpack page properties into a pteval_t mask.
> 2. It introduces a targeted safety check (if (prot_val & PTE_USER)) to
> selectively strip away volatile user permission parameters.
> 3. It maps the memory through pure kernel attributes, leaving standard
> peripheral device drivers completely unaffected.
>
> Tested-by: QEMU ARM64 (Cortex-A55, CONFIG_ARM64_PAN=y, /proc/<pid>/mem read)
> Fixes: 893dea9ccd08 ("arm64: Add HAVE_IOREMAP_PROT support")
> Signed-off-by: Xiangyu Chen <xiangyu.chen@windriver.com>
Instead of re-implementing this, could we cherry-pick the prior commit
renaming ioremap_prot() to __ioremap_prot() throughout arm64? It's not a
straightforward cherry-pick since we changed the prot arg from unsigned
long to pgprot_t (across multiple architectures), but with some minor
tweaks we can get the patch below. After this, 8f098037139b should apply
(hopefully unmodified). Please give it a try:
--------------------8<------------------------------------
From a38c5529973892914c1c967d43a2abcdcd9c6287 Mon Sep 17 00:00:00 2001
From: Will Deacon <will@kernel.org>
Date: Mon, 23 Feb 2026 22:10:10 +0000
Subject: [PATCH 1/2] arm64: io: Rename ioremap_prot() to __ioremap_prot()
commit f6bf47ab32e0863df50f5501d207dcdddb7fc507 upstream.
Rename our ioremap_prot() implementation to __ioremap_prot() and convert
all arch-internal callers over to the new function.
On this 6.12 branch, ioremap_prot() remains as an exported wrapper around
__ioremap_prot(), since the generic ioremap_prot() prototype still takes an
unsigned long protection value. The wrapper keeps the existing behaviour and
will be subsequently extended to handle user permissions in 'prot'.
Cc: Zeng Heng <zengheng4@huawei.com>
Cc: Jinjiang Tu <tujinjiang@huawei.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
arch/arm64/include/asm/io.h | 7 ++++---
arch/arm64/kernel/acpi.c | 2 +-
arch/arm64/mm/ioremap.c | 12 +++++++++---
3 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h
index 1ada23a6ec19..e6ad41131d80 100644
--- a/arch/arm64/include/asm/io.h
+++ b/arch/arm64/include/asm/io.h
@@ -274,15 +274,16 @@ __iowrite64_copy(void __iomem *to, const void *from, size_t count)
typedef int (*ioremap_prot_hook_t)(phys_addr_t phys_addr, size_t size,
pgprot_t *prot);
int arm64_ioremap_prot_hook_register(const ioremap_prot_hook_t hook);
+void __iomem *__ioremap_prot(phys_addr_t phys, size_t size, pgprot_t prot);
#define ioremap_prot ioremap_prot
#define _PAGE_IOREMAP PROT_DEVICE_nGnRE
#define ioremap_wc(addr, size) \
- ioremap_prot((addr), (size), PROT_NORMAL_NC)
+ __ioremap_prot((addr), (size), __pgprot(PROT_NORMAL_NC))
#define ioremap_np(addr, size) \
- ioremap_prot((addr), (size), PROT_DEVICE_nGnRnE)
+ __ioremap_prot((addr), (size), __pgprot(PROT_DEVICE_nGnRnE))
/*
* io{read,write}{16,32,64}be() macros
@@ -303,7 +304,7 @@ static inline void __iomem *ioremap_cache(phys_addr_t addr, size_t size)
if (pfn_is_map_memory(__phys_to_pfn(addr)))
return (void __iomem *)__phys_to_virt(addr);
- return ioremap_prot(addr, size, PROT_NORMAL);
+ return __ioremap_prot(addr, size, __pgprot(PROT_NORMAL));
}
/*
diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c
index e6f66491fbe9..a99476819e6b 100644
--- a/arch/arm64/kernel/acpi.c
+++ b/arch/arm64/kernel/acpi.c
@@ -379,7 +379,7 @@ void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size)
prot = __acpi_get_writethrough_mem_attribute();
}
}
- return ioremap_prot(phys, size, pgprot_val(prot));
+ return __ioremap_prot(phys, size, prot);
}
/*
diff --git a/arch/arm64/mm/ioremap.c b/arch/arm64/mm/ioremap.c
index 6cc0b7e7eb03..ca008a4732ae 100644
--- a/arch/arm64/mm/ioremap.c
+++ b/arch/arm64/mm/ioremap.c
@@ -14,11 +14,10 @@ int arm64_ioremap_prot_hook_register(ioremap_prot_hook_t hook)
return 0;
}
-void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
- unsigned long prot)
+void __iomem *__ioremap_prot(phys_addr_t phys_addr, size_t size,
+ pgprot_t pgprot)
{
unsigned long last_addr = phys_addr + size - 1;
- pgprot_t pgprot = __pgprot(prot);
/* Don't allow outside PHYS_MASK */
if (last_addr & ~PHYS_MASK)
@@ -39,6 +38,13 @@ void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
return generic_ioremap_prot(phys_addr, size, pgprot);
}
+EXPORT_SYMBOL(__ioremap_prot);
+
+void __iomem *ioremap_prot(phys_addr_t phys_addr, size_t size,
+ unsigned long prot)
+{
+ return __ioremap_prot(phys_addr, size, __pgprot(prot));
+}
EXPORT_SYMBOL(ioremap_prot);
/*
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-05-20 17:01 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-20 9:13 proposal to fix CVE-2026-23346 on 6.12 or older kernel Xiangyu Chen
2026-05-20 9:13 ` [PATCH 6.12 1/1] arm64: io: correct user memory type in ioremap_prot() Xiangyu Chen
2026-05-20 11:17 ` Greg KH
2026-05-20 17:01 ` Catalin Marinas
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox