summaryrefslogtreecommitdiff
path: root/arch/x86/lib/usercopy.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/lib/usercopy.c')
-rw-r--r--arch/x86/lib/usercopy.c76
1 files changed, 41 insertions, 35 deletions
diff --git a/arch/x86/lib/usercopy.c b/arch/x86/lib/usercopy.c
index 4f74d94c8d97..24b48af27417 100644
--- a/arch/x86/lib/usercopy.c
+++ b/arch/x86/lib/usercopy.c
@@ -4,46 +4,52 @@
* For licencing details see kernel-base/COPYING
*/
-#include <linux/highmem.h>
-#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/export.h>
+#include <linux/instrumented.h>
-#include <asm/word-at-a-time.h>
-#include <linux/sched.h>
+#include <asm/tlbflush.h>
-/*
- * best effort, GUP based copy_from_user() that is NMI-safe
+/**
+ * copy_from_user_nmi - NMI safe copy from user
+ * @to: Pointer to the destination buffer
+ * @from: Pointer to a user space address of the current task
+ * @n: Number of bytes to copy
+ *
+ * Returns: The number of not copied bytes. 0 is success, i.e. all bytes copied
+ *
+ * Contrary to other copy_from_user() variants this function can be called
+ * from NMI context. Despite the name it is not restricted to be called
+ * from NMI context. It is safe to be called from any other context as
+ * well. It disables pagefaults across the copy which means a fault will
+ * abort the copy.
+ *
+ * For NMI context invocations this relies on the nested NMI work to allow
+ * atomic faults from the NMI path; the nested NMI paths are careful to
+ * preserve CR2.
*/
unsigned long
copy_from_user_nmi(void *to, const void __user *from, unsigned long n)
{
- unsigned long offset, addr = (unsigned long)from;
- unsigned long size, len = 0;
- struct page *page;
- void *map;
- int ret;
-
- if (__range_not_ok(from, n, TASK_SIZE))
- return len;
-
- do {
- ret = __get_user_pages_fast(addr, 1, 0, &page);
- if (!ret)
- break;
-
- offset = addr & (PAGE_SIZE - 1);
- size = min(PAGE_SIZE - offset, n - len);
-
- map = kmap_atomic(page);
- memcpy(to, map+offset, size);
- kunmap_atomic(map);
- put_page(page);
-
- len += size;
- to += size;
- addr += size;
-
- } while (len < n);
-
- return len;
+ unsigned long ret;
+
+ if (!__access_ok(from, n))
+ return n;
+
+ if (!nmi_uaccess_okay())
+ return n;
+
+ /*
+ * Even though this function is typically called from NMI/IRQ context
+ * disable pagefaults so that its behaviour is consistent even when
+ * called from other contexts.
+ */
+ pagefault_disable();
+ instrument_copy_from_user_before(to, from, n);
+ ret = raw_copy_from_user(to, from, n);
+ instrument_copy_from_user_after(to, from, n, ret);
+ pagefault_enable();
+
+ return ret;
}
EXPORT_SYMBOL_GPL(copy_from_user_nmi);