aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2015-09-30 04:38:22 -0400
committerThomas Gleixner <tglx@linutronix.de>2015-09-30 15:51:34 -0400
commiteddd3826a1a0190e5235703d1e666affa4d13b96 (patch)
tree65a236d7bfe928f827a49319f4be27f9d6cd3f21
parent1e034743e918d195d339af340ae933727c072bce (diff)
x86/process: Add proper bound checks in 64bit get_wchan()
Dmitry Vyukov reported the following using trinity and the memory error detector AddressSanitizer (https://code.google.com/p/address-sanitizer/wiki/AddressSanitizerForKernel). [ 124.575597] ERROR: AddressSanitizer: heap-buffer-overflow on address ffff88002e280000 [ 124.576801] ffff88002e280000 is located 131938492886538 bytes to the left of 28857600-byte region [ffffffff81282e0a, ffffffff82e0830a) [ 124.578633] Accessed by thread T10915: [ 124.579295] inlined in describe_heap_address ./arch/x86/mm/asan/report.c:164 [ 124.579295] #0 ffffffff810dd277 in asan_report_error ./arch/x86/mm/asan/report.c:278 [ 124.580137] #1 ffffffff810dc6a0 in asan_check_region ./arch/x86/mm/asan/asan.c:37 [ 124.581050] #2 ffffffff810dd423 in __tsan_read8 ??:0 [ 124.581893] #3 ffffffff8107c093 in get_wchan ./arch/x86/kernel/process_64.c:444 The address checks in the 64bit implementation of get_wchan() are wrong in several ways: - The lower bound of the stack is not the start of the stack page. It's the start of the stack page plus sizeof (struct thread_info) - The upper bound must be: top_of_stack - TOP_OF_KERNEL_STACK_PADDING - 2 * sizeof(unsigned long). The 2 * sizeof(unsigned long) is required because the stack pointer points at the frame pointer. The layout on the stack is: ... IP FP ... IP FP. So we need to make sure that both IP and FP are in the bounds. Fix the bound checks and get rid of the mix of numeric constants, u64 and unsigned long. Making all unsigned long allows us to use the same function for 32bit as well. Use READ_ONCE() when accessing the stack. This does not prevent a concurrent wakeup of the task and the stack changing, but at least it avoids TOCTOU. Also check task state at the end of the loop. Again that does not prevent concurrent changes, but it avoids walking for nothing. Add proper comments while at it. Reported-by: Dmitry Vyukov <dvyukov@google.com> Reported-by: Sasha Levin <sasha.levin@oracle.com> Based-on-patch-from: Wolfram Gloger <wmglo@dent.med.uni-muenchen.de> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Borislav Petkov <bp@alien8.de> Reviewed-by: Dmitry Vyukov <dvyukov@google.com> Cc: Andrey Ryabinin <ryabinin.a.a@gmail.com> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Andrey Konovalov <andreyknvl@google.com> Cc: Kostya Serebryany <kcc@google.com> Cc: Alexander Potapenko <glider@google.com> Cc: kasan-dev <kasan-dev@googlegroups.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: Andi Kleen <ak@linux.intel.com> Cc: Wolfram Gloger <wmglo@dent.med.uni-muenchen.de> Cc: stable@vger.kernel.org Link: http://lkml.kernel.org/r/20150930083302.694788319@linutronix.de Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r--arch/x86/kernel/process_64.c52
1 files changed, 42 insertions, 10 deletions
diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c
index 3c1bbcf12924..f1fd0889e8af 100644
--- a/arch/x86/kernel/process_64.c
+++ b/arch/x86/kernel/process_64.c
@@ -499,27 +499,59 @@ void set_personality_ia32(bool x32)
499} 499}
500EXPORT_SYMBOL_GPL(set_personality_ia32); 500EXPORT_SYMBOL_GPL(set_personality_ia32);
501 501
502/*
503 * Called from fs/proc with a reference on @p to find the function
504 * which called into schedule(). This needs to be done carefully
505 * because the task might wake up and we might look at a stack
506 * changing under us.
507 */
502unsigned long get_wchan(struct task_struct *p) 508unsigned long get_wchan(struct task_struct *p)
503{ 509{
504 unsigned long stack; 510 unsigned long start, bottom, top, sp, fp, ip;
505 u64 fp, ip;
506 int count = 0; 511 int count = 0;
507 512
508 if (!p || p == current || p->state == TASK_RUNNING) 513 if (!p || p == current || p->state == TASK_RUNNING)
509 return 0; 514 return 0;
510 stack = (unsigned long)task_stack_page(p); 515
511 if (p->thread.sp < stack || p->thread.sp >= stack+THREAD_SIZE) 516 start = (unsigned long)task_stack_page(p);
517 if (!start)
518 return 0;
519
520 /*
521 * Layout of the stack page:
522 *
523 * ----------- topmax = start + THREAD_SIZE - sizeof(unsigned long)
524 * PADDING
525 * ----------- top = topmax - TOP_OF_KERNEL_STACK_PADDING
526 * stack
527 * ----------- bottom = start + sizeof(thread_info)
528 * thread_info
529 * ----------- start
530 *
531 * The tasks stack pointer points at the location where the
532 * framepointer is stored. The data on the stack is:
533 * ... IP FP ... IP FP
534 *
535 * We need to read FP and IP, so we need to adjust the upper
536 * bound by another unsigned long.
537 */
538 top = start + THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING;
539 top -= 2 * sizeof(unsigned long);
540 bottom = start + sizeof(struct thread_info);
541
542 sp = READ_ONCE(p->thread.sp);
543 if (sp < bottom || sp > top)
512 return 0; 544 return 0;
513 fp = *(u64 *)(p->thread.sp); 545
546 fp = READ_ONCE(*(unsigned long *)sp);
514 do { 547 do {
515 if (fp < (unsigned long)stack || 548 if (fp < bottom || fp > top)
516 fp >= (unsigned long)stack+THREAD_SIZE)
517 return 0; 549 return 0;
518 ip = *(u64 *)(fp+8); 550 ip = READ_ONCE(*(unsigned long *)(fp + sizeof(unsigned long)));
519 if (!in_sched_functions(ip)) 551 if (!in_sched_functions(ip))
520 return ip; 552 return ip;
521 fp = *(u64 *)fp; 553 fp = READ_ONCE(*(unsigned long *)fp);
522 } while (count++ < 16); 554 } while (count++ < 16 && p->state != TASK_RUNNING);
523 return 0; 555 return 0;
524} 556}
525 557