Re: [syzbot] [hams?] KASAN: slab-use-after-free Read in ax25_send_frame (3)

Arjan van de Ven <[email protected]> Mon, 27 Apr 2026 08:25:26 -0700
Newsgroups gmane.linux.hams,gmane.linux.network,gmane.linux.kernel
Message-ID <[email protected]>
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.