Subject: Re: [syzbot] [hams?] KASAN: slab-use-after-free Read in ax25_send_frame (3) This email is created by automation to help kernel developers deal with a large volume of AI generated bug reports by decoding oopses into more actionable information. Decoded Backtrace net/ax25/ax25_out.c (crash site, UAF read) 32 ax25_cb *ax25_send_frame(struct sk_buff *skb, int paclen, 32 const ax25_address *src, ax25_address *dest, 32 ax25_digi *digi, struct net_device *dev) 33 { 34 ax25_dev *ax25_dev; 35 ax25_cb *ax25; ... 77 if (digi != NULL) { ->78 ax25->digipeat = kmemdup(digi, sizeof(*digi), GFP_ATOMIC); // <- digi = neigh->digipeat; freed ax25_digi; 66-byte UAF read 79 if (ax25->digipeat == NULL) { ... 115 return ax25; 116 } net/rose/rose_link.c (caller, t0timer callback) 79 static void rose_t0timer_expiry(struct timer_list *t) 80 { 81 struct rose_neigh *neigh = timer_container_of(neigh, t, t0timer); 82 ->83 rose_transmit_restart_request(neigh); // <- inlined; calls rose_send_frame -> ax25_send_frame // with neigh->digipeat as the digi argument 84 85 neigh->dce_mode = 0; 86 87 rose_start_t0timer(neigh); 88 } rose_send_frame() inlined at rose_link.c:106: 95 static int rose_send_frame(struct sk_buff *skb, struct rose_neigh *neigh) 96 { ... 105 ax25s = neigh->ax25; ->106 neigh->ax25 = ax25_send_frame(skb, 260, rose_call, &neigh->callsign, neigh->digipeat, neigh->dev); // <- neigh->digipeat passed as digi; freed by rose_timer_expiry 107 if (ax25s) 108 ax25_cb_put(ax25s); 109 return neigh->ax25 != NULL; 110 } net/rose/rose_timer.c (free site) 164 static void rose_timer_expiry(struct timer_list *t) 165 { 166 struct rose_sock *rose = timer_container_of(rose, t, timer); 167 struct sock *sk = &rose->sock; ... 174 switch (rose->state) { ... 182 case ROSE_STATE_2: /* T3 */ ->183 rose_neigh_put(rose->neighbour); // <- drops refcount to 0; frees neigh->digipeat (ax25_digi) // and neigh itself; t0timer still pending 184 rose_disconnect(sk, ETIMEDOUT, -1, -1); 185 break; ... 197 } include/net/rose.h (rose_neigh_put, inline free function) 160 static inline void rose_neigh_put(struct rose_neigh *rose_neigh) 161 { 162 if (refcount_dec_and_test(&rose_neigh->use)) { 163 if (rose_neigh->ax25) 164 ax25_cb_put(rose_neigh->ax25); ->165 kfree(rose_neigh->digipeat); // <- frees the ax25_digi (66 bytes); t0timer not cancelled 166 kfree(rose_neigh); 167 } 168 } net/rose/rose_route.c (allocation site) 84 if (rose_neigh == NULL) { 85 rose_neigh = kmalloc_obj(*rose_neigh, GFP_ATOMIC); ... 100 refcount_set(&rose_neigh->use, 1); ... 107 if (rose_route->ndigis != 0) { 108 rose_neigh->digipeat = ->109 kmalloc_obj(ax25_digi, GFP_ATOMIC); // <- allocates the 66-byte ax25_digi later freed and read ... 124 } 125 } Tentative Analysis The crash is a KASAN slab-use-after-free: rose_t0timer_expiry() reads the freed rose_neigh->digipeat (an ax25_digi struct, 66 bytes) via ax25_send_frame() -> kmemdup(). Commit d860d1faa6b2 ("net: rose: convert 'use' field to refcount_t") changed rose_timer_expiry() from merely decrementing the plain 'use' counter to calling rose_neigh_put(), which now frees rose_neigh (and its digipeat) when the refcount hits zero. The new rose_neigh_put() omits timer cancellation, so after it returns the t0timer embedded in the (now-freed) rose_neigh can still fire in the same TIMER_SOFTIRQ batch. The race on a single-CPU machine (the syzbot scenario) is purely sequential: rose_timer_expiry() fires first, frees the neigh; then rose_t0timer_expiry() fires next in the same run_timer_base batch with a dangling neigh pointer, passes neigh->digipeat to ax25_send_frame, and kmemdup triggers the KASAN report. Before d860d1faa6b2, rose_timer_expiry() never freed the neigh; the free was always performed by rose_remove_neigh() which called timer_delete_sync() on both timers before freeing. The refcount conversion introduced a new free path that missed this cancellation. Potential Solution Add timer_delete() calls for both ftimer and t0timer inside rose_neigh_put() before the kfree calls, mirroring what rose_remove_neigh() already does via timer_delete_sync(). The non-synchronous variant is required because rose_neigh_put() may be called from softirq context. 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); kfree(rose_neigh); } } More information Oops-Analysis: http://oops.fenrus.org/reports/lkml/69ef2847.170a0220.11de9.001a.GAE_google.com/ Assisted-by: Copilot:claude-sonnet-4.6 linux-kernel-oops-x86.