This patch records the last two timer queueing stacks and prints up to 2 timer stacks in KASAN report. It is useful for programmers to solve use-after-free or double-free memory timer issues. When timer_setup() or timer_setup_on_stack() is called, then it prepares to use this timer and sets timer callback, we store this call stack in order to print it in KASAN report. Signed-off-by: Walter Wu <walter-zh.wu@xxxxxxxxxxxx> Cc: Andrey Ryabinin <aryabinin@xxxxxxxxxxxxx> Cc: Dmitry Vyukov <dvyukov@xxxxxxxxxx> Cc: Alexander Potapenko <glider@xxxxxxxxxx> Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx> Cc: John Stultz <john.stultz@xxxxxxxxxx> Cc: Stephen Boyd <sboyd@xxxxxxxxxx> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> --- include/linux/kasan.h | 2 ++ kernel/time/timer.c | 2 ++ mm/kasan/generic.c | 21 +++++++++++++++++++++ mm/kasan/kasan.h | 4 +++- mm/kasan/report.c | 11 +++++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 23b7ee00572d..763664b36dc6 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -175,12 +175,14 @@ static inline size_t kasan_metadata_size(struct kmem_cache *cache) { return 0; } void kasan_cache_shrink(struct kmem_cache *cache); void kasan_cache_shutdown(struct kmem_cache *cache); void kasan_record_aux_stack(void *ptr); +void kasan_record_tmr_stack(void *ptr); #else /* CONFIG_KASAN_GENERIC */ static inline void kasan_cache_shrink(struct kmem_cache *cache) {} static inline void kasan_cache_shutdown(struct kmem_cache *cache) {} static inline void kasan_record_aux_stack(void *ptr) {} +static inline void kasan_record_tmr_stack(void *ptr) {} #endif /* CONFIG_KASAN_GENERIC */ diff --git a/kernel/time/timer.c b/kernel/time/timer.c index a5221abb4594..ef2da9ddfac7 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -783,6 +783,8 @@ static void do_init_timer(struct timer_list *timer, timer->function = func; timer->flags = flags | raw_smp_processor_id(); lockdep_init_map(&timer->lockdep_map, name, key, 0); + + kasan_record_tmr_stack(timer); } /** diff --git a/mm/kasan/generic.c b/mm/kasan/generic.c index 4b3cbad7431b..f35dcec990ab 100644 --- a/mm/kasan/generic.c +++ b/mm/kasan/generic.c @@ -347,6 +347,27 @@ void kasan_record_aux_stack(void *addr) alloc_info->aux_stack[0] = kasan_save_stack(GFP_NOWAIT); } +void kasan_record_tmr_stack(void *addr) +{ + struct page *page = kasan_addr_to_page(addr); + struct kmem_cache *cache; + struct kasan_alloc_meta *alloc_info; + void *object; + + if (!(page && PageSlab(page))) + return; + + cache = page->slab_cache; + object = nearest_obj(cache, page, addr); + alloc_info = get_alloc_info(cache, object); + + /* + * record the last two timer stacks. + */ + alloc_info->tmr_stack[1] = alloc_info->tmr_stack[0]; + alloc_info->tmr_stack[0] = kasan_save_stack(GFP_NOWAIT); +} + void kasan_set_free_info(struct kmem_cache *cache, void *object, u8 tag) { diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index ef655a1c6e15..c50827f388a3 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -108,10 +108,12 @@ struct kasan_alloc_meta { struct kasan_track alloc_track; #ifdef CONFIG_KASAN_GENERIC /* - * call_rcu() call stack is stored into struct kasan_alloc_meta. + * call_rcu() call stack and timer queueing stack are stored + * into struct kasan_alloc_meta. * The free stack is stored into struct kasan_free_meta. */ depot_stack_handle_t aux_stack[2]; + depot_stack_handle_t tmr_stack[2]; #else struct kasan_track free_track[KASAN_NR_FREE_STACKS]; #endif diff --git a/mm/kasan/report.c b/mm/kasan/report.c index fed3c8fdfd25..6fa3bfee381f 100644 --- a/mm/kasan/report.c +++ b/mm/kasan/report.c @@ -191,6 +191,17 @@ static void describe_object(struct kmem_cache *cache, void *object, print_stack(alloc_info->aux_stack[1]); pr_err("\n"); } + + if (alloc_info->tmr_stack[0]) { + pr_err("Last timer stack:\n"); + print_stack(alloc_info->tmr_stack[0]); + pr_err("\n"); + } + if (alloc_info->tmr_stack[1]) { + pr_err("Second to last timer stack:\n"); + print_stack(alloc_info->tmr_stack[1]); + pr_err("\n"); + } #endif } -- 2.18.0