diff options
-rw-r--r-- | arch/x86/oprofile/backtrace.c | 46 |
1 files changed, 42 insertions, 4 deletions
diff --git a/arch/x86/oprofile/backtrace.c b/arch/x86/oprofile/backtrace.c index 3855096c59b8..98597818c1fd 100644 --- a/arch/x86/oprofile/backtrace.c +++ b/arch/x86/oprofile/backtrace.c @@ -11,6 +11,8 @@ #include <linux/oprofile.h> #include <linux/sched.h> #include <linux/mm.h> +#include <linux/highmem.h> + #include <asm/ptrace.h> #include <asm/uaccess.h> #include <asm/stacktrace.h> @@ -48,6 +50,42 @@ static struct stacktrace_ops backtrace_ops = { .walk_stack = print_context_stack, }; +/* from arch/x86/kernel/cpu/perf_event.c: */ + +/* + * best effort, GUP based copy_from_user() that assumes IRQ or NMI context + */ +static 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; + + 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, KM_USER0); + memcpy(to, map+offset, size); + kunmap_atomic(map, KM_USER0); + put_page(page); + + len += size; + to += size; + addr += size; + + } while (len < n); + + return len; +} + struct frame_head { struct frame_head *bp; unsigned long ret; @@ -55,12 +93,12 @@ struct frame_head { static struct frame_head *dump_user_backtrace(struct frame_head *head) { + /* Also check accessibility of one struct frame_head beyond: */ struct frame_head bufhead[2]; + unsigned long bytes; - /* Also check accessibility of one struct frame_head beyond */ - if (!access_ok(VERIFY_READ, head, sizeof(bufhead))) - return NULL; - if (__copy_from_user_inatomic(bufhead, head, sizeof(bufhead))) + bytes = copy_from_user_nmi(bufhead, head, sizeof(bufhead)); + if (bytes != sizeof(bufhead)) return NULL; oprofile_add_trace(bufhead[0].ret); |