]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
parisc: Check region is readable by user in raw_copy_from_user()
authorJohn David Anglin <dave.anglin@bell.net>
Mon, 21 Jul 2025 19:39:26 +0000 (15:39 -0400)
committerHelge Deller <deller@gmx.de>
Fri, 25 Jul 2025 20:45:23 +0000 (22:45 +0200)
Because of the way the _PAGE_READ is handled in the parisc PTE, an
access interruption is not generated when the kernel reads from a
region where the _PAGE_READ is zero. The current code was written
assuming read access faults would also occur in the kernel.

This change adds user access checks to raw_copy_from_user().  The
prober_user() define checks whether user code has read access to
a virtual address. Note that page faults are not handled in the
exception support for the probe instruction. For this reason, we
precede the probe by a ldb access check.

Signed-off-by: John David Anglin <dave.anglin@bell.net>
Signed-off-by: Helge Deller <deller@gmx.de>
Cc: stable@vger.kernel.org # v5.12+
arch/parisc/include/asm/special_insns.h
arch/parisc/lib/memcpy.c

index 51f40eaf7780659263f37b7c10fa7bd4ecf4ced7..1013eeba31e5bbcb6c0496b7ca4397ab2d2f089e 100644 (file)
        pa;                                             \
 })
 
+/**
+ * prober_user() - Probe user read access
+ * @sr:                Space regster.
+ * @va:                Virtual address.
+ *
+ * Return: Non-zero if address is accessible.
+ *
+ * Due to the way _PAGE_READ is handled in TLB entries, we need
+ * a special check to determine whether a user address is accessible.
+ * The ldb instruction does the initial access check. If it is
+ * successful, the probe instruction checks user access rights.
+ */
+#define prober_user(sr, va)    ({                      \
+       unsigned long read_allowed;                     \
+       __asm__ __volatile__(                           \
+               "copy %%r0,%0\n"                        \
+               "8:\tldb 0(%%sr%1,%2),%%r0\n"           \
+               "\tproberi (%%sr%1,%2),%3,%0\n"         \
+               "9:\n"                                  \
+               ASM_EXCEPTIONTABLE_ENTRY(8b, 9b,        \
+                               "or %%r0,%%r0,%%r0")    \
+               : "=&r" (read_allowed)                  \
+               : "i" (sr), "r" (va), "i" (PRIV_USER)   \
+               : "memory"                              \
+       );                                              \
+       read_allowed;                                   \
+})
+
 #define CR_EIEM 15     /* External Interrupt Enable Mask */
 #define CR_CR16 16     /* CR16 Interval Timer */
 #define CR_EIRR 23     /* External Interrupt Request Register */
index 5fc0c852c84c8dc28b44accd2bf00d249649cb45..69d65ffab312634b7e3ef3a4548d1a52359f53fe 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/compiler.h>
 #include <linux/uaccess.h>
+#include <linux/mm.h>
 
 #define get_user_space()       mfsp(SR_USER)
 #define get_kernel_space()     SR_KERNEL
@@ -32,9 +33,25 @@ EXPORT_SYMBOL(raw_copy_to_user);
 unsigned long raw_copy_from_user(void *dst, const void __user *src,
                               unsigned long len)
 {
+       unsigned long start = (unsigned long) src;
+       unsigned long end = start + len;
+       unsigned long newlen = len;
+
        mtsp(get_user_space(), SR_TEMP1);
        mtsp(get_kernel_space(), SR_TEMP2);
-       return pa_memcpy(dst, (void __force *)src, len);
+
+       /* Check region is user accessible */
+       if (start)
+       while (start < end) {
+               if (!prober_user(SR_TEMP1, start)) {
+                       newlen = (start - (unsigned long) src);
+                       break;
+               }
+               start += PAGE_SIZE;
+               /* align to page boundry which may have different permission */
+               start = PAGE_ALIGN_DOWN(start);
+       }
+       return len - newlen + pa_memcpy(dst, (void __force *)src, newlen);
 }
 EXPORT_SYMBOL(raw_copy_from_user);