14e66934eSEric Dumazet // SPDX-License-Identifier: GPL-2.0-or-later 2*b6d7c0ebSAndrzej Hajda 3*b6d7c0ebSAndrzej Hajda #define pr_fmt(fmt) "ref_tracker: " fmt 4*b6d7c0ebSAndrzej Hajda 54e66934eSEric Dumazet #include <linux/export.h> 6*b6d7c0ebSAndrzej Hajda #include <linux/list_sort.h> 74e66934eSEric Dumazet #include <linux/ref_tracker.h> 84e66934eSEric Dumazet #include <linux/slab.h> 94e66934eSEric Dumazet #include <linux/stacktrace.h> 104e66934eSEric Dumazet #include <linux/stackdepot.h> 114e66934eSEric Dumazet 124e66934eSEric Dumazet #define REF_TRACKER_STACK_ENTRIES 16 13*b6d7c0ebSAndrzej Hajda #define STACK_BUF_SIZE 1024 144e66934eSEric Dumazet 154e66934eSEric Dumazet struct ref_tracker { 164e66934eSEric Dumazet struct list_head head; /* anchor into dir->list or dir->quarantine */ 174e66934eSEric Dumazet bool dead; 184e66934eSEric Dumazet depot_stack_handle_t alloc_stack_handle; 194e66934eSEric Dumazet depot_stack_handle_t free_stack_handle; 204e66934eSEric Dumazet }; 214e66934eSEric Dumazet 22*b6d7c0ebSAndrzej Hajda struct ref_tracker_dir_stats { 23*b6d7c0ebSAndrzej Hajda int total; 24*b6d7c0ebSAndrzej Hajda int count; 25*b6d7c0ebSAndrzej Hajda struct { 26*b6d7c0ebSAndrzej Hajda depot_stack_handle_t stack_handle; 27*b6d7c0ebSAndrzej Hajda unsigned int count; 28*b6d7c0ebSAndrzej Hajda } stacks[]; 29*b6d7c0ebSAndrzej Hajda }; 30*b6d7c0ebSAndrzej Hajda 31*b6d7c0ebSAndrzej Hajda static struct ref_tracker_dir_stats * 32*b6d7c0ebSAndrzej Hajda ref_tracker_get_stats(struct ref_tracker_dir *dir, unsigned int limit) 33*b6d7c0ebSAndrzej Hajda { 34*b6d7c0ebSAndrzej Hajda struct ref_tracker_dir_stats *stats; 35*b6d7c0ebSAndrzej Hajda struct ref_tracker *tracker; 36*b6d7c0ebSAndrzej Hajda 37*b6d7c0ebSAndrzej Hajda stats = kmalloc(struct_size(stats, stacks, limit), 38*b6d7c0ebSAndrzej Hajda GFP_NOWAIT | __GFP_NOWARN); 39*b6d7c0ebSAndrzej Hajda if (!stats) 40*b6d7c0ebSAndrzej Hajda return ERR_PTR(-ENOMEM); 41*b6d7c0ebSAndrzej Hajda stats->total = 0; 42*b6d7c0ebSAndrzej Hajda stats->count = 0; 43*b6d7c0ebSAndrzej Hajda 44*b6d7c0ebSAndrzej Hajda list_for_each_entry(tracker, &dir->list, head) { 45*b6d7c0ebSAndrzej Hajda depot_stack_handle_t stack = tracker->alloc_stack_handle; 46*b6d7c0ebSAndrzej Hajda int i; 47*b6d7c0ebSAndrzej Hajda 48*b6d7c0ebSAndrzej Hajda ++stats->total; 49*b6d7c0ebSAndrzej Hajda for (i = 0; i < stats->count; ++i) 50*b6d7c0ebSAndrzej Hajda if (stats->stacks[i].stack_handle == stack) 51*b6d7c0ebSAndrzej Hajda break; 52*b6d7c0ebSAndrzej Hajda if (i >= limit) 53*b6d7c0ebSAndrzej Hajda continue; 54*b6d7c0ebSAndrzej Hajda if (i >= stats->count) { 55*b6d7c0ebSAndrzej Hajda stats->stacks[i].stack_handle = stack; 56*b6d7c0ebSAndrzej Hajda stats->stacks[i].count = 0; 57*b6d7c0ebSAndrzej Hajda ++stats->count; 58*b6d7c0ebSAndrzej Hajda } 59*b6d7c0ebSAndrzej Hajda ++stats->stacks[i].count; 60*b6d7c0ebSAndrzej Hajda } 61*b6d7c0ebSAndrzej Hajda 62*b6d7c0ebSAndrzej Hajda return stats; 63*b6d7c0ebSAndrzej Hajda } 64*b6d7c0ebSAndrzej Hajda 657a113ff6SAndrzej Hajda void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir, 667a113ff6SAndrzej Hajda unsigned int display_limit) 677a113ff6SAndrzej Hajda { 68*b6d7c0ebSAndrzej Hajda struct ref_tracker_dir_stats *stats; 69*b6d7c0ebSAndrzej Hajda unsigned int i = 0, skipped; 70*b6d7c0ebSAndrzej Hajda depot_stack_handle_t stack; 71*b6d7c0ebSAndrzej Hajda char *sbuf; 727a113ff6SAndrzej Hajda 737a113ff6SAndrzej Hajda lockdep_assert_held(&dir->lock); 747a113ff6SAndrzej Hajda 75*b6d7c0ebSAndrzej Hajda if (list_empty(&dir->list)) 76*b6d7c0ebSAndrzej Hajda return; 77*b6d7c0ebSAndrzej Hajda 78*b6d7c0ebSAndrzej Hajda stats = ref_tracker_get_stats(dir, display_limit); 79*b6d7c0ebSAndrzej Hajda if (IS_ERR(stats)) { 80*b6d7c0ebSAndrzej Hajda pr_err("%s@%pK: couldn't get stats, error %pe\n", 81*b6d7c0ebSAndrzej Hajda dir->name, dir, stats); 82*b6d7c0ebSAndrzej Hajda return; 837a113ff6SAndrzej Hajda } 84*b6d7c0ebSAndrzej Hajda 85*b6d7c0ebSAndrzej Hajda sbuf = kmalloc(STACK_BUF_SIZE, GFP_NOWAIT | __GFP_NOWARN); 86*b6d7c0ebSAndrzej Hajda 87*b6d7c0ebSAndrzej Hajda for (i = 0, skipped = stats->total; i < stats->count; ++i) { 88*b6d7c0ebSAndrzej Hajda stack = stats->stacks[i].stack_handle; 89*b6d7c0ebSAndrzej Hajda if (sbuf && !stack_depot_snprint(stack, sbuf, STACK_BUF_SIZE, 4)) 90*b6d7c0ebSAndrzej Hajda sbuf[0] = 0; 91*b6d7c0ebSAndrzej Hajda pr_err("%s@%pK has %d/%d users at\n%s\n", dir->name, dir, 92*b6d7c0ebSAndrzej Hajda stats->stacks[i].count, stats->total, sbuf); 93*b6d7c0ebSAndrzej Hajda skipped -= stats->stacks[i].count; 947a113ff6SAndrzej Hajda } 95*b6d7c0ebSAndrzej Hajda 96*b6d7c0ebSAndrzej Hajda if (skipped) 97*b6d7c0ebSAndrzej Hajda pr_err("%s@%pK skipped reports about %d/%d users.\n", 98*b6d7c0ebSAndrzej Hajda dir->name, dir, skipped, stats->total); 99*b6d7c0ebSAndrzej Hajda 100*b6d7c0ebSAndrzej Hajda kfree(sbuf); 101*b6d7c0ebSAndrzej Hajda 102*b6d7c0ebSAndrzej Hajda kfree(stats); 1037a113ff6SAndrzej Hajda } 1047a113ff6SAndrzej Hajda EXPORT_SYMBOL(ref_tracker_dir_print_locked); 1057a113ff6SAndrzej Hajda 1067a113ff6SAndrzej Hajda void ref_tracker_dir_print(struct ref_tracker_dir *dir, 1077a113ff6SAndrzej Hajda unsigned int display_limit) 1087a113ff6SAndrzej Hajda { 1097a113ff6SAndrzej Hajda unsigned long flags; 1107a113ff6SAndrzej Hajda 1117a113ff6SAndrzej Hajda spin_lock_irqsave(&dir->lock, flags); 1127a113ff6SAndrzej Hajda ref_tracker_dir_print_locked(dir, display_limit); 1137a113ff6SAndrzej Hajda spin_unlock_irqrestore(&dir->lock, flags); 1147a113ff6SAndrzej Hajda } 1157a113ff6SAndrzej Hajda EXPORT_SYMBOL(ref_tracker_dir_print); 1167a113ff6SAndrzej Hajda 1174e66934eSEric Dumazet void ref_tracker_dir_exit(struct ref_tracker_dir *dir) 1184e66934eSEric Dumazet { 1194e66934eSEric Dumazet struct ref_tracker *tracker, *n; 1204e66934eSEric Dumazet unsigned long flags; 1214e66934eSEric Dumazet bool leak = false; 1224e66934eSEric Dumazet 123e3ececfeSEric Dumazet dir->dead = true; 1244e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 1254e66934eSEric Dumazet list_for_each_entry_safe(tracker, n, &dir->quarantine, head) { 1264e66934eSEric Dumazet list_del(&tracker->head); 1274e66934eSEric Dumazet kfree(tracker); 1284e66934eSEric Dumazet dir->quarantine_avail++; 1294e66934eSEric Dumazet } 1307a113ff6SAndrzej Hajda if (!list_empty(&dir->list)) { 1317a113ff6SAndrzej Hajda ref_tracker_dir_print_locked(dir, 16); 1324e66934eSEric Dumazet leak = true; 1337a113ff6SAndrzej Hajda list_for_each_entry_safe(tracker, n, &dir->list, head) { 1344e66934eSEric Dumazet list_del(&tracker->head); 1354e66934eSEric Dumazet kfree(tracker); 1364e66934eSEric Dumazet } 1377a113ff6SAndrzej Hajda } 1384e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1394e66934eSEric Dumazet WARN_ON_ONCE(leak); 1404e66934eSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->untracked) != 1); 1418fd5522fSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->no_tracker) != 1); 1424e66934eSEric Dumazet } 1434e66934eSEric Dumazet EXPORT_SYMBOL(ref_tracker_dir_exit); 1444e66934eSEric Dumazet 1454e66934eSEric Dumazet int ref_tracker_alloc(struct ref_tracker_dir *dir, 1464e66934eSEric Dumazet struct ref_tracker **trackerp, 1474e66934eSEric Dumazet gfp_t gfp) 1484e66934eSEric Dumazet { 1494e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 1504e66934eSEric Dumazet struct ref_tracker *tracker; 1514e66934eSEric Dumazet unsigned int nr_entries; 152c12837d1SEric Dumazet gfp_t gfp_mask = gfp; 1534e66934eSEric Dumazet unsigned long flags; 1544e66934eSEric Dumazet 155e3ececfeSEric Dumazet WARN_ON_ONCE(dir->dead); 156e3ececfeSEric Dumazet 1578fd5522fSEric Dumazet if (!trackerp) { 1588fd5522fSEric Dumazet refcount_inc(&dir->no_tracker); 1598fd5522fSEric Dumazet return 0; 1608fd5522fSEric Dumazet } 161c12837d1SEric Dumazet if (gfp & __GFP_DIRECT_RECLAIM) 162c12837d1SEric Dumazet gfp_mask |= __GFP_NOFAIL; 163c12837d1SEric Dumazet *trackerp = tracker = kzalloc(sizeof(*tracker), gfp_mask); 1644e66934eSEric Dumazet if (unlikely(!tracker)) { 1654e66934eSEric Dumazet pr_err_once("memory allocation failure, unreliable refcount tracker.\n"); 1664e66934eSEric Dumazet refcount_inc(&dir->untracked); 1674e66934eSEric Dumazet return -ENOMEM; 1684e66934eSEric Dumazet } 1694e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 1704e66934eSEric Dumazet tracker->alloc_stack_handle = stack_depot_save(entries, nr_entries, gfp); 1714e66934eSEric Dumazet 1724e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 1734e66934eSEric Dumazet list_add(&tracker->head, &dir->list); 1744e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1754e66934eSEric Dumazet return 0; 1764e66934eSEric Dumazet } 1774e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_alloc); 1784e66934eSEric Dumazet 1794e66934eSEric Dumazet int ref_tracker_free(struct ref_tracker_dir *dir, 1804e66934eSEric Dumazet struct ref_tracker **trackerp) 1814e66934eSEric Dumazet { 1824e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 1834e66934eSEric Dumazet depot_stack_handle_t stack_handle; 1848fd5522fSEric Dumazet struct ref_tracker *tracker; 1854e66934eSEric Dumazet unsigned int nr_entries; 1864e66934eSEric Dumazet unsigned long flags; 1874e66934eSEric Dumazet 188e3ececfeSEric Dumazet WARN_ON_ONCE(dir->dead); 189e3ececfeSEric Dumazet 1908fd5522fSEric Dumazet if (!trackerp) { 1918fd5522fSEric Dumazet refcount_dec(&dir->no_tracker); 1928fd5522fSEric Dumazet return 0; 1938fd5522fSEric Dumazet } 1948fd5522fSEric Dumazet tracker = *trackerp; 1954e66934eSEric Dumazet if (!tracker) { 1964e66934eSEric Dumazet refcount_dec(&dir->untracked); 1974e66934eSEric Dumazet return -EEXIST; 1984e66934eSEric Dumazet } 1994e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 2004e66934eSEric Dumazet stack_handle = stack_depot_save(entries, nr_entries, GFP_ATOMIC); 2014e66934eSEric Dumazet 2024e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 2034e66934eSEric Dumazet if (tracker->dead) { 2044e66934eSEric Dumazet pr_err("reference already released.\n"); 2054e66934eSEric Dumazet if (tracker->alloc_stack_handle) { 2064e66934eSEric Dumazet pr_err("allocated in:\n"); 2074e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 2084e66934eSEric Dumazet } 2094e66934eSEric Dumazet if (tracker->free_stack_handle) { 2104e66934eSEric Dumazet pr_err("freed in:\n"); 2114e66934eSEric Dumazet stack_depot_print(tracker->free_stack_handle); 2124e66934eSEric Dumazet } 2134e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 2144e66934eSEric Dumazet WARN_ON_ONCE(1); 2154e66934eSEric Dumazet return -EINVAL; 2164e66934eSEric Dumazet } 2174e66934eSEric Dumazet tracker->dead = true; 2184e66934eSEric Dumazet 2194e66934eSEric Dumazet tracker->free_stack_handle = stack_handle; 2204e66934eSEric Dumazet 2214e66934eSEric Dumazet list_move_tail(&tracker->head, &dir->quarantine); 2224e66934eSEric Dumazet if (!dir->quarantine_avail) { 2234e66934eSEric Dumazet tracker = list_first_entry(&dir->quarantine, struct ref_tracker, head); 2244e66934eSEric Dumazet list_del(&tracker->head); 2254e66934eSEric Dumazet } else { 2264e66934eSEric Dumazet dir->quarantine_avail--; 2274e66934eSEric Dumazet tracker = NULL; 2284e66934eSEric Dumazet } 2294e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 2304e66934eSEric Dumazet 2314e66934eSEric Dumazet kfree(tracker); 2324e66934eSEric Dumazet return 0; 2334e66934eSEric Dumazet } 2344e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_free); 235