1*4e66934eSEric Dumazet // SPDX-License-Identifier: GPL-2.0-or-later 2*4e66934eSEric Dumazet #include <linux/export.h> 3*4e66934eSEric Dumazet #include <linux/ref_tracker.h> 4*4e66934eSEric Dumazet #include <linux/slab.h> 5*4e66934eSEric Dumazet #include <linux/stacktrace.h> 6*4e66934eSEric Dumazet #include <linux/stackdepot.h> 7*4e66934eSEric Dumazet 8*4e66934eSEric Dumazet #define REF_TRACKER_STACK_ENTRIES 16 9*4e66934eSEric Dumazet 10*4e66934eSEric Dumazet struct ref_tracker { 11*4e66934eSEric Dumazet struct list_head head; /* anchor into dir->list or dir->quarantine */ 12*4e66934eSEric Dumazet bool dead; 13*4e66934eSEric Dumazet depot_stack_handle_t alloc_stack_handle; 14*4e66934eSEric Dumazet depot_stack_handle_t free_stack_handle; 15*4e66934eSEric Dumazet }; 16*4e66934eSEric Dumazet 17*4e66934eSEric Dumazet void ref_tracker_dir_exit(struct ref_tracker_dir *dir) 18*4e66934eSEric Dumazet { 19*4e66934eSEric Dumazet struct ref_tracker *tracker, *n; 20*4e66934eSEric Dumazet unsigned long flags; 21*4e66934eSEric Dumazet bool leak = false; 22*4e66934eSEric Dumazet 23*4e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 24*4e66934eSEric Dumazet list_for_each_entry_safe(tracker, n, &dir->quarantine, head) { 25*4e66934eSEric Dumazet list_del(&tracker->head); 26*4e66934eSEric Dumazet kfree(tracker); 27*4e66934eSEric Dumazet dir->quarantine_avail++; 28*4e66934eSEric Dumazet } 29*4e66934eSEric Dumazet list_for_each_entry_safe(tracker, n, &dir->list, head) { 30*4e66934eSEric Dumazet pr_err("leaked reference.\n"); 31*4e66934eSEric Dumazet if (tracker->alloc_stack_handle) 32*4e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 33*4e66934eSEric Dumazet leak = true; 34*4e66934eSEric Dumazet list_del(&tracker->head); 35*4e66934eSEric Dumazet kfree(tracker); 36*4e66934eSEric Dumazet } 37*4e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 38*4e66934eSEric Dumazet WARN_ON_ONCE(leak); 39*4e66934eSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->untracked) != 1); 40*4e66934eSEric Dumazet } 41*4e66934eSEric Dumazet EXPORT_SYMBOL(ref_tracker_dir_exit); 42*4e66934eSEric Dumazet 43*4e66934eSEric Dumazet void ref_tracker_dir_print(struct ref_tracker_dir *dir, 44*4e66934eSEric Dumazet unsigned int display_limit) 45*4e66934eSEric Dumazet { 46*4e66934eSEric Dumazet struct ref_tracker *tracker; 47*4e66934eSEric Dumazet unsigned long flags; 48*4e66934eSEric Dumazet unsigned int i = 0; 49*4e66934eSEric Dumazet 50*4e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 51*4e66934eSEric Dumazet list_for_each_entry(tracker, &dir->list, head) { 52*4e66934eSEric Dumazet if (i < display_limit) { 53*4e66934eSEric Dumazet pr_err("leaked reference.\n"); 54*4e66934eSEric Dumazet if (tracker->alloc_stack_handle) 55*4e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 56*4e66934eSEric Dumazet i++; 57*4e66934eSEric Dumazet } else { 58*4e66934eSEric Dumazet break; 59*4e66934eSEric Dumazet } 60*4e66934eSEric Dumazet } 61*4e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 62*4e66934eSEric Dumazet } 63*4e66934eSEric Dumazet EXPORT_SYMBOL(ref_tracker_dir_print); 64*4e66934eSEric Dumazet 65*4e66934eSEric Dumazet int ref_tracker_alloc(struct ref_tracker_dir *dir, 66*4e66934eSEric Dumazet struct ref_tracker **trackerp, 67*4e66934eSEric Dumazet gfp_t gfp) 68*4e66934eSEric Dumazet { 69*4e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 70*4e66934eSEric Dumazet struct ref_tracker *tracker; 71*4e66934eSEric Dumazet unsigned int nr_entries; 72*4e66934eSEric Dumazet unsigned long flags; 73*4e66934eSEric Dumazet 74*4e66934eSEric Dumazet *trackerp = tracker = kzalloc(sizeof(*tracker), gfp | __GFP_NOFAIL); 75*4e66934eSEric Dumazet if (unlikely(!tracker)) { 76*4e66934eSEric Dumazet pr_err_once("memory allocation failure, unreliable refcount tracker.\n"); 77*4e66934eSEric Dumazet refcount_inc(&dir->untracked); 78*4e66934eSEric Dumazet return -ENOMEM; 79*4e66934eSEric Dumazet } 80*4e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 81*4e66934eSEric Dumazet nr_entries = filter_irq_stacks(entries, nr_entries); 82*4e66934eSEric Dumazet tracker->alloc_stack_handle = stack_depot_save(entries, nr_entries, gfp); 83*4e66934eSEric Dumazet 84*4e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 85*4e66934eSEric Dumazet list_add(&tracker->head, &dir->list); 86*4e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 87*4e66934eSEric Dumazet return 0; 88*4e66934eSEric Dumazet } 89*4e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_alloc); 90*4e66934eSEric Dumazet 91*4e66934eSEric Dumazet int ref_tracker_free(struct ref_tracker_dir *dir, 92*4e66934eSEric Dumazet struct ref_tracker **trackerp) 93*4e66934eSEric Dumazet { 94*4e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 95*4e66934eSEric Dumazet struct ref_tracker *tracker = *trackerp; 96*4e66934eSEric Dumazet depot_stack_handle_t stack_handle; 97*4e66934eSEric Dumazet unsigned int nr_entries; 98*4e66934eSEric Dumazet unsigned long flags; 99*4e66934eSEric Dumazet 100*4e66934eSEric Dumazet if (!tracker) { 101*4e66934eSEric Dumazet refcount_dec(&dir->untracked); 102*4e66934eSEric Dumazet return -EEXIST; 103*4e66934eSEric Dumazet } 104*4e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 105*4e66934eSEric Dumazet nr_entries = filter_irq_stacks(entries, nr_entries); 106*4e66934eSEric Dumazet stack_handle = stack_depot_save(entries, nr_entries, GFP_ATOMIC); 107*4e66934eSEric Dumazet 108*4e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 109*4e66934eSEric Dumazet if (tracker->dead) { 110*4e66934eSEric Dumazet pr_err("reference already released.\n"); 111*4e66934eSEric Dumazet if (tracker->alloc_stack_handle) { 112*4e66934eSEric Dumazet pr_err("allocated in:\n"); 113*4e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 114*4e66934eSEric Dumazet } 115*4e66934eSEric Dumazet if (tracker->free_stack_handle) { 116*4e66934eSEric Dumazet pr_err("freed in:\n"); 117*4e66934eSEric Dumazet stack_depot_print(tracker->free_stack_handle); 118*4e66934eSEric Dumazet } 119*4e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 120*4e66934eSEric Dumazet WARN_ON_ONCE(1); 121*4e66934eSEric Dumazet return -EINVAL; 122*4e66934eSEric Dumazet } 123*4e66934eSEric Dumazet tracker->dead = true; 124*4e66934eSEric Dumazet 125*4e66934eSEric Dumazet tracker->free_stack_handle = stack_handle; 126*4e66934eSEric Dumazet 127*4e66934eSEric Dumazet list_move_tail(&tracker->head, &dir->quarantine); 128*4e66934eSEric Dumazet if (!dir->quarantine_avail) { 129*4e66934eSEric Dumazet tracker = list_first_entry(&dir->quarantine, struct ref_tracker, head); 130*4e66934eSEric Dumazet list_del(&tracker->head); 131*4e66934eSEric Dumazet } else { 132*4e66934eSEric Dumazet dir->quarantine_avail--; 133*4e66934eSEric Dumazet tracker = NULL; 134*4e66934eSEric Dumazet } 135*4e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 136*4e66934eSEric Dumazet 137*4e66934eSEric Dumazet kfree(tracker); 138*4e66934eSEric Dumazet return 0; 139*4e66934eSEric Dumazet } 140*4e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_free); 141