aboutsummaryrefslogtreecommitdiffstats
diff options
authorThierry Reding <treding@nvidia.com>2026-04-30 10:33:28 +0200
committerThierry Reding <treding@nvidia.com>2026-04-30 10:33:28 +0200
commit9b6b87ab815460695ab868f81348edaf007d333c (patch)
treed6624ad88deae1164c36ad267736b148da285545
parent657fda65c89c29c430725740453b782d74188e64 (diff)
parentde8a048f0f6a770f0afb7e988f8881c2903dc4ea (diff)
downloadlinux-next-9b6b87ab815460695ab868f81348edaf007d333c.tar.gz
Merge branch 'master' of git://git.code.sf.net/p/tomoyo/tomoyo.git
-rw-r--r--drivers/block/loop.c39
-rw-r--r--include/linux/netdevice.h15
-rw-r--r--include/net/dst.h22
-rw-r--r--include/net/sock.h8
-rw-r--r--kernel/locking/lockdep.c2
-rw-r--r--kernel/softirq.c4
-rw-r--r--kernel/workqueue.c28
-rw-r--r--lib/Kconfig.debug7
-rw-r--r--net/bridge/br_nf_core.c1
-rw-r--r--net/core/dev.c188
-rw-r--r--net/core/dst.c85
-rw-r--r--net/core/lock_debug.c1
-rw-r--r--net/socket.c32
13 files changed, 419 insertions, 13 deletions
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index 0000913f7efcc..3ba06b632aa03 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -74,6 +74,14 @@ struct loop_device {
struct gendisk *lo_disk;
struct mutex lo_mutex;
bool idr_visible;
+
+#ifdef CONFIG_DEBUG_AID_FOR_SYZBOT
+ pid_t clear_pid;
+ char clear_comm[TASK_COMM_LEN];
+ unsigned long clear_jiffies;
+ int clear_nr_entries;
+ unsigned long clear_entries[20];
+#endif
};
struct loop_cmd {
@@ -94,6 +102,26 @@ static DEFINE_IDR(loop_index_idr);
static DEFINE_MUTEX(loop_ctl_mutex);
static DEFINE_MUTEX(loop_validate_mutex);
+static struct file *check_file_not_null(struct loop_device *lo)
+{
+ struct file *file = READ_ONCE(lo->lo_backing_file);
+
+#ifdef CONFIG_DEBUG_AID_FOR_SYZBOT
+ if (!file)
+ pr_err("file == NULL\n");
+ else if (IS_ERR(file))
+ pr_err("IS_ERR(file)\n");
+ else
+ return file;
+ if (lo->clear_nr_entries) {
+ pr_err("Last __loop_clr_fd() call was %lu jiffies ago by %s (%d)\n",
+ jiffies - lo->clear_jiffies, lo->clear_comm, lo->clear_pid);
+ stack_trace_print(lo->clear_entries, lo->clear_nr_entries, 4);
+ }
+#endif
+ return file;
+}
+
/**
* loop_global_lock_killable() - take locks for safe loop_validate_file() test
*
@@ -250,7 +278,7 @@ static int lo_fallocate(struct loop_device *lo, struct request *rq, loff_t pos,
* We use fallocate to manipulate the space mappings used by the image
* a.k.a. discard/zerorange.
*/
- struct file *file = lo->lo_backing_file;
+ struct file *file = check_file_not_null(lo);
int ret;
mode |= FALLOC_FL_KEEP_SIZE;
@@ -345,7 +373,7 @@ static int lo_rw_aio(struct loop_device *lo, struct loop_cmd *cmd,
struct bio_vec *bvec;
struct request *rq = blk_mq_rq_from_pdu(cmd);
struct bio *bio = rq->bio;
- struct file *file = lo->lo_backing_file;
+ struct file *file = check_file_not_null(lo);
struct bio_vec tmp;
unsigned int offset;
unsigned int nr_bvec;
@@ -1118,6 +1146,13 @@ static void __loop_clr_fd(struct loop_device *lo)
struct file *filp;
gfp_t gfp = lo->old_gfp_mask;
+#ifdef CONFIG_DEBUG_AID_FOR_SYZBOT
+ lo->clear_pid = current->pid;
+ get_task_comm(lo->clear_comm, current);
+ lo->clear_jiffies = jiffies;
+ lo->clear_nr_entries = stack_trace_save(lo->clear_entries,
+ ARRAY_SIZE(lo->clear_entries), 0);
+#endif
spin_lock_irq(&lo->lo_lock);
filp = lo->lo_backing_file;
lo->lo_backing_file = NULL;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 0e1e581efc5ac..99fcb5ac45bc2 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2119,6 +2119,8 @@ enum netdev_reg_state {
*
* FIXME: cleanup struct net_device such that network protocol info
* moves out.
+ *
+ * @netdev_trace_buffer_list: Linked list for debugging refcount leak.
*/
struct net_device {
@@ -2275,6 +2277,9 @@ struct net_device {
#if IS_ENABLED(CONFIG_TLS_DEVICE)
const struct tlsdev_ops *tlsdev_ops;
#endif
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ struct list_head netdev_trace_buffer_list;
+#endif
unsigned int operstate;
unsigned char link_mode;
@@ -3219,6 +3224,7 @@ enum netdev_cmd {
NETDEV_OFFLOAD_XSTATS_REPORT_USED,
NETDEV_OFFLOAD_XSTATS_REPORT_DELTA,
NETDEV_XDP_FEAT_CHANGE,
+ NETDEV_DEBUG_UNREGISTER,
};
const char *netdev_cmd_to_name(enum netdev_cmd cmd);
@@ -4441,9 +4447,15 @@ static inline bool dev_nit_active(const struct net_device *dev)
void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev);
+void save_netdev_trace_buffer(struct net_device *dev, int delta);
+int trim_netdev_trace(unsigned long *entries, int nr_entries);
+
static inline void __dev_put(struct net_device *dev)
{
if (dev) {
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ save_netdev_trace_buffer(dev, -1);
+#endif
#ifdef CONFIG_PCPU_DEV_REFCNT
this_cpu_dec(*dev->pcpu_refcnt);
#else
@@ -4455,6 +4467,9 @@ static inline void __dev_put(struct net_device *dev)
static inline void __dev_hold(struct net_device *dev)
{
if (dev) {
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ save_netdev_trace_buffer(dev, 1);
+#endif
#ifdef CONFIG_PCPU_DEV_REFCNT
this_cpu_inc(*dev->pcpu_refcnt);
#else
diff --git a/include/net/dst.h b/include/net/dst.h
index 307073eae7f83..10b73fb45117c 100644
--- a/include/net/dst.h
+++ b/include/net/dst.h
@@ -95,8 +95,19 @@ struct dst_entry {
#ifdef CONFIG_64BIT
struct lwtunnel_state *lwtstate;
#endif
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ atomic_t dst_trace_seq;
+#endif
};
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+void save_dst_trace_buffer(struct dst_entry *dst, int delta);
+void dump_dst_trace_buffer(const struct net_device *dev);
+#else
+static inline void save_dst_trace_buffer(struct dst_entry *dst, int delta) { };
+static inline void dump_dst_trace_buffer(const struct net_device *dev) { };
+#endif
+
struct dst_metrics {
u32 metrics[RTAX_MAX];
refcount_t refcnt;
@@ -244,7 +255,10 @@ static inline void dst_hold(struct dst_entry *dst)
* the placement of __rcuref in struct dst_entry
*/
BUILD_BUG_ON(offsetof(struct dst_entry, __rcuref) & 63);
- WARN_ON(!rcuref_get(&dst->__rcuref));
+ if (!rcuref_get(&dst->__rcuref))
+ WARN_ON(1);
+ else
+ save_dst_trace_buffer(dst, 1);
}
static inline void dst_use_noref(struct dst_entry *dst, unsigned long time)
@@ -308,7 +322,11 @@ static inline void skb_dst_copy(struct sk_buff *nskb, const struct sk_buff *oskb
*/
static inline bool dst_hold_safe(struct dst_entry *dst)
{
- return rcuref_get(&dst->__rcuref);
+ const bool ret = rcuref_get(&dst->__rcuref);
+
+ if (ret)
+ save_dst_trace_buffer(dst, 1);
+ return ret;
}
/**
diff --git a/include/net/sock.h b/include/net/sock.h
index dccd3738c3687..f5ec656a9fb67 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -2196,8 +2196,12 @@ sk_dst_get(const struct sock *sk)
rcu_read_lock();
dst = rcu_dereference(sk->sk_dst_cache);
- if (dst && !rcuref_get(&dst->__rcuref))
- dst = NULL;
+ if (dst) {
+ if (!rcuref_get(&dst->__rcuref))
+ dst = NULL;
+ else
+ save_dst_trace_buffer(dst, 1);
+ }
rcu_read_unlock();
return dst;
}
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 2d4c5bab5af88..f13883162cfe1 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -796,8 +796,10 @@ static void lockdep_print_held_locks(struct task_struct *p)
* It's not reliable to print a task's held locks if it's not sleeping
* and it's not the current task.
*/
+#ifndef CONFIG_DEBUG_AID_FOR_SYZBOT
if (p != current && task_is_running(p))
return;
+#endif
for (i = 0; i < depth; i++) {
printk(" #%d: ", i);
print_lock(p->held_locks + i);
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 4425d8dce44b4..a3bd54de82595 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -576,6 +576,10 @@ static inline bool lockdep_softirq_start(void) { return false; }
static inline void lockdep_softirq_end(bool in_hardirq) { }
#endif
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+static noinline void handle_softirqs(bool ksirqd);
+#endif
+
static void handle_softirqs(bool ksirqd)
{
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 5f747f241a5f1..bdf0879f4caf3 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -3183,6 +3183,10 @@ static bool manage_workers(struct worker *worker)
return true;
}
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+static noinline void process_one_work(struct worker *worker, struct work_struct *work);
+#endif
+
/**
* process_one_work - process single work
* @worker: self
@@ -3206,6 +3210,9 @@ __acquires(&pool->lock)
unsigned long work_data;
int lockdep_start_depth, rcu_start_depth;
bool bh_draining = pool->flags & POOL_BH_DRAINING;
+#ifdef CONFIG_KCOV
+ unsigned int old_kcov_mode, new_kcov_mode;
+#endif
#ifdef CONFIG_LOCKDEP
/*
* It is permissible to free the struct work_struct from
@@ -3299,7 +3306,13 @@ __acquires(&pool->lock)
*/
lockdep_invariant_state(true);
trace_workqueue_execute_start(work);
+#ifdef CONFIG_KCOV
+ old_kcov_mode = READ_ONCE(current->kcov_mode);
+#endif
worker->current_func(work);
+#ifdef CONFIG_KCOV
+ new_kcov_mode = READ_ONCE(current->kcov_mode);
+#endif
/*
* While we must be careful to not use "work" after this, the trace
* point will only record its address.
@@ -3322,6 +3335,11 @@ __acquires(&pool->lock)
debug_show_held_locks(current);
dump_stack();
}
+#ifdef CONFIG_KCOV
+ if (unlikely((old_kcov_mode & ~(1 << 30)) != (new_kcov_mode & ~(1 << 30))))
+ pr_err("BUG: workqueue function %ps changed kcov_mode from %u to %u\n",
+ worker->current_func, old_kcov_mode, new_kcov_mode);
+#endif
/*
* The following prevents a kworker from hogging CPU on !PREEMPTION
@@ -3413,6 +3431,11 @@ static int worker_thread(void *__worker)
struct worker *worker = __worker;
struct worker_pool *pool = worker->pool;
+#ifdef CONFIG_KCOV
+ if (unlikely(current->kcov_mode & ~(1 << 30)))
+ pr_err("BUG: %s started with kcov_mode=%u\n", __func__, current->kcov_mode);
+#endif
+
/* tell the scheduler that this is a workqueue worker */
set_pf_worker(true);
woke_up:
@@ -3565,6 +3588,11 @@ static int rescuer_thread(void *__rescuer)
struct workqueue_struct *wq = rescuer->rescue_wq;
bool should_stop;
+#ifdef CONFIG_KCOV
+ if (unlikely(current->kcov_mode & ~(1 << 30)))
+ pr_err("BUG: %s started with kcov_mode=%u\n", __func__, current->kcov_mode);
+#endif
+
set_user_nice(current, RESCUER_NICE_LEVEL);
/*
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index ad5131776f684..4ebaea9974ab9 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2261,6 +2261,13 @@ config KCOV_SELFTEST
On test failure, causes the kernel to panic. Recommended to be
enabled, ensuring critical functionality works as intended.
+config DEBUG_AID_FOR_SYZBOT
+ bool "Additional debug code for syzbot"
+ default n
+ select PREEMPT_RT if EXPERT && ARCH_SUPPORTS_RT && !COMPILE_TEST
+ help
+ This option is intended for testing by syzbot.
+
menuconfig RUNTIME_TESTING_MENU
bool "Runtime Testing"
default y
diff --git a/net/bridge/br_nf_core.c b/net/bridge/br_nf_core.c
index a8c67035e23c0..5b3a85b4be8fd 100644
--- a/net/bridge/br_nf_core.c
+++ b/net/bridge/br_nf_core.c
@@ -70,6 +70,7 @@ void br_netfilter_rtable_init(struct net_bridge *br)
struct rtable *rt = &br->fake_rtable;
rcuref_init(&rt->dst.__rcuref, 1);
+ save_dst_trace_buffer(&rt->dst, 1);
rt->dst.dev = br->dev;
dst_init_metrics(&rt->dst, br->metrics, false);
dst_metric_set(&rt->dst, RTAX_MTU, br->dev->mtu);
diff --git a/net/core/dev.c b/net/core/dev.c
index 06c195906231a..0e8db7db171b3 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1874,6 +1874,7 @@ const char *netdev_cmd_to_name(enum netdev_cmd cmd)
N(PRE_CHANGEADDR) N(OFFLOAD_XSTATS_ENABLE) N(OFFLOAD_XSTATS_DISABLE)
N(OFFLOAD_XSTATS_REPORT_USED) N(OFFLOAD_XSTATS_REPORT_DELTA)
N(XDP_FEAT_CHANGE)
+ N(DEBUG_UNREGISTER)
}
#undef N
return "UNKNOWN_NETDEV_EVENT";
@@ -11555,6 +11556,14 @@ int netdev_refcnt_read(const struct net_device *dev)
}
EXPORT_SYMBOL(netdev_refcnt_read);
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+static void dump_netdev_trace_buffer(const struct net_device *dev);
+static void erase_netdev_trace_buffer(const struct net_device *dev);
+#else
+static inline void dump_netdev_trace_buffer(const struct net_device *dev) { }
+static inline void erase_netdev_trace_buffer(const struct net_device *dev) { }
+#endif
+
int netdev_unregister_timeout_secs __read_mostly = 10;
#define WAIT_REFS_MIN_MSECS 1
@@ -11628,11 +11637,16 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list)
if (time_after(jiffies, warning_time +
READ_ONCE(netdev_unregister_timeout_secs) * HZ)) {
+ rtnl_lock();
list_for_each_entry(dev, list, todo_list) {
pr_emerg("unregister_netdevice: waiting for %s to become free. Usage count = %d\n",
dev->name, netdev_refcnt_read(dev));
ref_tracker_dir_print(&dev->refcnt_tracker, 10);
+ call_netdevice_notifiers(NETDEV_DEBUG_UNREGISTER, dev);
+ dump_netdev_trace_buffer(dev);
}
+ __rtnl_unlock();
+ rcu_barrier();
warning_time = jiffies;
}
@@ -12030,6 +12044,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
dev->priv_len = sizeof_priv;
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ INIT_LIST_HEAD(&dev->netdev_trace_buffer_list);
+#endif
ref_tracker_dir_init(&dev->refcnt_tracker, 128, "netdev");
#ifdef CONFIG_PCPU_DEV_REFCNT
dev->pcpu_refcnt = alloc_percpu(int);
@@ -12206,6 +12223,8 @@ void free_netdev(struct net_device *dev)
mutex_destroy(&dev->lock);
+ erase_netdev_trace_buffer(dev);
+
/* Compatibility with error handling in drivers */
if (dev->reg_state == NETREG_UNINITIALIZED ||
dev->reg_state == NETREG_DUMMY) {
@@ -13312,3 +13331,172 @@ out:
}
subsys_initcall(net_dev_init);
+
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+
+#define NETDEV_TRACE_BUFFER_SIZE 32768
+static struct netdev_trace_buffer {
+ struct list_head list;
+ int prev_count;
+ atomic_t count;
+ int nr_entries;
+ unsigned long entries[20];
+} netdev_trace_buffer[NETDEV_TRACE_BUFFER_SIZE];
+static LIST_HEAD(netdev_trace_buffer_list);
+static DEFINE_SPINLOCK(netdev_trace_buffer_lock);
+static bool netdev_trace_buffer_exhausted;
+
+static int netdev_trace_buffer_init(void)
+{
+ int i;
+
+ for (i = 0; i < NETDEV_TRACE_BUFFER_SIZE; i++)
+ list_add_tail(&netdev_trace_buffer[i].list, &netdev_trace_buffer_list);
+ return 0;
+}
+pure_initcall(netdev_trace_buffer_init);
+
+static void dump_netdev_trace_buffer(const struct net_device *dev)
+{
+ struct netdev_trace_buffer *ptr;
+ int count, balance = 0, pos = 0;
+
+ dump_dst_trace_buffer(dev);
+ list_for_each_entry_rcu(ptr, &dev->netdev_trace_buffer_list, list,
+ /* list elements can't go away. */ 1) {
+ pos++;
+ count = atomic_read(&ptr->count);
+ balance += count;
+ if (ptr->prev_count == count)
+ continue;
+ ptr->prev_count = count;
+ pr_info("Call trace for %s[%d] %+d at\n", dev->name, pos, count);
+ stack_trace_print(ptr->entries, ptr->nr_entries, 4);
+ cond_resched();
+ }
+ if (!netdev_trace_buffer_exhausted)
+ pr_info("balance as of %s[%d] is %d\n", dev->name, pos, balance);
+}
+
+static void erase_netdev_trace_buffer(const struct net_device *dev)
+{
+ struct netdev_trace_buffer *ptr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&netdev_trace_buffer_lock, flags);
+ while (!list_empty(&dev->netdev_trace_buffer_list)) {
+ ptr = list_first_entry(&dev->netdev_trace_buffer_list, typeof(*ptr), list);
+ list_del(&ptr->list);
+ list_add_tail(&ptr->list, &netdev_trace_buffer_list);
+ }
+ spin_unlock_irqrestore(&netdev_trace_buffer_lock, flags);
+}
+
+int trim_netdev_trace(unsigned long *entries, int nr_entries)
+{
+#ifdef CONFIG_KALLSYMS
+ char buffer[32] = { };
+ char *cp;
+ int i;
+
+ if (in_softirq()) {
+ static unsigned long __data_racy caller;
+
+ if (!caller) {
+ for (i = 0; i < nr_entries; i++) {
+ snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]);
+ cp = strchr(buffer, ' ');
+ if (cp)
+ *cp = '\0';
+ if (!strcmp(buffer, "handle_softirqs")) {
+ caller = entries[i];
+ break;
+ }
+ }
+ }
+ for (i = 0; i < nr_entries; i++)
+ if (entries[i] == caller)
+ return i + 1;
+ } else if (current->flags & PF_WQ_WORKER) {
+ static unsigned long __data_racy caller;
+
+ if (!caller) {
+ for (i = 0; i < nr_entries; i++) {
+ snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]);
+ cp = strchr(buffer, ' ');
+ if (cp)
+ *cp = '\0';
+ if (!strcmp(buffer, "process_one_work")) {
+ caller = entries[i];
+ break;
+ }
+ }
+ }
+ for (i = 0; i < nr_entries; i++)
+ if (entries[i] == caller)
+ return i + 1;
+ } else {
+ for (i = 0; i < nr_entries; i++) {
+ snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]);
+ cp = strchr(buffer, ' ');
+ if (cp)
+ *cp = '\0';
+ if (buffer[0] == 'k') {
+ if (!strcmp(buffer, "ksys_unshare"))
+ return i + 1;
+ } else if (buffer[0] == 's') {
+ if (!strcmp(buffer, "sock_sendmsg_nosec") ||
+ !strcmp(buffer, "sock_recvmsg_nosec"))
+ return i + 1;
+ } else if (buffer[0] == '_') {
+ if (!strcmp(buffer, "__sys_bind") ||
+ !strcmp(buffer, "__sock_release") ||
+ !strcmp(buffer, "__sys_bpf"))
+ return i + 1;
+ } else {
+ if (!strcmp(buffer, "do_sock_setsockopt"))
+ return i + 1;
+ }
+ }
+ }
+#endif
+ return nr_entries;
+}
+EXPORT_SYMBOL(trim_netdev_trace);
+
+void save_netdev_trace_buffer(struct net_device *dev, int delta)
+{
+ struct netdev_trace_buffer *ptr;
+ unsigned long entries[ARRAY_SIZE(ptr->entries)];
+ unsigned long nr_entries;
+ unsigned long flags;
+
+ if (in_nmi())
+ return;
+ nr_entries = stack_trace_save(entries, ARRAY_SIZE(ptr->entries), 1);
+ nr_entries = trim_netdev_trace(entries, nr_entries);
+ list_for_each_entry_rcu(ptr, &dev->netdev_trace_buffer_list, list,
+ /* list elements can't go away. */ 1) {
+ if (ptr->nr_entries == nr_entries &&
+ !memcmp(ptr->entries, entries, nr_entries * sizeof(unsigned long))) {
+ atomic_add(delta, &ptr->count);
+ return;
+ }
+ }
+ spin_lock_irqsave(&netdev_trace_buffer_lock, flags);
+ if (!list_empty(&netdev_trace_buffer_list)) {
+ ptr = list_first_entry(&netdev_trace_buffer_list, typeof(*ptr), list);
+ list_del(&ptr->list);
+ ptr->prev_count = 0;
+ atomic_set(&ptr->count, delta);
+ ptr->nr_entries = nr_entries;
+ memmove(ptr->entries, entries, nr_entries * sizeof(unsigned long));
+ list_add_tail_rcu(&ptr->list, &dev->netdev_trace_buffer_list);
+ } else {
+ netdev_trace_buffer_exhausted = true;
+ }
+ spin_unlock_irqrestore(&netdev_trace_buffer_lock, flags);
+}
+EXPORT_SYMBOL(save_netdev_trace_buffer);
+
+#endif
diff --git a/net/core/dst.c b/net/core/dst.c
index 092861133023c..45452fa62e24f 100644
--- a/net/core/dst.c
+++ b/net/core/dst.c
@@ -44,6 +44,82 @@ const struct dst_metrics dst_default_metrics = {
};
EXPORT_SYMBOL(dst_default_metrics);
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+
+#define DST_TRACE_BUFFER_SIZE 8192
+static struct dst_trace_buffer {
+ struct dst_entry *dst; // no-ref
+ struct net_device *ndev; // no-ref
+ int seq;
+ int delta;
+ u32 caller_id;
+ int nr_entries;
+ unsigned long entries[20];
+} dst_trace_buffer[DST_TRACE_BUFFER_SIZE];
+static bool dst_trace_buffer_exhausted;
+
+void dump_dst_trace_buffer(const struct net_device *ndev)
+{
+ struct dst_trace_buffer *ptr;
+ int count, balance = 0;
+ int i;
+
+ for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++) {
+ ptr = &dst_trace_buffer[i];
+ if (!ptr->dst || ptr->ndev != ndev)
+ continue;
+ count = ptr->delta;
+ balance += count;
+ pr_info("Call trace for %s@%p[%d] %+d %c%u at\n", ndev->name, ptr->dst, ptr->seq,
+ count, ptr->caller_id & 0x80000000 ? 'C' : 'T',
+ ptr->caller_id & ~0x80000000);
+ stack_trace_print(ptr->entries, ptr->nr_entries, 4);
+ }
+ if (!dst_trace_buffer_exhausted)
+ pr_info("balance for %s@dst_entry is %d\n", ndev->name, balance);
+ else
+ pr_info("balance for %s@dst_entry is unknown\n", ndev->name);
+}
+
+static void erase_dst_trace_buffer(struct dst_entry *dst)
+{
+ int i;
+
+ for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++)
+ if (dst_trace_buffer[i].dst == dst)
+ dst_trace_buffer[i].dst = NULL;
+}
+
+void save_dst_trace_buffer(struct dst_entry *dst, int delta)
+{
+ struct dst_trace_buffer *ptr;
+ unsigned long entries[ARRAY_SIZE(ptr->entries)];
+ unsigned long nr_entries;
+ int i;
+
+ if (!dst->dev)
+ return;
+ nr_entries = stack_trace_save(entries, ARRAY_SIZE(ptr->entries), 1);
+ nr_entries = trim_netdev_trace(entries, nr_entries);
+ for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++) {
+ ptr = &dst_trace_buffer[i];
+ if (!ptr->dst && !cmpxchg(&ptr->dst, NULL, dst)) {
+ ptr->ndev = dst->dev;
+ ptr->seq = atomic_inc_return(&dst->dst_trace_seq);
+ ptr->delta = delta;
+ ptr->caller_id = in_task() ? task_pid_nr(current) :
+ 0x80000000 + raw_smp_processor_id();
+ ptr->nr_entries = nr_entries;
+ memmove(ptr->entries, entries, nr_entries * sizeof(unsigned long));
+ return;
+ }
+ }
+ dst_trace_buffer_exhausted = true;
+}
+EXPORT_SYMBOL(save_dst_trace_buffer);
+
+#endif
+
void dst_init(struct dst_entry *dst, struct dst_ops *ops,
struct net_device *dev, int initial_obsolete,
unsigned short flags)
@@ -67,6 +143,7 @@ void dst_init(struct dst_entry *dst, struct dst_ops *ops,
#endif
dst->lwtstate = NULL;
rcuref_init(&dst->__rcuref, 1);
+ save_dst_trace_buffer(dst, 1);
INIT_LIST_HEAD(&dst->rt_uncached);
dst->rt_uncached_list = NULL;
dst->__use = 0;
@@ -116,6 +193,10 @@ static void dst_destroy(struct dst_entry *dst)
lwtstate_put(dst->lwtstate);
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+ erase_dst_trace_buffer(dst);
+#endif
+
if (dst->flags & DST_METADATA)
metadata_dst_free((struct metadata_dst *)dst);
else
@@ -165,6 +246,8 @@ static void dst_count_dec(struct dst_entry *dst)
void dst_release(struct dst_entry *dst)
{
+ if (dst)
+ save_dst_trace_buffer(dst, -1);
if (dst && rcuref_put(&dst->__rcuref)) {
#ifdef CONFIG_DST_CACHE
if (dst->flags & DST_METADATA) {
@@ -182,6 +265,8 @@ EXPORT_SYMBOL(dst_release);
void dst_release_immediate(struct dst_entry *dst)
{
+ if (dst)
+ save_dst_trace_buffer(dst, -1);
if (dst && rcuref_put(&dst->__rcuref)) {
dst_count_dec(dst);
dst_destroy(dst);
diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c
index 9e9fb25314b9c..78d611bb6d1cd 100644
--- a/net/core/lock_debug.c
+++ b/net/core/lock_debug.c
@@ -29,6 +29,7 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event,
case NETDEV_DOWN:
case NETDEV_REBOOT:
case NETDEV_UNREGISTER:
+ case NETDEV_DEBUG_UNREGISTER:
case NETDEV_CHANGEMTU:
case NETDEV_CHANGEADDR:
case NETDEV_PRE_CHANGEADDR:
diff --git a/net/socket.c b/net/socket.c
index 22a412fdec079..12324a9967c59 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -710,7 +710,11 @@ struct socket *sock_alloc(void)
}
EXPORT_SYMBOL(sock_alloc);
-static void __sock_release(struct socket *sock, struct inode *inode)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#endif
+void __sock_release(struct socket *sock, struct inode *inode)
{
const struct proto_ops *ops = READ_ONCE(sock->ops);
@@ -782,7 +786,13 @@ static noinline void call_trace_sock_send_length(struct sock *sk, int ret,
trace_sock_send_length(sk, ret, 0);
}
-static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#else
+inline
+#endif
+int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
{
int ret = INDIRECT_CALL_INET(READ_ONCE(sock->ops)->sendmsg, inet6_sendmsg,
inet_sendmsg, sock, msg,
@@ -1131,8 +1141,13 @@ static noinline void call_trace_sock_recv_length(struct sock *sk, int ret, int f
trace_sock_recv_length(sk, ret, flags);
}
-static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg,
- int flags)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#else
+inline
+#endif
+int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg, int flags)
{
int ret = INDIRECT_CALL_INET(READ_ONCE(sock->ops)->recvmsg,
inet6_recvmsg,
@@ -2638,9 +2653,12 @@ static int copy_msghdr_from_user(struct msghdr *kmsg,
return err < 0 ? err : 0;
}
-static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
- unsigned int flags, struct used_address *used_address,
- unsigned int allowed_msghdr_flags)
+static
+#ifdef CONFIG_NET_DEV_REFCNT_TRACKER
+noinline
+#endif
+int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys, unsigned int flags,
+ struct used_address *used_address, unsigned int allowed_msghdr_flags)
{
unsigned char ctl[sizeof(struct cmsghdr) + 20]
__aligned(sizeof(__kernel_size_t));