Subject: [PATCH] net: rose: cancel timers before freeing rose_neigh in rose_neigh_put()
From: Arjan van de Ven <[email-masked]>

This patch is based on a KASAN slab-use-after-free as reported by
syzbot at https://lore.kernel.org/r/[email-masked].

rose_neigh_put() was introduced by d860d1faa6b2 ("net: rose: convert
'use' field to refcount_t") to free rose_neigh when its refcount drops
to zero.  The struct rose_neigh embeds two timer_list fields (ftimer
and t0timer) that may still be pending in the timer wheel when the
final rose_neigh_put() is called from rose_timer_expiry() in softirq
context.

The race is:

  CPU 0 (TIMER_SOFTIRQ, one batch):
    1. rose_timer_expiry() handles ROSE_STATE_2:
         rose_neigh_put(rose->neighbour)
           -> refcount_dec_and_test() returns true
           -> kfree(rose_neigh->digipeat)   /* frees ax25_digi */
           -> kfree(rose_neigh)
    2. rose_t0timer_expiry() fires next in the same batch:
         neigh = timer_container_of(neigh, t, t0timer)  /* dangling */
         rose_transmit_restart_request(neigh)
           -> rose_send_frame() passes neigh->digipeat to ax25_send_frame()
           -> kmemdup(digi, 66, GFP_ATOMIC)     /* UAF read */

Before commit d860d1faa6b2, rose_timer_expiry() only decremented the
plain 'use' counter without ever freeing the struct; the free was always
done by rose_remove_neigh() which calls timer_delete_sync() on both
timers before freeing.  The refcount conversion introduced a new free
path from softirq context that omitted the timer cancellation.

Fix: cancel both timers inside rose_neigh_put() before freeing.
timer_delete() (non-synchronous) is used because rose_neigh_put() may
be called from softirq context where timer_delete_sync() would
deadlock.  The cancellation is safe: after refcount_dec_and_test()
returns true no other thread holds a reference, so no new timer arm
can occur before the kfree.

Fixes: d860d1faa6b2 ("net: rose: convert 'use' field to refcount_t")
Reported-by: [email-masked]
Link: https://lore.kernel.org/r/[email-masked]
Oops-Analysis: http://oops.fenrus.org/reports/lkml/69ef2847.170a0220.11de9.001a.GAE_google.com/report.html
Assisted-by: Copilot:claude-sonnet-4.6 linux-kernel-oops-x86.
Signed-off-by: Arjan van de Ven <[email-masked]>
---
 include/net/rose.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/net/rose.h b/include/net/rose.h
index 2b5491bbf39a..93a26b08e648 100644
--- a/include/net/rose.h
+++ b/include/net/rose.h
@@ -160,6 +160,8 @@ static inline void rose_neigh_hold(struct rose_neigh *rose_neigh)
 static inline void rose_neigh_put(struct rose_neigh *rose_neigh)
 {
 	if (refcount_dec_and_test(&rose_neigh->use)) {
+		timer_delete(&rose_neigh->ftimer);
+		timer_delete(&rose_neigh->t0timer);
 		if (rose_neigh->ax25)
 			ax25_cb_put(rose_neigh->ax25);
 		kfree(rose_neigh->digipeat);