cacheflush completely broken, suspecting PAN+LPAE
Subsystems (2)
Hi,
I installed v6.11.5 on Tegra K1 (Cortex-A15) with tegra_defconfing + CONFIG_ARM_LPAE + a few drivers + minor patches for driver issues.
gdb segfaults on startup, strace shows this:
openat(AT_FDCWD, "/usr/lib/guile/3.0/ccache/ice-9/psyntax-pp.go", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 11_llseek(11, 0, [477309], SEEK_END) = 0mmap2(NULL, 477309, PROT_READ, MAP_PRIVATE, 11, 0) = 0xb378a000
close(11) = 0mprotect(0xb37da000, 43512, PROT_READ|PROT_WRITE) = 0
mmap2(NULL, 262144, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb374a000 cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address) cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address) cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address)
futex(0xb6f0bb14, FUTEX_WAKE_PRIVATE, 2147483647) = 0cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address)cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x4} ---
guile is apparently a scripting language with JIT compiler, disabling JIT resolves the crash, so cacheflush is a big suspect at this point.
No apparent valid reason for those failures and no recent changes to cacheflush handling that I see, so I searched for commits touching LPAE (perhaps the most uncommon part of my system) and I quickly found the ARM_PAN feature, which claims to monkey with page tables. Hmm...
Disabled ARM_PAN, cacheflush returns 0 now and gdb crashes no more.
So I guess it looks like there is a problem with this feature, perhaps a missing "permit user accesss" somewhere?
Thanks,
Michal
Hi Michal,
Quoted 27 lines · Michał Pecio
I installed v6.11.5 on Tegra K1 (Cortex-A15) with
tegra_defconfing+CONFIG_ARM_LPAE+ a few drivers + minor patches for driver issues.gdb segfaults on startup, strace shows this:
openat(AT_FDCWD, "/usr/lib/guile/3.0/ccache/ice-9/psyntax-pp.go", O_RDONLY|O_LARGEFILE|O_CLOEXEC)= 11_llseek(11, 0, [477309], SEEK_END)= 0mmap2(NULL, 477309, PROT_READ, MAP_PRIVATE, 11, 0)=0xb378a000
close(11) = 0mprotect(0xb37da000, 43512, PROT_READ|PROT_WRITE)= 0mmap2(NULL, 262144, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb374a000 cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address) cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address) cacheflush(0xb374a000, 0xb374b000, 0) = -1 EFAULT (Bad address)
futex(0xb6f0bb14, FUTEX_WAKE_PRIVATE, 2147483647)= 0cacheflush(0xb374a000, 0xb374b000, 0)= -1 EFAULT (Bad address)cacheflush(0xb374a000, 0xb374b000, 0)= -1 EFAULT (Bad address)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x4} ---guile is apparently a scripting language with JIT compiler, disabling JIT resolves the crash, so cacheflush is a big suspect at this point.
No apparent valid reason for those failures and no recent changes to cacheflush handling that I see, so I searched for commits touching LPAE (perhaps the most uncommon part of my system) and I quickly found the
ARM_PANfeature, which claims to monkey with page tables. Hmm...
Quoted 4 lines
Disabled
ARM_PAN, cacheflush returns 0 now and gdb crashes no more.So I guess it looks like there is a problem with this feature, perhaps a missing "permit user accesss" somewhere?
We are trying to locate the issue, which I think is the same as this but not sure:
https://bugzilla.kernel.org/show_bug.cgi?id=219247
I have been trying to replicate it on a Chromebook but didn't get so far yet because the installation is pretty idiomatic :/ also there is only appears in a single Qt program and not as predictable as here.
But. It appears the code is issuing cacheflush() which I guess ends up in arm_syscall() here:
case NR(cacheflush):
return do_cache_op(regs->ARM_r0, regs->ARM_r1, regs->ARM_r2);To here:
static inline intdo_cache_op(unsigned long start, unsigned long end, int flags)
{
if (end < start || flags)
return -EINVAL;
if (!access_ok((void __user *)start, end - start))
return -EFAULT;
return __do_cache_op(start, end);
}Here userspace access should be fine because we have entered a syscall from userspace. I tried to emulate the situation with this program:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#define NR_cacheflush 0xf0002
/* libgcc */
extern void __clear_cache(void *, void *);
int main (int argc, char **argv) {
void *addr;
int ret;
printf("Test()\n");
addr = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
printf("mmap() failed\n");
exit(1);
}The libgcc version is what guile uses.
But it all just works fine for me.
I added prints in the cacheflush trap:
arch/arm/kernel/traps.c +4 -1
@@ -592,11 +592,14 @@ __do_cache_op(unsigned long start, unsigned long end)static inline int do_cache_op (unsigned long start,unsigned long end,int flags) { +pr_info ("%s(%08lx-%08lx)\n" , __func__, start, end);if (end < start || flags)return -EINVAL ; -if (!access_ok ((void __user *)start, end - start)) +if (!access_ok ((void __user *)start, end - start)) { +pr_err ("ACCESS NOT OK\n" );return -EFAULT ; + }return __do_cache_op (start, end); }
And they all work fine with the test program.
This is on v6.12-rc1.
Does this test program work also on your set-up?
Yours,
Linus Walleij
Quoted 3 lines · Linus Walleij
Quoted 1 line · Michał Pecio
cacheflush(0xb374a000, 0xb374b000, 0)= -1 EFAULT (Bad address)
...
Quoted 6 lines
Quoted 2 lines
So I guess it looks like there is a problem with this feature, perhaps a missing "permit user accesss" somewhere?
static inline int
do_cache_op(unsigned long start, unsigned long end, int flags)
Quoted 9 lines
{
if (end < start || flags)
return -EINVAL;
if (!access_ok((void __user *)start, end - start))
return -EFAULT;
return __do_cache_op(start, end);
}I would guess that the problem is not the access_ok() but the actual access in v7_coherent_user_range() that does not appear to call uaccess_save_and_enable() or its assembler equivalent around the lines
USER( mcr p15, 0, r12, c7, c11, 1 )
...
USER( mcr p15, 0, r12, c7, c5, 1 )Quoted 10 lines
arch/arm/kernel/traps.c +1
-0@@ -592,11 +592,14 @@ __do_cache_op(unsigned long start, unsigned long end)static inline int do_cache_op (unsigned long start,unsigned long end,int flags) { +pr_info ("%s(%08lx-%08lx)\n" , __func__, start, end);if (end < start || flags)
arch/arm/kernel/traps.c __do_cache_op(unsigned long start, unsigned long end) · line 605
Quoted 10 lines
arch/arm/kernel/traps.c +3
-1return -EINVAL ; -if (!access_ok ((void __user *)start, end - start)) +if (!access_ok ((void __user *)start, end - start)) { +pr_err ("ACCESS NOT OK\n" );return -EFAULT ; + }return __do_cache_op (start, end); }
This does not catch -EFAULT being returned from the USER() trap in __do_cache_op(), so I would expect it to trigger but also not to flush the caches.
It's unclear to me if this problem is specific to the TTBR0 PAN variant, or if it can also happen on any variant of the CPU_SW_DOMAIN_PAN. It seems unlikely that CPU_SW_DOMAIN_PAN has been broken for this long without anyone noticing, but I also don't see why it doesn't trap in the cache flush when the TTBR0 version does.
Arnd
Hi,
Quoted 9 lines · Arnd Bergmann
I would guess that the problem is not the
access_ok()but the actual access inv7_coherent_user_range()that does not appear to calluaccess_save_and_enable()or its assembler equivalent around the linesUSER( mcr p15, 0, r12, c7, c11, 1 ) ... USER( mcr p15, 0, r12, c7, c5, 1 )
Yes, this is what fails and where I got stuck tracing this code because I'm not exactly an ARM MMU wizard.
What I could tell is that this code hasn't changed much since the 3.10 vendor kernel which works fine with LPAE on the same CPU and userspace, so I started looking for recent changes in arch/arm and found PAN.
According to comments, this routine is meant to return an error when it catches a pagefault, so that's what I suppose happens here and that's why PAN immediately caught my attention.
Disabling PAN fixes cacheflush for me.
Quoted 6 lines
It's unclear to me if this problem is specific to the TTBR0 PAN variant, or if it can also happen on any variant of the
CPU_SW_DOMAIN_PAN. It seems unlikely thatCPU_SW_DOMAIN_PANhas been broken for this long without anyone noticing, but I also don't see why it doesn't trap in the cache flush when the TTBR0 version does.
I don't know, but I booted the first kernel image I made for this machine, which lacks LPAE and a few other things and barely works, but it runs gdb without issues (and cacheflush returns 0).
--- config.good 2024-11-11 20:22:16.604586266 +0100 +++ config.bad 2024-11-11 20:22:10.641511948 +0100 +CONFIG_ARCH_DMA_ADDR_T_64BIT=y +CONFIG_ARCH_HAS_PTE_SPECIAL=y +CONFIG_ARCH_SUPPORTS_HUGETLBFS=y +CONFIG_ARM_LPAE=y
+CONFIG_CFG80211_WEXT=y -CONFIG_CPU_SW_DOMAIN_PAN=y +CONFIG_CPU_TTBR0_PAN=y +CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y +CONFIG_HAVE_GUP_FAST=y +CONFIG_MMU_GATHER_RCU_TABLE_FREE=y +CONFIG_MMU_GATHER_TABLE_FREE=y -CONFIG_PGTABLE_LEVELS=2 +CONFIG_PGTABLE_LEVELS=3 +CONFIG_PHYS_ADDR_T_64BIT=y +CONFIG_SWIOTLB=y
+CONFIG_WEXT_CORE=y
+CONFIG_WEXT_PROC=y
+CONFIG_ZONE_DMA=y
Regards,
Michal
Hi Linus,
Quoted 3 lines · Linus Walleij
We are trying to locate the issue, which I think is the same as this but not sure:
https://bugzilla.kernel.org/show_bug.cgi?id=219247
You can verify by asking the reporter to run the crashing program under strace. If SIGSEGV follows a failed cacheflush, it's my bug most likely.
A straightforward repro of this bug:
gdb
GUILE_JIT_THRESHOLD=0 gdb
GUILE_JIT_THRESHOLD=-1 gdb
Expected outcome: segfault, segfault, shows command prompt.
Quoted 3 lines
I have been trying to replicate it on a Chromebook but didn't get so far yet because the installation is pretty idiomatic :/ also there is only appears in a single Qt program and not as predictable as here.
My bug also appears in a single program ;) This system works fine, but any JIT is broken by this kind of bug. The failure may be random if the caches resynchronize by a fluke, but with gdb it was every time so far.
Quoted 10 lines
But. It appears the code is issuing
cacheflush()which I guess ends up inarm_syscall()here:case NR(cacheflush): return do_cache_op(regs->ARM_r0, regs->ARM_r1, regs->ARM_r2);To here:
static inline int
do_cache_op(unsigned long start, unsigned long end, int flags)
Quoted 9 lines
{
if (end < start || flags)
return -EINVAL;
if (!access_ok((void __user *)start, end - start))
return -EFAULT;
return __do_cache_op(start, end);
}Yep. I added printks here and it is particularly the call to flush_icache_range() from __do_cache_op() which returns -EFAULT.
Quoted 20 lines
Here userspace access should be fine because we have entered a syscall from userspace. I tried to emulate the situation with this program:
#include <
stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>#define
NR_cacheflush0xf0002/* libgcc */
extern void__clear_cache(void *, void *);int main (int argc, char **argv) {
void *addr;
int ret;
Quoted 7 lines
printf("Test()\n");
addr = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
printf("mmap() failed\n");
exit(1);
}This seems incomplete, there is no __clear_cache(). But if you add it at the end then yes, it should fail. Confirm it with strace.
Quoted 12 lines
I added prints in the cacheflush trap:
arch/arm/kernel/traps.c +0
-0@@ -592,11 +592,14 @@ __do_cache_op(unsigned long start, unsignedlong end) static inline int
do_cache_op(unsigned long start, unsigned long end, int flags){arch/arm/kernel/traps.c +1
-0+pr_info ("%s(%08lx-%08lx)\n" , __func__, start, end);if (end < start || flags)
arch/arm/kernel/traps.c __do_cache_op(unsigned long start, unsigned long end) · line 605
Quoted 10 lines
arch/arm/kernel/traps.c +3
-1return -EINVAL ; -if (!access_ok ((void __user *)start, end - start)) +if (!access_ok ((void __user *)start, end - start)) { +pr_err ("ACCESS NOT OK\n" );return -EFAULT ; + }return __do_cache_op (start, end); }
You also need to check what __do_cache_op() returns.
Regards,
Michal
Regarding test programs, I also wrote and tried this one yesterday. It's based on a similar demo released by ARM, but much simplified.
It both triggers the bug and confirms the necessity of cacheflush in JIT compilers on my CPU when it works normally (prints: 1, 1, 2).
On the buggy kernel, it usually segfaults on the first attempt to call *code, but sometimes both __clear_cache() appear to take effect despite the syscall returning EFAULT (according to strace), not sure why.
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int f1() {
return 1;
}int f2() {
return 2;
}int main() {
puts("start");
char *code = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
int x;
memcpy(code, f1, 0x100);
__builtin___clear_cache(code, code + 0x100);
x = ((int(*)())code)();
printf("%x\n", x);
memcpy(code, f2, 0x100);
x = ((int(*)())code)();
printf("%x\n", x);
__builtin___clear_cache(code, code + 0x100);
x = ((int(*)())code)();
printf("%x\n", x);
}Russell King (Oracle)
maintainerQuoted 3 lines · Michał Pecio
Hi,
So I guess it looks like there is a problem with this feature, perhaps a missing "permit user accesss" somewhere?
That's exactly the reason - user access needs to be enabled before calling flush_icache_user_range() so that the cache operation instructions don't fault. The patch below should fix this.
Please ensure that you copy me with ARM related bugs in future.
Thanks for finding the issue.
8<===
From: "Russell King (Oracle)" <[email protected]>
Subject: [PATCH] ARM: fix cacheflush with PAN
It seems that the cacheflush syscall got broken when PAN was implemented. User access was not enabled around the cache maintenance instructions, causing them to fault.
--- arch/arm/kernel/traps.c | 3 +++ 1 file changed, 3 insertions(+)
arch/arm/kernel/traps.c +3 -0
@@ -574,6 +574,7 @@ static int bad_syscall(int n, struct pt_regs *regs)static inline int __do_cache_op (unsigned long start,unsigned long end) { +unsigned int ua_flags;int ret; do {
@@ -582,7 +583,9 @@ __do_cache_op(unsigned long start, unsigned long end)if (fatal_signal_pending (current))return 0 ; + ua_flags =uaccess_save_and_enable (); ret =flush_icache_user_range (start, start + chunk); +uaccess_restore (ua_flags);if (ret)return ret;
Signature · 5 lines
2.30.2 -- RMK's Patch system: https://www.armlinux.org.uk/developer/patches/ FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
Hi,
Quoted 8 lines · Russell King (Oracle)
Quoted 3 lines · Michał Pecio
Hi,
So I guess it looks like there is a problem with this feature, perhaps a missing "permit user accesss" somewhere?That's exactly the reason - user access needs to be enabled before calling
flush_icache_user_range()so that the cache operation instructions don't fault. The patch below should fix this.
Thanks, I will test it later this day.
By the way, do you know why it wasn't broken without LPAE? It looks like either those specific coprocessor operations somehow bypass the protection, or maybe all of PAN is a big, expensive no-op...
Quoted 1 line
Please ensure that you copy me with ARM related bugs in future.
OK, will do.
Regards,
Michal
Quoted 3 lines · Michał Pecio
By the way, do you know why it wasn't broken without LPAE? It looks like either those specific coprocessor operations somehow bypass the protection, or maybe all of PAN is a big, expensive no-op...
PAN is supposed to stop the kernel from reading or writing into userspace memory and that works.
Nobody really said we can't flush caches for some random userspace memory, and so, that works.
It's what we call a grey area.
But with the TTBR0 thing for PAN on LPAE we completely disable the page walks on userspace pages from the kernel (unless explicitly allowed) and that means this now also blocks cacheflush, because we can't flush caches for a piece of memory we don't even map.
Russell's patch looks like it fixes the issue, I was gonna write something similar but he quickly beat me to it, so test that!
Yours,
Linus Walleij
Michał Pecio
Tested-byQuoted 8 lines · Russell King (Oracle)
From: "Russell King (Oracle)" <[email protected]>
Subject: [PATCH] ARM: fix cacheflush with PANIt seems that the cacheflush syscall got broken when PAN was implemented. User access was not enabled around the cache maintenance instructions, causing them to fault.
Fixes a5e090acbf54 ("ARM: software-based priviledged-no-access support")
For the record, non-LPAE seems to be OK, so for me it was 7af5b901e847.
Quoted 1 line
Reported-by From: Michał Pecio <[email protected]>
"From:" perhaps not strictly necessary ;)
That was it. With this patch gdb starts normally, does 295 cacheflush calls and all of them succeed, with ARM_LPAE and ARM_PAN enabled.
Quoted 29 lines
Signed-off-by Russell King (Oracle) <[email protected]>--- arch/arm/kernel/traps.c | 3 +++ 1 file changed, 3 insertions(+)arch/arm/kernel/traps.c +3
-0@@ -574,6 +574,7 @@ static int bad_syscall(int n, struct pt_regs *regs)static inline int __do_cache_op (unsigned long start,unsigned long end) { +unsigned int ua_flags;int ret; do {@@ -582,7 +583,9 @@ __do_cache_op(unsigned long start, unsigned long end)if (fatal_signal_pending (current))return 0 ; + ua_flags =uaccess_save_and_enable (); ret =flush_icache_user_range (start, start + chunk); +uaccess_restore (ua_flags);if (ret)return ret;Signature · 1 line
2.30.2