Re: [PATCH] x86, kasan: add KASAN checks to atomic operations

From: Dmitry Vyukov
Date: Mon Mar 06 2017 - 07:51:22 EST


On Mon, Mar 6, 2017 at 1:42 PM, Dmitry Vyukov <dvyukov@xxxxxxxxxx> wrote:
> KASAN uses compiler instrumentation to intercept all memory accesses.
> But it does not see memory accesses done in assembly code.
> One notable user of assembly code is atomic operations. Frequently,
> for example, an atomic reference decrement is the last access to an
> object and a good candidate for a racy use-after-free.
>
> Add manual KASAN checks to atomic operations.
> Note: we need checks only before asm blocks and don't need them
> in atomic functions composed of other atomic functions
> (e.g. load-cmpxchg loops).

Peter, also pointed me at arch/x86/include/asm/bitops.h. Will add them in v2.


> Signed-off-by: Dmitry Vyukov <dvyukov@xxxxxxxxxx>
> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
> Cc: Andrey Ryabinin <aryabinin@xxxxxxxxxxxxx>
> Cc: kasan-dev@xxxxxxxxxxxxxxxx
> Cc: linux-mm@xxxxxxxxx
> Cc: linux-kernel@xxxxxxxxxxxxxxx
>
> ---
> Within a day it has found its first bug:
>
> ==================================================================
> BUG: KASAN: use-after-free in atomic_dec_and_test
> arch/x86/include/asm/atomic.h:123 [inline] at addr ffff880079c30158
> BUG: KASAN: use-after-free in put_task_struct
> include/linux/sched/task.h:93 [inline] at addr ffff880079c30158
> BUG: KASAN: use-after-free in put_ctx+0xcf/0x110
> kernel/events/core.c:1131 at addr ffff880079c30158
> Write of size 4 by task syz-executor6/25698
> CPU: 2 PID: 25698 Comm: syz-executor6 Not tainted 4.10.0+ #302
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
> Call Trace:
> __dump_stack lib/dump_stack.c:16 [inline]
> dump_stack+0x2fb/0x3fd lib/dump_stack.c:52
> kasan_object_err+0x1c/0x90 mm/kasan/report.c:166
> print_address_description mm/kasan/report.c:208 [inline]
> kasan_report_error mm/kasan/report.c:292 [inline]
> kasan_report.part.2+0x1b0/0x460 mm/kasan/report.c:314
> kasan_report+0x21/0x30 mm/kasan/report.c:301
> check_memory_region_inline mm/kasan/kasan.c:326 [inline]
> check_memory_region+0x139/0x190 mm/kasan/kasan.c:333
> kasan_check_write+0x14/0x20 mm/kasan/kasan.c:344
> atomic_dec_and_test arch/x86/include/asm/atomic.h:123 [inline]
> put_task_struct include/linux/sched/task.h:93 [inline]
> put_ctx+0xcf/0x110 kernel/events/core.c:1131
> perf_event_release_kernel+0x3ad/0xc90 kernel/events/core.c:4322
> perf_release+0x37/0x50 kernel/events/core.c:4338
> __fput+0x332/0x800 fs/file_table.c:209
> ____fput+0x15/0x20 fs/file_table.c:245
> task_work_run+0x197/0x260 kernel/task_work.c:116
> exit_task_work include/linux/task_work.h:21 [inline]
> do_exit+0xb38/0x29c0 kernel/exit.c:880
> do_group_exit+0x149/0x420 kernel/exit.c:984
> get_signal+0x7e0/0x1820 kernel/signal.c:2318
> do_signal+0xd2/0x2190 arch/x86/kernel/signal.c:808
> exit_to_usermode_loop+0x200/0x2a0 arch/x86/entry/common.c:157
> syscall_return_slowpath arch/x86/entry/common.c:191 [inline]
> do_syscall_64+0x6fc/0x930 arch/x86/entry/common.c:286
> entry_SYSCALL64_slow_path+0x25/0x25
> RIP: 0033:0x4458d9
> RSP: 002b:00007f3f07187cf8 EFLAGS: 00000246 ORIG_RAX: 00000000000000ca
> RAX: fffffffffffffe00 RBX: 00000000007080c8 RCX: 00000000004458d9
> RDX: 0000000000000000 RSI: 0000000000000000 RDI: 00000000007080c8
> RBP: 00000000007080a8 R08: 0000000000000000 R09: 0000000000000000
> R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
> R13: 0000000000000000 R14: 00007f3f071889c0 R15: 00007f3f07188700
> Object at ffff880079c30140, in cache task_struct size: 5376
> Allocated:
> PID = 25681
> save_stack_trace+0x16/0x20 arch/x86/kernel/stacktrace.c:59
> save_stack+0x43/0xd0 mm/kasan/kasan.c:513
> set_track mm/kasan/kasan.c:525 [inline]
> kasan_kmalloc+0xaa/0xd0 mm/kasan/kasan.c:616
> kasan_slab_alloc+0x12/0x20 mm/kasan/kasan.c:555
> kmem_cache_alloc_node+0x122/0x6f0 mm/slab.c:3662
> alloc_task_struct_node kernel/fork.c:153 [inline]
> dup_task_struct kernel/fork.c:495 [inline]
> copy_process.part.38+0x19c8/0x4aa0 kernel/fork.c:1560
> copy_process kernel/fork.c:1531 [inline]
> _do_fork+0x200/0x1010 kernel/fork.c:1994
> SYSC_clone kernel/fork.c:2104 [inline]
> SyS_clone+0x37/0x50 kernel/fork.c:2098
> do_syscall_64+0x2e8/0x930 arch/x86/entry/common.c:281
> return_from_SYSCALL_64+0x0/0x7a
> Freed:
> PID = 25681
> save_stack_trace+0x16/0x20 arch/x86/kernel/stacktrace.c:59
> save_stack+0x43/0xd0 mm/kasan/kasan.c:513
> set_track mm/kasan/kasan.c:525 [inline]
> kasan_slab_free+0x6f/0xb0 mm/kasan/kasan.c:589
> __cache_free mm/slab.c:3514 [inline]
> kmem_cache_free+0x71/0x240 mm/slab.c:3774
> free_task_struct kernel/fork.c:158 [inline]
> free_task+0x151/0x1d0 kernel/fork.c:370
> copy_process.part.38+0x18e5/0x4aa0 kernel/fork.c:1931
> copy_process kernel/fork.c:1531 [inline]
> _do_fork+0x200/0x1010 kernel/fork.c:1994
> SYSC_clone kernel/fork.c:2104 [inline]
> SyS_clone+0x37/0x50 kernel/fork.c:2098
> do_syscall_64+0x2e8/0x930 arch/x86/entry/common.c:281
> return_from_SYSCALL_64+0x0/0x7a
> ---
> arch/x86/include/asm/atomic.h | 11 +++++++++++
> arch/x86/include/asm/atomic64_64.h | 10 ++++++++++
> arch/x86/include/asm/cmpxchg.h | 4 ++++
> 3 files changed, 25 insertions(+)
>
> diff --git a/arch/x86/include/asm/atomic.h b/arch/x86/include/asm/atomic.h
> index 14635c5ea025..64f0a7fb9b2f 100644
> --- a/arch/x86/include/asm/atomic.h
> +++ b/arch/x86/include/asm/atomic.h
> @@ -2,6 +2,7 @@
> #define _ASM_X86_ATOMIC_H
>
> #include <linux/compiler.h>
> +#include <linux/kasan-checks.h>
> #include <linux/types.h>
> #include <asm/alternative.h>
> #include <asm/cmpxchg.h>
> @@ -47,6 +48,7 @@ static __always_inline void atomic_set(atomic_t *v, int i)
> */
> static __always_inline void atomic_add(int i, atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "addl %1,%0"
> : "+m" (v->counter)
> : "ir" (i));
> @@ -61,6 +63,7 @@ static __always_inline void atomic_add(int i, atomic_t *v)
> */
> static __always_inline void atomic_sub(int i, atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "subl %1,%0"
> : "+m" (v->counter)
> : "ir" (i));
> @@ -77,6 +80,7 @@ static __always_inline void atomic_sub(int i, atomic_t *v)
> */
> static __always_inline bool atomic_sub_and_test(int i, atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_BINARY_RMWcc(LOCK_PREFIX "subl", v->counter, "er", i, "%0", e);
> }
>
> @@ -88,6 +92,7 @@ static __always_inline bool atomic_sub_and_test(int i, atomic_t *v)
> */
> static __always_inline void atomic_inc(atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "incl %0"
> : "+m" (v->counter));
> }
> @@ -100,6 +105,7 @@ static __always_inline void atomic_inc(atomic_t *v)
> */
> static __always_inline void atomic_dec(atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "decl %0"
> : "+m" (v->counter));
> }
> @@ -114,6 +120,7 @@ static __always_inline void atomic_dec(atomic_t *v)
> */
> static __always_inline bool atomic_dec_and_test(atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_UNARY_RMWcc(LOCK_PREFIX "decl", v->counter, "%0", e);
> }
>
> @@ -127,6 +134,7 @@ static __always_inline bool atomic_dec_and_test(atomic_t *v)
> */
> static __always_inline bool atomic_inc_and_test(atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_UNARY_RMWcc(LOCK_PREFIX "incl", v->counter, "%0", e);
> }
>
> @@ -141,6 +149,7 @@ static __always_inline bool atomic_inc_and_test(atomic_t *v)
> */
> static __always_inline bool atomic_add_negative(int i, atomic_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_BINARY_RMWcc(LOCK_PREFIX "addl", v->counter, "er", i, "%0", s);
> }
>
> @@ -194,6 +203,7 @@ static inline int atomic_xchg(atomic_t *v, int new)
> #define ATOMIC_OP(op) \
> static inline void atomic_##op(int i, atomic_t *v) \
> { \
> + kasan_check_write(v, sizeof(*v)); \
> asm volatile(LOCK_PREFIX #op"l %1,%0" \
> : "+m" (v->counter) \
> : "ir" (i) \
> @@ -258,6 +268,7 @@ static __always_inline int __atomic_add_unless(atomic_t *v, int a, int u)
> */
> static __always_inline short int atomic_inc_short(short int *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm(LOCK_PREFIX "addw $1, %0" : "+m" (*v));
> return *v;
> }
> diff --git a/arch/x86/include/asm/atomic64_64.h b/arch/x86/include/asm/atomic64_64.h
> index 89ed2f6ae2f7..13fe8ff5a126 100644
> --- a/arch/x86/include/asm/atomic64_64.h
> +++ b/arch/x86/include/asm/atomic64_64.h
> @@ -2,6 +2,7 @@
> #define _ASM_X86_ATOMIC64_64_H
>
> #include <linux/types.h>
> +#include <linux/kasan-checks.h>
> #include <asm/alternative.h>
> #include <asm/cmpxchg.h>
>
> @@ -42,6 +43,7 @@ static inline void atomic64_set(atomic64_t *v, long i)
> */
> static __always_inline void atomic64_add(long i, atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "addq %1,%0"
> : "=m" (v->counter)
> : "er" (i), "m" (v->counter));
> @@ -56,6 +58,7 @@ static __always_inline void atomic64_add(long i, atomic64_t *v)
> */
> static inline void atomic64_sub(long i, atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "subq %1,%0"
> : "=m" (v->counter)
> : "er" (i), "m" (v->counter));
> @@ -72,6 +75,7 @@ static inline void atomic64_sub(long i, atomic64_t *v)
> */
> static inline bool atomic64_sub_and_test(long i, atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_BINARY_RMWcc(LOCK_PREFIX "subq", v->counter, "er", i, "%0", e);
> }
>
> @@ -83,6 +87,7 @@ static inline bool atomic64_sub_and_test(long i, atomic64_t *v)
> */
> static __always_inline void atomic64_inc(atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "incq %0"
> : "=m" (v->counter)
> : "m" (v->counter));
> @@ -96,6 +101,7 @@ static __always_inline void atomic64_inc(atomic64_t *v)
> */
> static __always_inline void atomic64_dec(atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> asm volatile(LOCK_PREFIX "decq %0"
> : "=m" (v->counter)
> : "m" (v->counter));
> @@ -111,6 +117,7 @@ static __always_inline void atomic64_dec(atomic64_t *v)
> */
> static inline bool atomic64_dec_and_test(atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_UNARY_RMWcc(LOCK_PREFIX "decq", v->counter, "%0", e);
> }
>
> @@ -124,6 +131,7 @@ static inline bool atomic64_dec_and_test(atomic64_t *v)
> */
> static inline bool atomic64_inc_and_test(atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_UNARY_RMWcc(LOCK_PREFIX "incq", v->counter, "%0", e);
> }
>
> @@ -138,6 +146,7 @@ static inline bool atomic64_inc_and_test(atomic64_t *v)
> */
> static inline bool atomic64_add_negative(long i, atomic64_t *v)
> {
> + kasan_check_write(v, sizeof(*v));
> GEN_BINARY_RMWcc(LOCK_PREFIX "addq", v->counter, "er", i, "%0", s);
> }
>
> @@ -233,6 +242,7 @@ static inline long atomic64_dec_if_positive(atomic64_t *v)
> #define ATOMIC64_OP(op) \
> static inline void atomic64_##op(long i, atomic64_t *v) \
> { \
> + kasan_check_write(v, sizeof(*v)); \
> asm volatile(LOCK_PREFIX #op"q %1,%0" \
> : "+m" (v->counter) \
> : "er" (i) \
> diff --git a/arch/x86/include/asm/cmpxchg.h b/arch/x86/include/asm/cmpxchg.h
> index 97848cdfcb1a..a10e7fb09210 100644
> --- a/arch/x86/include/asm/cmpxchg.h
> +++ b/arch/x86/include/asm/cmpxchg.h
> @@ -2,6 +2,7 @@
> #define ASM_X86_CMPXCHG_H
>
> #include <linux/compiler.h>
> +#include <linux/kasan-checks.h>
> #include <asm/cpufeatures.h>
> #include <asm/alternative.h> /* Provides LOCK_PREFIX */
>
> @@ -41,6 +42,7 @@ extern void __add_wrong_size(void)
> #define __xchg_op(ptr, arg, op, lock) \
> ({ \
> __typeof__ (*(ptr)) __ret = (arg); \
> + kasan_check_write((void *)(ptr), sizeof(*(ptr))); \
> switch (sizeof(*(ptr))) { \
> case __X86_CASE_B: \
> asm volatile (lock #op "b %b0, %1\n" \
> @@ -86,6 +88,7 @@ extern void __add_wrong_size(void)
> __typeof__(*(ptr)) __ret; \
> __typeof__(*(ptr)) __old = (old); \
> __typeof__(*(ptr)) __new = (new); \
> + kasan_check_write((void *)(ptr), sizeof(*(ptr))); \
> switch (size) { \
> case __X86_CASE_B: \
> { \
> @@ -171,6 +174,7 @@ extern void __add_wrong_size(void)
> BUILD_BUG_ON(sizeof(*(p2)) != sizeof(long)); \
> VM_BUG_ON((unsigned long)(p1) % (2 * sizeof(long))); \
> VM_BUG_ON((unsigned long)((p1) + 1) != (unsigned long)(p2)); \
> + kasan_check_write((void *)(p1), 2 * sizeof(*(p1))); \
> asm volatile(pfx "cmpxchg%c4b %2; sete %0" \
> : "=a" (__ret), "+d" (__old2), \
> "+m" (*(p1)), "+m" (*(p2)) \
> --
> 2.12.0.rc1.440.g5b76565f74-goog
>