14e66934eSEric Dumazet // SPDX-License-Identifier: GPL-2.0-or-later 2b6d7c0ebSAndrzej Hajda 3b6d7c0ebSAndrzej Hajda #define pr_fmt(fmt) "ref_tracker: " fmt 4b6d7c0ebSAndrzej Hajda 54e66934eSEric Dumazet #include <linux/export.h> 6b6d7c0ebSAndrzej 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 13b6d7c0ebSAndrzej 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 22b6d7c0ebSAndrzej Hajda struct ref_tracker_dir_stats { 23b6d7c0ebSAndrzej Hajda int total; 24b6d7c0ebSAndrzej Hajda int count; 25b6d7c0ebSAndrzej Hajda struct { 26b6d7c0ebSAndrzej Hajda depot_stack_handle_t stack_handle; 27b6d7c0ebSAndrzej Hajda unsigned int count; 28b6d7c0ebSAndrzej Hajda } stacks[]; 29b6d7c0ebSAndrzej Hajda }; 30b6d7c0ebSAndrzej Hajda 31b6d7c0ebSAndrzej Hajda static struct ref_tracker_dir_stats * 32b6d7c0ebSAndrzej Hajda ref_tracker_get_stats(struct ref_tracker_dir *dir, unsigned int limit) 33b6d7c0ebSAndrzej Hajda { 34b6d7c0ebSAndrzej Hajda struct ref_tracker_dir_stats *stats; 35b6d7c0ebSAndrzej Hajda struct ref_tracker *tracker; 36b6d7c0ebSAndrzej Hajda 37b6d7c0ebSAndrzej Hajda stats = kmalloc(struct_size(stats, stacks, limit), 38b6d7c0ebSAndrzej Hajda GFP_NOWAIT | __GFP_NOWARN); 39b6d7c0ebSAndrzej Hajda if (!stats) 40b6d7c0ebSAndrzej Hajda return ERR_PTR(-ENOMEM); 41b6d7c0ebSAndrzej Hajda stats->total = 0; 42b6d7c0ebSAndrzej Hajda stats->count = 0; 43b6d7c0ebSAndrzej Hajda 44b6d7c0ebSAndrzej Hajda list_for_each_entry(tracker, &dir->list, head) { 45b6d7c0ebSAndrzej Hajda depot_stack_handle_t stack = tracker->alloc_stack_handle; 46b6d7c0ebSAndrzej Hajda int i; 47b6d7c0ebSAndrzej Hajda 48b6d7c0ebSAndrzej Hajda ++stats->total; 49b6d7c0ebSAndrzej Hajda for (i = 0; i < stats->count; ++i) 50b6d7c0ebSAndrzej Hajda if (stats->stacks[i].stack_handle == stack) 51b6d7c0ebSAndrzej Hajda break; 52b6d7c0ebSAndrzej Hajda if (i >= limit) 53b6d7c0ebSAndrzej Hajda continue; 54b6d7c0ebSAndrzej Hajda if (i >= stats->count) { 55b6d7c0ebSAndrzej Hajda stats->stacks[i].stack_handle = stack; 56b6d7c0ebSAndrzej Hajda stats->stacks[i].count = 0; 57b6d7c0ebSAndrzej Hajda ++stats->count; 58b6d7c0ebSAndrzej Hajda } 59b6d7c0ebSAndrzej Hajda ++stats->stacks[i].count; 60b6d7c0ebSAndrzej Hajda } 61b6d7c0ebSAndrzej Hajda 62b6d7c0ebSAndrzej Hajda return stats; 63b6d7c0ebSAndrzej Hajda } 64b6d7c0ebSAndrzej Hajda 65227c6c83SAndrzej Hajda struct ostream { 66227c6c83SAndrzej Hajda char *buf; 67227c6c83SAndrzej Hajda int size, used; 68227c6c83SAndrzej Hajda }; 69227c6c83SAndrzej Hajda 70227c6c83SAndrzej Hajda #define pr_ostream(stream, fmt, args...) \ 71227c6c83SAndrzej Hajda ({ \ 72227c6c83SAndrzej Hajda struct ostream *_s = (stream); \ 73227c6c83SAndrzej Hajda \ 74227c6c83SAndrzej Hajda if (!_s->buf) { \ 75227c6c83SAndrzej Hajda pr_err(fmt, ##args); \ 76227c6c83SAndrzej Hajda } else { \ 77227c6c83SAndrzej Hajda int ret, len = _s->size - _s->used; \ 78227c6c83SAndrzej Hajda ret = snprintf(_s->buf + _s->used, len, pr_fmt(fmt), ##args); \ 79227c6c83SAndrzej Hajda _s->used += min(ret, len); \ 80227c6c83SAndrzej Hajda } \ 81227c6c83SAndrzej Hajda }) 82227c6c83SAndrzej Hajda 83227c6c83SAndrzej Hajda static void 84227c6c83SAndrzej Hajda __ref_tracker_dir_pr_ostream(struct ref_tracker_dir *dir, 85227c6c83SAndrzej Hajda unsigned int display_limit, struct ostream *s) 867a113ff6SAndrzej Hajda { 87b6d7c0ebSAndrzej Hajda struct ref_tracker_dir_stats *stats; 88b6d7c0ebSAndrzej Hajda unsigned int i = 0, skipped; 89b6d7c0ebSAndrzej Hajda depot_stack_handle_t stack; 90b6d7c0ebSAndrzej Hajda char *sbuf; 917a113ff6SAndrzej Hajda 927a113ff6SAndrzej Hajda lockdep_assert_held(&dir->lock); 937a113ff6SAndrzej Hajda 94b6d7c0ebSAndrzej Hajda if (list_empty(&dir->list)) 95b6d7c0ebSAndrzej Hajda return; 96b6d7c0ebSAndrzej Hajda 97b6d7c0ebSAndrzej Hajda stats = ref_tracker_get_stats(dir, display_limit); 98b6d7c0ebSAndrzej Hajda if (IS_ERR(stats)) { 99227c6c83SAndrzej Hajda pr_ostream(s, "%s@%pK: couldn't get stats, error %pe\n", 100b6d7c0ebSAndrzej Hajda dir->name, dir, stats); 101b6d7c0ebSAndrzej Hajda return; 1027a113ff6SAndrzej Hajda } 103b6d7c0ebSAndrzej Hajda 104b6d7c0ebSAndrzej Hajda sbuf = kmalloc(STACK_BUF_SIZE, GFP_NOWAIT | __GFP_NOWARN); 105b6d7c0ebSAndrzej Hajda 106b6d7c0ebSAndrzej Hajda for (i = 0, skipped = stats->total; i < stats->count; ++i) { 107b6d7c0ebSAndrzej Hajda stack = stats->stacks[i].stack_handle; 108b6d7c0ebSAndrzej Hajda if (sbuf && !stack_depot_snprint(stack, sbuf, STACK_BUF_SIZE, 4)) 109b6d7c0ebSAndrzej Hajda sbuf[0] = 0; 110227c6c83SAndrzej Hajda pr_ostream(s, "%s@%pK has %d/%d users at\n%s\n", dir->name, dir, 111b6d7c0ebSAndrzej Hajda stats->stacks[i].count, stats->total, sbuf); 112b6d7c0ebSAndrzej Hajda skipped -= stats->stacks[i].count; 1137a113ff6SAndrzej Hajda } 114b6d7c0ebSAndrzej Hajda 115b6d7c0ebSAndrzej Hajda if (skipped) 116227c6c83SAndrzej Hajda pr_ostream(s, "%s@%pK skipped reports about %d/%d users.\n", 117b6d7c0ebSAndrzej Hajda dir->name, dir, skipped, stats->total); 118b6d7c0ebSAndrzej Hajda 119b6d7c0ebSAndrzej Hajda kfree(sbuf); 120b6d7c0ebSAndrzej Hajda 121b6d7c0ebSAndrzej Hajda kfree(stats); 1227a113ff6SAndrzej Hajda } 123227c6c83SAndrzej Hajda 124227c6c83SAndrzej Hajda void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir, 125227c6c83SAndrzej Hajda unsigned int display_limit) 126227c6c83SAndrzej Hajda { 127227c6c83SAndrzej Hajda struct ostream os = {}; 128227c6c83SAndrzej Hajda 129227c6c83SAndrzej Hajda __ref_tracker_dir_pr_ostream(dir, display_limit, &os); 130227c6c83SAndrzej Hajda } 1317a113ff6SAndrzej Hajda EXPORT_SYMBOL(ref_tracker_dir_print_locked); 1327a113ff6SAndrzej Hajda 1337a113ff6SAndrzej Hajda void ref_tracker_dir_print(struct ref_tracker_dir *dir, 1347a113ff6SAndrzej Hajda unsigned int display_limit) 1357a113ff6SAndrzej Hajda { 1367a113ff6SAndrzej Hajda unsigned long flags; 1377a113ff6SAndrzej Hajda 1387a113ff6SAndrzej Hajda spin_lock_irqsave(&dir->lock, flags); 1397a113ff6SAndrzej Hajda ref_tracker_dir_print_locked(dir, display_limit); 1407a113ff6SAndrzej Hajda spin_unlock_irqrestore(&dir->lock, flags); 1417a113ff6SAndrzej Hajda } 1427a113ff6SAndrzej Hajda EXPORT_SYMBOL(ref_tracker_dir_print); 1437a113ff6SAndrzej Hajda 144227c6c83SAndrzej Hajda int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size) 145227c6c83SAndrzej Hajda { 146227c6c83SAndrzej Hajda struct ostream os = { .buf = buf, .size = size }; 147227c6c83SAndrzej Hajda unsigned long flags; 148227c6c83SAndrzej Hajda 149227c6c83SAndrzej Hajda spin_lock_irqsave(&dir->lock, flags); 150227c6c83SAndrzej Hajda __ref_tracker_dir_pr_ostream(dir, 16, &os); 151227c6c83SAndrzej Hajda spin_unlock_irqrestore(&dir->lock, flags); 152227c6c83SAndrzej Hajda 153227c6c83SAndrzej Hajda return os.used; 154227c6c83SAndrzej Hajda } 155227c6c83SAndrzej Hajda EXPORT_SYMBOL(ref_tracker_dir_snprint); 156227c6c83SAndrzej Hajda 1574e66934eSEric Dumazet void ref_tracker_dir_exit(struct ref_tracker_dir *dir) 1584e66934eSEric Dumazet { 1594e66934eSEric Dumazet struct ref_tracker *tracker, *n; 1604e66934eSEric Dumazet unsigned long flags; 1614e66934eSEric Dumazet bool leak = false; 1624e66934eSEric Dumazet 163e3ececfeSEric Dumazet dir->dead = true; 1644e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 1654e66934eSEric Dumazet list_for_each_entry_safe(tracker, n, &dir->quarantine, head) { 1664e66934eSEric Dumazet list_del(&tracker->head); 1674e66934eSEric Dumazet kfree(tracker); 1684e66934eSEric Dumazet dir->quarantine_avail++; 1694e66934eSEric Dumazet } 1707a113ff6SAndrzej Hajda if (!list_empty(&dir->list)) { 1717a113ff6SAndrzej Hajda ref_tracker_dir_print_locked(dir, 16); 1724e66934eSEric Dumazet leak = true; 1737a113ff6SAndrzej Hajda list_for_each_entry_safe(tracker, n, &dir->list, head) { 1744e66934eSEric Dumazet list_del(&tracker->head); 1754e66934eSEric Dumazet kfree(tracker); 1764e66934eSEric Dumazet } 1777a113ff6SAndrzej Hajda } 1784e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1794e66934eSEric Dumazet WARN_ON_ONCE(leak); 1804e66934eSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->untracked) != 1); 1818fd5522fSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->no_tracker) != 1); 1824e66934eSEric Dumazet } 1834e66934eSEric Dumazet EXPORT_SYMBOL(ref_tracker_dir_exit); 1844e66934eSEric Dumazet 1854e66934eSEric Dumazet int ref_tracker_alloc(struct ref_tracker_dir *dir, 1864e66934eSEric Dumazet struct ref_tracker **trackerp, 1874e66934eSEric Dumazet gfp_t gfp) 1884e66934eSEric Dumazet { 1894e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 1904e66934eSEric Dumazet struct ref_tracker *tracker; 1914e66934eSEric Dumazet unsigned int nr_entries; 192*acd8f0e5SAndrzej Hajda gfp_t gfp_mask = gfp | __GFP_NOWARN; 1934e66934eSEric Dumazet unsigned long flags; 1944e66934eSEric Dumazet 195e3ececfeSEric Dumazet WARN_ON_ONCE(dir->dead); 196e3ececfeSEric Dumazet 1978fd5522fSEric Dumazet if (!trackerp) { 1988fd5522fSEric Dumazet refcount_inc(&dir->no_tracker); 1998fd5522fSEric Dumazet return 0; 2008fd5522fSEric Dumazet } 201c12837d1SEric Dumazet if (gfp & __GFP_DIRECT_RECLAIM) 202c12837d1SEric Dumazet gfp_mask |= __GFP_NOFAIL; 203c12837d1SEric Dumazet *trackerp = tracker = kzalloc(sizeof(*tracker), gfp_mask); 2044e66934eSEric Dumazet if (unlikely(!tracker)) { 2054e66934eSEric Dumazet pr_err_once("memory allocation failure, unreliable refcount tracker.\n"); 2064e66934eSEric Dumazet refcount_inc(&dir->untracked); 2074e66934eSEric Dumazet return -ENOMEM; 2084e66934eSEric Dumazet } 2094e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 2104e66934eSEric Dumazet tracker->alloc_stack_handle = stack_depot_save(entries, nr_entries, gfp); 2114e66934eSEric Dumazet 2124e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 2134e66934eSEric Dumazet list_add(&tracker->head, &dir->list); 2144e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 2154e66934eSEric Dumazet return 0; 2164e66934eSEric Dumazet } 2174e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_alloc); 2184e66934eSEric Dumazet 2194e66934eSEric Dumazet int ref_tracker_free(struct ref_tracker_dir *dir, 2204e66934eSEric Dumazet struct ref_tracker **trackerp) 2214e66934eSEric Dumazet { 2224e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 2234e66934eSEric Dumazet depot_stack_handle_t stack_handle; 2248fd5522fSEric Dumazet struct ref_tracker *tracker; 2254e66934eSEric Dumazet unsigned int nr_entries; 2264e66934eSEric Dumazet unsigned long flags; 2274e66934eSEric Dumazet 228e3ececfeSEric Dumazet WARN_ON_ONCE(dir->dead); 229e3ececfeSEric Dumazet 2308fd5522fSEric Dumazet if (!trackerp) { 2318fd5522fSEric Dumazet refcount_dec(&dir->no_tracker); 2328fd5522fSEric Dumazet return 0; 2338fd5522fSEric Dumazet } 2348fd5522fSEric Dumazet tracker = *trackerp; 2354e66934eSEric Dumazet if (!tracker) { 2364e66934eSEric Dumazet refcount_dec(&dir->untracked); 2374e66934eSEric Dumazet return -EEXIST; 2384e66934eSEric Dumazet } 2394e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 240*acd8f0e5SAndrzej Hajda stack_handle = stack_depot_save(entries, nr_entries, 241*acd8f0e5SAndrzej Hajda GFP_NOWAIT | __GFP_NOWARN); 2424e66934eSEric Dumazet 2434e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 2444e66934eSEric Dumazet if (tracker->dead) { 2454e66934eSEric Dumazet pr_err("reference already released.\n"); 2464e66934eSEric Dumazet if (tracker->alloc_stack_handle) { 2474e66934eSEric Dumazet pr_err("allocated in:\n"); 2484e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 2494e66934eSEric Dumazet } 2504e66934eSEric Dumazet if (tracker->free_stack_handle) { 2514e66934eSEric Dumazet pr_err("freed in:\n"); 2524e66934eSEric Dumazet stack_depot_print(tracker->free_stack_handle); 2534e66934eSEric Dumazet } 2544e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 2554e66934eSEric Dumazet WARN_ON_ONCE(1); 2564e66934eSEric Dumazet return -EINVAL; 2574e66934eSEric Dumazet } 2584e66934eSEric Dumazet tracker->dead = true; 2594e66934eSEric Dumazet 2604e66934eSEric Dumazet tracker->free_stack_handle = stack_handle; 2614e66934eSEric Dumazet 2624e66934eSEric Dumazet list_move_tail(&tracker->head, &dir->quarantine); 2634e66934eSEric Dumazet if (!dir->quarantine_avail) { 2644e66934eSEric Dumazet tracker = list_first_entry(&dir->quarantine, struct ref_tracker, head); 2654e66934eSEric Dumazet list_del(&tracker->head); 2664e66934eSEric Dumazet } else { 2674e66934eSEric Dumazet dir->quarantine_avail--; 2684e66934eSEric Dumazet tracker = NULL; 2694e66934eSEric Dumazet } 2704e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 2714e66934eSEric Dumazet 2724e66934eSEric Dumazet kfree(tracker); 2734e66934eSEric Dumazet return 0; 2744e66934eSEric Dumazet } 2754e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_free); 276