summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-06 14:53:42 +0100
committerMark Brown <broonie@kernel.org>2026-07-06 14:53:42 +0100
commit8c8c3cbc6977f456289d406241fa19d957281df6 (patch)
treebc8d84942b3e61a61db73209990619f8f74c2b5b
parentbfb669ccae628b14d5bf57d84aebcc704d9a8889 (diff)
parent110ef9f94f84d425ab55d09b2d70ed12ceffdf08 (diff)
downloadlinux-next-8c8c3cbc6977f456289d406241fa19d957281df6.tar.gz
linux-next-8c8c3cbc6977f456289d406241fa19d957281df6.zip
Merge branch 'master' of git://git.code.sf.net/p/tomoyo/tomoyo.git
-rw-r--r--drivers/block/loop.c87
-rw-r--r--fs/namespace.c5
-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.c4
-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
-rw-r--r--security/apparmor/Kconfig1
15 files changed, 449 insertions, 13 deletions
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index 310de0463beb..7408f314a1fa 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -85,8 +85,27 @@ struct loop_cmd {
struct bio_vec *bvec;
struct cgroup_subsys_state *blkcg_css;
struct cgroup_subsys_state *memcg_css;
+#ifdef CONFIG_KCOV
+ unsigned long stack_entries[30];
+ int stack_nr;
+ pid_t pid;
+ char comm[TASK_COMM_LEN];
+#endif
};
+static void loop_check_io_race(struct loop_device *lo, struct loop_cmd *cmd)
+{
+#ifdef CONFIG_KCOV
+ if (unlikely(data_race(READ_ONCE(lo->lo_state)) == Lo_rundown &&
+ disk_openers(lo->lo_disk) == 0)) {
+ pr_err("BUG: %s/%u is doing I/O request on loop%d in Lo_rundown state.\n",
+ cmd->comm, cmd->pid, lo->lo_number);
+ printk("Call Trace:\n");
+ stack_trace_print(cmd->stack_entries, cmd->stack_nr, 4);
+ }
+#endif
+}
+
#define LOOP_IDLE_WORKER_TIMEOUT (60 * HZ)
#define LOOP_DEFAULT_HW_Q_DEPTH 128
@@ -1743,8 +1762,63 @@ static void lo_release(struct gendisk *disk)
need_clear = (lo->lo_state == Lo_rundown);
mutex_unlock(&lo->lo_mutex);
- if (need_clear)
+ if (need_clear) {
+ printk("Flush: task=%s[%d] dev=loop%d state=%d\n",
+ current->comm, current->pid, lo->lo_number, lo->lo_state);
+ /*
+ * Temporarily release disk->open_mutex in order to flush pending I/O
+ * requests before clearing the backing device.
+ *
+ * This is a layering violation. But since bdev->bd_disk->fops->release()
+ * (which is mapped to lo_release()) is the final function which
+ * blkdev_put_whole() from bdev_release() calls immediately before
+ * releasing disk->open_mutex, this changes nothing except opens a new
+ * race window for allowing disk->fops->open() (which is mapped to
+ * lo_open()) to be called.
+ *
+ * Even if lo_open() is called from blkdev_get_whole() due to this race,
+ * the Lo_rundown state guarantees that lo_open() will fail with -ENXIO.
+ * Thus, there will be effectively no change caused by this violation.
+ */
+ mutex_unlock(&lo->lo_disk->open_mutex);
+ /*
+ * Now that loop_queue_rq() sees lo->lo_state != Lo_bound,
+ * wait for already started loop_queue_rq() to complete.
+ */
+ synchronize_rcu();
+ /*
+ * Now that no more works are scheduled by loop_queue_rq(),
+ * wait for already scheduled works to complete.
+ */
+ drain_workqueue(lo->workqueue);
+ /*
+ * Now that no more AIO requests are scheduled by lo_rw_aio(),
+ * wait for already started AIO to complete.
+ *
+ * Due to synchronize_rcu() + drain_workqueue() sequence above,
+ * calling blk_mq_unfreeze_queue() immediately after blk_mq_freeze_queue()
+ * returns has to be safe, for loop_queue_rq() no longer schedules new
+ * lo_rw_aio() works and lo_rw_aio() no longer submits new AIO requests.
+ *
+ * Deferring blk_mq_unfreeze_queue() does not help because we are about
+ * to clear the backing device and drop the refcount for the backing device.
+ * There is nothing we can do if blk_mq_freeze_queue() fails to flush.
+ */
+ blk_mq_unfreeze_queue(lo->lo_queue, blk_mq_freeze_queue(lo->lo_queue));
+ /*
+ * Perform remaining cleanup, with disk->open_mutex held.
+ *
+ * The lo->lo_state should remain Lo_rundown despite we temporarily
+ * released disk->open_mutex, for I am the only and the last user of
+ * this loop device because lo_open() cannot succeed.
+ */
+ mutex_lock(&lo->lo_disk->open_mutex);
+ if (WARN_ON(data_race(READ_ONCE(lo->lo_state)) != Lo_rundown))
+ return;
+ printk("Teardown: task=%s[%d] dev=loop%d state=%d\n",
+ current->comm, current->pid, lo->lo_number, lo->lo_state);
__loop_clr_fd(lo);
+ }
}
static void lo_free_disk(struct gendisk *disk)
@@ -1851,10 +1925,18 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx,
struct loop_cmd *cmd = blk_mq_rq_to_pdu(rq);
struct loop_device *lo = rq->q->queuedata;
+#ifdef CONFIG_KCOV
+ cmd->stack_nr = stack_trace_save(cmd->stack_entries, ARRAY_SIZE(cmd->stack_entries), 0);
+ cmd->pid = current->pid;
+ get_task_comm(cmd->comm, current);
+#endif
+
blk_mq_start_request(rq);
- if (data_race(READ_ONCE(lo->lo_state)) != Lo_bound)
+ if (data_race(READ_ONCE(lo->lo_state)) != Lo_bound) {
+ loop_check_io_race(lo, cmd);
return BLK_STS_IOERR;
+ }
switch (req_op(rq)) {
case REQ_OP_FLUSH:
@@ -1897,6 +1979,7 @@ static void loop_handle_cmd(struct loop_cmd *cmd)
int ret = 0;
struct mem_cgroup *old_memcg = NULL;
+ loop_check_io_race(lo, cmd);
if (write && (lo->lo_flags & LO_FLAGS_READ_ONLY)) {
ret = -EIO;
goto failed;
diff --git a/fs/namespace.c b/fs/namespace.c
index 6b81b77f9303..afc8da2afd67 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -1893,6 +1893,8 @@ static int do_umount(struct mount *mnt, int flags)
*/
lock_mount_hash();
if (!list_empty(&mnt->mnt_mounts) || mnt_get_count(mnt) != 2) {
+ printk("%s: task=%s[%d] !list_empty(&mnt->mnt_mounts)=%d mnt_get_count(mnt)=%d\n", __func__,
+ current->comm, current->pid, !list_empty(&mnt->mnt_mounts), mnt_get_count(mnt));
unlock_mount_hash();
return -EBUSY;
}
@@ -1960,6 +1962,9 @@ static int do_umount(struct mount *mnt, int flags)
if (!propagate_mount_busy(mnt, 2)) {
umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC);
retval = 0;
+ } else {
+ printk("%s: task=%s[%d] propagate_mount_busy()!=0\n", __func__,
+ current->comm, current->pid);
}
}
out:
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b5..987c3c87f14f 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2142,6 +2142,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 {
@@ -2298,6 +2300,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;
@@ -3248,6 +3253,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);
@@ -4469,9 +4475,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
@@ -4483,6 +4495,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 307073eae7f8..10b73fb45117 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 51185222aac2..a043fd422089 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -2206,8 +2206,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 a69567bdd791..e9ca4b26efec 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 4425d8dce44b..a3bd54de8259 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 78068ae8f28a..78f25afb4a9d 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -3203,6 +3203,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
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index bd961278dd45..77339e68a6fb 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2233,6 +2233,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 a8c67035e23c..5b3a85b4be8f 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 5933c5dab09e..547e10516258 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1875,6 +1875,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";
@@ -11581,6 +11582,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
@@ -11654,11 +11663,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;
}
@@ -12058,6 +12072,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);
@@ -12234,6 +12251,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) {
@@ -13338,3 +13357,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 092861133023..45452fa62e24 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 8a81c5430705..c1a44594c492 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 b0256cd222f8..068cd8a7705b 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -723,7 +723,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);
@@ -795,7 +799,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,
@@ -1145,8 +1155,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,
@@ -2649,9 +2664,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));
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 1e3bd44643da..1c5608068ba3 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -2,6 +2,7 @@
config SECURITY_APPARMOR
bool "AppArmor support"
depends on SECURITY && NET
+ depends on !DEBUG_AID_FOR_SYZBOT
select AUDIT
select SECURITY_PATH
select SECURITYFS