summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn David Anglin <dave.anglin@bell.net>2025-07-21 15:39:26 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2025-08-28 16:31:00 +0200
commit537264f4b3dc6db52161e91b2678a4285e223aa2 (patch)
tree9d571e3de6c80dd0cbb3411b4a713c213e461e89
parenta2513b82fe7a8dcf5dca4bab8fa7d4b88ce7d861 (diff)
parisc: Check region is readable by user in raw_copy_from_user()
commit 91428ca9320edbab1211851d82429d33b9cd73ef upstream. 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+ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--arch/parisc/include/asm/special_insns.h28
-rw-r--r--arch/parisc/lib/memcpy.c19
2 files changed, 46 insertions, 1 deletions
diff --git a/arch/parisc/include/asm/special_insns.h b/arch/parisc/include/asm/special_insns.h
index 51f40eaf7780..1013eeba31e5 100644
--- a/arch/parisc/include/asm/special_insns.h
+++ b/arch/parisc/include/asm/special_insns.h
@@ -32,6 +32,34 @@
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 */
diff --git a/arch/parisc/lib/memcpy.c b/arch/parisc/lib/memcpy.c
index 5fc0c852c84c..69d65ffab312 100644
--- a/arch/parisc/lib/memcpy.c
+++ b/arch/parisc/lib/memcpy.c
@@ -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);