]> git.baikalelectronics.ru Git - kernel.git/commitdiff
ARM: 8898/1: mm: Don't treat faults reported from cache maintenance as writes
authorWill Deacon <will@kernel.org>
Thu, 8 Aug 2019 15:51:00 +0000 (16:51 +0100)
committerRussell King <rmk+kernel@armlinux.org.uk>
Fri, 23 Aug 2019 10:39:34 +0000 (11:39 +0100)
Translation faults arising from cache maintenance instructions are
rather unhelpfully reported with an FSR value where the WnR field is set
to 1, indicating that the faulting access was a write. Since cache
maintenance instructions on 32-bit ARM do not require any particular
permissions, this can cause our private 'cacheflush' system call to fail
spuriously if a translation fault is generated due to page aging when
targetting a read-only VMA.

In this situation, we will return -EFAULT to userspace, although this is
unfortunately suppressed by the popular '__builtin___clear_cache()'
intrinsic provided by GCC, which returns void.

Although it's tempting to write this off as a userspace issue, we can
actually do a little bit better on CPUs that support LPAE, even if the
short-descriptor format is in use. On these CPUs, cache maintenance
faults additionally set the CM field in the FSR, which we can use to
suppress the write permission checks in the page fault handler and
succeed in performing cache maintenance to read-only areas even in the
presence of a translation fault.

Reported-by: Orion Hodson <oth@google.com>
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
arch/arm/mm/fault.c
arch/arm/mm/fault.h

index 890eeaac3cbbaea361c7771b446ba5cc3f68cd05..bd0f4821f7e11fa52b20e73fd1c0ea56bab728e6 100644 (file)
@@ -191,7 +191,7 @@ static inline bool access_error(unsigned int fsr, struct vm_area_struct *vma)
 {
        unsigned int mask = VM_READ | VM_WRITE | VM_EXEC;
 
-       if (fsr & FSR_WRITE)
+       if ((fsr & FSR_WRITE) && !(fsr & FSR_CM))
                mask = VM_WRITE;
        if (fsr & FSR_LNX_PF)
                mask = VM_EXEC;
@@ -262,7 +262,7 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 
        if (user_mode(regs))
                flags |= FAULT_FLAG_USER;
-       if (fsr & FSR_WRITE)
+       if ((fsr & FSR_WRITE) && !(fsr & FSR_CM))
                flags |= FAULT_FLAG_WRITE;
 
        /*
index c063708fa5032a5b4393be25ecfdc886d534aa31..9ecc2097a87a07e0c03bb5c915a1e175664db961 100644 (file)
@@ -6,6 +6,7 @@
  * Fault status register encodings.  We steal bit 31 for our own purposes.
  */
 #define FSR_LNX_PF             (1 << 31)
+#define FSR_CM                 (1 << 13)
 #define FSR_WRITE              (1 << 11)
 #define FSR_FS4                        (1 << 10)
 #define FSR_FS3_0              (15)