14e66934eSEric Dumazet // SPDX-License-Identifier: GPL-2.0-or-later 24e66934eSEric Dumazet #include <linux/export.h> 34e66934eSEric Dumazet #include <linux/ref_tracker.h> 44e66934eSEric Dumazet #include <linux/slab.h> 54e66934eSEric Dumazet #include <linux/stacktrace.h> 64e66934eSEric Dumazet #include <linux/stackdepot.h> 74e66934eSEric Dumazet 84e66934eSEric Dumazet #define REF_TRACKER_STACK_ENTRIES 16 94e66934eSEric Dumazet 104e66934eSEric Dumazet struct ref_tracker { 114e66934eSEric Dumazet struct list_head head; /* anchor into dir->list or dir->quarantine */ 124e66934eSEric Dumazet bool dead; 134e66934eSEric Dumazet depot_stack_handle_t alloc_stack_handle; 144e66934eSEric Dumazet depot_stack_handle_t free_stack_handle; 154e66934eSEric Dumazet }; 164e66934eSEric Dumazet 17*7a113ff6SAndrzej Hajda void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir, 18*7a113ff6SAndrzej Hajda unsigned int display_limit) 19*7a113ff6SAndrzej Hajda { 20*7a113ff6SAndrzej Hajda struct ref_tracker *tracker; 21*7a113ff6SAndrzej Hajda unsigned int i = 0; 22*7a113ff6SAndrzej Hajda 23*7a113ff6SAndrzej Hajda lockdep_assert_held(&dir->lock); 24*7a113ff6SAndrzej Hajda 25*7a113ff6SAndrzej Hajda list_for_each_entry(tracker, &dir->list, head) { 26*7a113ff6SAndrzej Hajda if (i < display_limit) { 27*7a113ff6SAndrzej Hajda pr_err("leaked reference.\n"); 28*7a113ff6SAndrzej Hajda if (tracker->alloc_stack_handle) 29*7a113ff6SAndrzej Hajda stack_depot_print(tracker->alloc_stack_handle); 30*7a113ff6SAndrzej Hajda i++; 31*7a113ff6SAndrzej Hajda } else { 32*7a113ff6SAndrzej Hajda break; 33*7a113ff6SAndrzej Hajda } 34*7a113ff6SAndrzej Hajda } 35*7a113ff6SAndrzej Hajda } 36*7a113ff6SAndrzej Hajda EXPORT_SYMBOL(ref_tracker_dir_print_locked); 37*7a113ff6SAndrzej Hajda 38*7a113ff6SAndrzej Hajda void ref_tracker_dir_print(struct ref_tracker_dir *dir, 39*7a113ff6SAndrzej Hajda unsigned int display_limit) 40*7a113ff6SAndrzej Hajda { 41*7a113ff6SAndrzej Hajda unsigned long flags; 42*7a113ff6SAndrzej Hajda 43*7a113ff6SAndrzej Hajda spin_lock_irqsave(&dir->lock, flags); 44*7a113ff6SAndrzej Hajda ref_tracker_dir_print_locked(dir, display_limit); 45*7a113ff6SAndrzej Hajda spin_unlock_irqrestore(&dir->lock, flags); 46*7a113ff6SAndrzej Hajda } 47*7a113ff6SAndrzej Hajda EXPORT_SYMBOL(ref_tracker_dir_print); 48*7a113ff6SAndrzej Hajda 494e66934eSEric Dumazet void ref_tracker_dir_exit(struct ref_tracker_dir *dir) 504e66934eSEric Dumazet { 514e66934eSEric Dumazet struct ref_tracker *tracker, *n; 524e66934eSEric Dumazet unsigned long flags; 534e66934eSEric Dumazet bool leak = false; 544e66934eSEric Dumazet 55e3ececfeSEric Dumazet dir->dead = true; 564e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 574e66934eSEric Dumazet list_for_each_entry_safe(tracker, n, &dir->quarantine, head) { 584e66934eSEric Dumazet list_del(&tracker->head); 594e66934eSEric Dumazet kfree(tracker); 604e66934eSEric Dumazet dir->quarantine_avail++; 614e66934eSEric Dumazet } 62*7a113ff6SAndrzej Hajda if (!list_empty(&dir->list)) { 63*7a113ff6SAndrzej Hajda ref_tracker_dir_print_locked(dir, 16); 644e66934eSEric Dumazet leak = true; 65*7a113ff6SAndrzej Hajda list_for_each_entry_safe(tracker, n, &dir->list, head) { 664e66934eSEric Dumazet list_del(&tracker->head); 674e66934eSEric Dumazet kfree(tracker); 684e66934eSEric Dumazet } 69*7a113ff6SAndrzej Hajda } 704e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 714e66934eSEric Dumazet WARN_ON_ONCE(leak); 724e66934eSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->untracked) != 1); 738fd5522fSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->no_tracker) != 1); 744e66934eSEric Dumazet } 754e66934eSEric Dumazet EXPORT_SYMBOL(ref_tracker_dir_exit); 764e66934eSEric Dumazet 774e66934eSEric Dumazet int ref_tracker_alloc(struct ref_tracker_dir *dir, 784e66934eSEric Dumazet struct ref_tracker **trackerp, 794e66934eSEric Dumazet gfp_t gfp) 804e66934eSEric Dumazet { 814e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 824e66934eSEric Dumazet struct ref_tracker *tracker; 834e66934eSEric Dumazet unsigned int nr_entries; 84c12837d1SEric Dumazet gfp_t gfp_mask = gfp; 854e66934eSEric Dumazet unsigned long flags; 864e66934eSEric Dumazet 87e3ececfeSEric Dumazet WARN_ON_ONCE(dir->dead); 88e3ececfeSEric Dumazet 898fd5522fSEric Dumazet if (!trackerp) { 908fd5522fSEric Dumazet refcount_inc(&dir->no_tracker); 918fd5522fSEric Dumazet return 0; 928fd5522fSEric Dumazet } 93c12837d1SEric Dumazet if (gfp & __GFP_DIRECT_RECLAIM) 94c12837d1SEric Dumazet gfp_mask |= __GFP_NOFAIL; 95c12837d1SEric Dumazet *trackerp = tracker = kzalloc(sizeof(*tracker), gfp_mask); 964e66934eSEric Dumazet if (unlikely(!tracker)) { 974e66934eSEric Dumazet pr_err_once("memory allocation failure, unreliable refcount tracker.\n"); 984e66934eSEric Dumazet refcount_inc(&dir->untracked); 994e66934eSEric Dumazet return -ENOMEM; 1004e66934eSEric Dumazet } 1014e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 1024e66934eSEric Dumazet tracker->alloc_stack_handle = stack_depot_save(entries, nr_entries, gfp); 1034e66934eSEric Dumazet 1044e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 1054e66934eSEric Dumazet list_add(&tracker->head, &dir->list); 1064e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1074e66934eSEric Dumazet return 0; 1084e66934eSEric Dumazet } 1094e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_alloc); 1104e66934eSEric Dumazet 1114e66934eSEric Dumazet int ref_tracker_free(struct ref_tracker_dir *dir, 1124e66934eSEric Dumazet struct ref_tracker **trackerp) 1134e66934eSEric Dumazet { 1144e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 1154e66934eSEric Dumazet depot_stack_handle_t stack_handle; 1168fd5522fSEric Dumazet struct ref_tracker *tracker; 1174e66934eSEric Dumazet unsigned int nr_entries; 1184e66934eSEric Dumazet unsigned long flags; 1194e66934eSEric Dumazet 120e3ececfeSEric Dumazet WARN_ON_ONCE(dir->dead); 121e3ececfeSEric Dumazet 1228fd5522fSEric Dumazet if (!trackerp) { 1238fd5522fSEric Dumazet refcount_dec(&dir->no_tracker); 1248fd5522fSEric Dumazet return 0; 1258fd5522fSEric Dumazet } 1268fd5522fSEric Dumazet tracker = *trackerp; 1274e66934eSEric Dumazet if (!tracker) { 1284e66934eSEric Dumazet refcount_dec(&dir->untracked); 1294e66934eSEric Dumazet return -EEXIST; 1304e66934eSEric Dumazet } 1314e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 1324e66934eSEric Dumazet stack_handle = stack_depot_save(entries, nr_entries, GFP_ATOMIC); 1334e66934eSEric Dumazet 1344e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 1354e66934eSEric Dumazet if (tracker->dead) { 1364e66934eSEric Dumazet pr_err("reference already released.\n"); 1374e66934eSEric Dumazet if (tracker->alloc_stack_handle) { 1384e66934eSEric Dumazet pr_err("allocated in:\n"); 1394e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 1404e66934eSEric Dumazet } 1414e66934eSEric Dumazet if (tracker->free_stack_handle) { 1424e66934eSEric Dumazet pr_err("freed in:\n"); 1434e66934eSEric Dumazet stack_depot_print(tracker->free_stack_handle); 1444e66934eSEric Dumazet } 1454e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1464e66934eSEric Dumazet WARN_ON_ONCE(1); 1474e66934eSEric Dumazet return -EINVAL; 1484e66934eSEric Dumazet } 1494e66934eSEric Dumazet tracker->dead = true; 1504e66934eSEric Dumazet 1514e66934eSEric Dumazet tracker->free_stack_handle = stack_handle; 1524e66934eSEric Dumazet 1534e66934eSEric Dumazet list_move_tail(&tracker->head, &dir->quarantine); 1544e66934eSEric Dumazet if (!dir->quarantine_avail) { 1554e66934eSEric Dumazet tracker = list_first_entry(&dir->quarantine, struct ref_tracker, head); 1564e66934eSEric Dumazet list_del(&tracker->head); 1574e66934eSEric Dumazet } else { 1584e66934eSEric Dumazet dir->quarantine_avail--; 1594e66934eSEric Dumazet tracker = NULL; 1604e66934eSEric Dumazet } 1614e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1624e66934eSEric Dumazet 1634e66934eSEric Dumazet kfree(tracker); 1644e66934eSEric Dumazet return 0; 1654e66934eSEric Dumazet } 1664e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_free); 167