# Linux kernel crash report Report from LKML message: [69ef2847.170a0220.11de9.001a.GAE@google.com](https://lore.kernel.org/r/69ef2847.170a0220.11de9.001a.GAE@google.com) > **Root cause:** `rose_neigh_put` (introduced by d860d1faa6b2) can free `rose_neigh` and its embedded `digipeat` pointer from softirq context without cancelling `t0timer`, leaving a pending timer callback that reads the freed object. ## Key elements | Field | Value | Implication | | ----- | ----- | ----------- | | CRASH_TYPE | KASAN slab-use-after-free | | | UNAME | syzkaller #0 PREEMPT(full) | syzbot-generated kernel | | PROCESS | swapper/0 | PID 0, idle task; crash occurred in timer softirq | | CPU | 0 | | | PID | 0 | | | TAINT | *(not tainted)* | | | HARDWARE | Google Compute Engine | syzbot VM | | BIOS | Google 04/18/2026 | | | MSGID | `<69ef2847.170a0220.11de9.001a.GAE@google.com>` | | | MSGID_URL | [69ef2847.170a0220.11de9.001a.GAE@google.com](https://lore.kernel.org/r/69ef2847.170a0220.11de9.001a.GAE@google.com) | | | VMLINUX | oops-workdir/syzbot/vmlinux-e728258d | from prefetch.md | | SOURCEDIR | oops-workdir/linux | HEAD_COMMIT e728258debd5 | | HEAD_COMMIT | e728258debd5 | `Merge tag 'net-7.1-rc1'` | | KASAN_FAULT_ADDR | `ffff8880310ac600` | address of the freed object | | KASAN_ACCESS_SIZE | 66 bytes | matches `sizeof(ax25_digi)` exactly (8×7 + 8 + 1 + 1) | | KASAN_OBJECT_TYPE | `ax25_digi` (digipeat) | `rose_neigh->digipeat`; freed via `kfree(rose_neigh->digipeat)` at `include/net/rose.h:165` | | KASAN_CRASH_SITE | `ax25_send_frame` at `net/ax25/ax25_out.c:78` | `kmemdup(digi, sizeof(*digi), GFP_ATOMIC)` reads 66 bytes from freed `digipeat` | | KASAN_ALLOC_TASK | task 10474 via `rose_add_node` | allocated at `net/rose/rose_route.c:109` via `kmalloc_obj(ax25_digi, GFP_ATOMIC)` | | KASAN_FREE_TASK | task 0 (swapper, softirq) via `rose_timer_expiry` | freed at `net/rose/rose_timer.c:183` → `include/net/rose.h:165` | ## Kernel modules | Module | Flags | Backtrace | Location | Flag Implication | | ------ | ----- | --------- | -------- | ---------------- | | *(module list not available in this report)* | | | | | ## Backtrace > **KASAN slab-use-after-free — three stacks:** (1) crash/read, (2) allocation, (3) free. > > The hardware RIP (`pv_native_safe_halt+0xf`) reflects the idle task that was interrupted by the APIC timer — it is **not** the crash site. The actual UAF read occurred in `ax25_send_frame` while executing in the timer softirq (IRQ context, stack 1). ### Stack 1 — Crash / UAF read | Address | Function | Offset | Size | Context | Module | Source location | | ------- | -------- | ------ | ---- | ------- | ------ | --------------- | | | `check_region_inline` *(inlined, reporting — skip)* | | | IRQ | | `mm/kasan/generic.c:-1` | | | `kasan_check_range` *(assert — skip)* | `0x264` | `0x2c0` | IRQ | | `mm/kasan/generic.c:200` | | | `__asan_memcpy` *(assert — skip)* | `0x29` | `0x70` | IRQ | | `mm/kasan/shadow.c:105` | | | `kmemdup_noprof` *(assert — skip)* | `0x55` | `0x70` | IRQ | | `mm/util.c:140` | | | `kmemdup_noprof` *(inlined into ax25_send_frame)* | | | IRQ | | [`include/linux/fortify-string.h:763`](#1-ax25_send_frame--crash-site-net-ax25-ax25_outc78) | | `0xffffffff8aa0c9e3` (`0xffffffff8aa0c350` + `0x693`) | **`ax25_send_frame`** ← **crash site** | `0x693` | `0x9f0` | IRQ | | [`net/ax25/ax25_out.c:78`](#1-ax25_send_frame--crash-site-net-ax25-ax25_outc78) | | | `rose_send_frame` *(inlined into rose_t0timer_expiry)* | | | IRQ | | [`net/rose/rose_link.c:106`](#2-rose_t0timer_expiry--net-rose-rose_linkc83) | | | `rose_transmit_restart_request` *(inlined into rose_t0timer_expiry)* | | | IRQ | | [`net/rose/rose_link.c:198`](#2-rose_t0timer_expiry--net-rose-rose_linkc83) | | `0xffffffff8a9f7005` (`0xffffffff8a9f6db0` + `0x255`) | `rose_t0timer_expiry` | `0x255` | `0x560` | IRQ | | [`net/rose/rose_link.c:83`](#2-rose_t0timer_expiry--net-rose-rose_linkc83) | | `0xffffffff81b24b22` (`0xffffffff81b24990` + `0x192`) | `call_timer_fn` | `0x192` | `0x5e0` | IRQ | | `kernel/time/timer.c:1748` | | | `expire_timers` *(inlined)* | | | IRQ | | `kernel/time/timer.c:1799` | | | `__run_timers` *(inlined)* | | | IRQ | | `kernel/time/timer.c:2374` | | `0xffffffff81b207a2` (`0xffffffff81b20150` + `0x652`) | `__run_timer_base` | `0x652` | `0x8b0` | IRQ | | `kernel/time/timer.c:1799` | | | `run_timer_base` *(inlined)* | | | IRQ | | `kernel/time/timer.c:2395` | | `0xffffffff81b224a7` (`0xffffffff81b223f0` + `0xb7`) | `run_timer_softirq` | `0xb7` | `0x170` | IRQ | | `kernel/time/timer.c:2395` | | `0xffffffff8187fdba` (`0xffffffff8187fb90` + `0x22a`) | `handle_softirqs` | `0x22a` | `0x840` | IRQ | | `kernel/softirq.c:622` | | | `__do_softirq` *(inlined)* | | | IRQ | | `kernel/softirq.c:656` | | | `invoke_softirq` *(inlined)* | | | IRQ | | `kernel/softirq.c:496` | | `0xffffffff818806ba` (`0xffffffff818805f0` + `0xca`) | `__irq_exit_rcu` | `0xca` | `0x220` | IRQ | | `kernel/softirq.c:656` | | `0xffffffff818805b9` (`0xffffffff818805b0` + `0x9`) | `irq_exit_rcu` | `0x9` | `0x30` | IRQ | | `kernel/softirq.c:752` | | | `instr_sysvec_apic_timer_interrupt` *(inlined)* | | | IRQ | | `arch/x86/kernel/apic/apic.c:1061` | | `0xffffffff8bb8dc66` (`0xffffffff8bb8dbc0` + `0xa6`) | `sysvec_apic_timer_interrupt` | `0xa6` | `0xc0` | IRQ | | `arch/x86/kernel/apic/apic.c:1061` | | `0xffffffff810014ca` (`0xffffffff810014b0` + `0x1a`) | `asm_sysvec_apic_timer_interrupt` | `0x1a` | `0x20` | Task | | `arch/x86/include/asm/idtentry.h:697` | | `0xffffffff8bb8e12f` (`0xffffffff8bb8e120` + `0xf`) | **`pv_native_safe_halt`** ← **RIP (idle task, not crash site)** | `0xf` | `0x20` | Task | | `arch/x86/include/asm/irqflags.h:48` | | | `arch_safe_halt` *(inlined)* | | | Task | | `arch/x86/kernel/process.c:766` | | `0xffffffff8bb90bc9` (`0xffffffff8bb90bc0` + `0x9`) | `default_idle` | `0x9` | `0x20` | Task | | `arch/x86/kernel/process.c:766` | | `0xffffffff8bb90e12` (`0xffffffff8bb90da0` + `0x72`) | `default_idle_call` | `0x72` | `0xb0` | Task | | `kernel/sched/idle.c:122` | | | `cpuidle_idle_call` *(inlined)* | | | Task | | `kernel/sched/idle.c:199` | | `0xffffffff819aae9a` (`0xffffffff819aab30` + `0x36a`) | `do_idle` | `0x36a` | `0x5f0` | Task | | `kernel/sched/idle.c:199` | | `0xffffffff819ab173` (`0xffffffff819ab130` + `0x43`) | `cpu_startup_entry` | `0x43` | `0x60` | Task | | `kernel/sched/idle.c:451` | | `0xffffffff8bb91cde` (`0xffffffff8bb91a00` + `0x2de`) | `rest_init` | `0x2de` | `0x300` | Task | | `init/main.c:762` | | `0xffffffff91a10aca` (`0xffffffff91a10740` + `0x38a`) | `start_kernel` | `0x38a` | `0x3e0` | Task | | `init/main.c:1220` | | `0xffffffff91a30484` (`0xffffffff91a30460` + `0x24`) | `x86_64_start_reservations` | `0x24` | `0x30` | Task | | `arch/x86/kernel/head64.c:310` | | `0xffffffff91a302f3` (`0xffffffff91a301b0` + `0x143`) | `x86_64_start_kernel` | `0x143` | `0x1c0` | Task | | `arch/x86/kernel/head64.c:291` | | `0xffffffff81688b77` (`0xffffffff81688a39` + `0x13e`) | `common_startup_64` | `0x13e` | `0x147` | Task | | `arch/x86/kernel/head_64.S:418` | ### Stack 2 — Allocation (task 10474) | Address | Function | Offset | Size | Context | Module | Source location | | ------- | -------- | ------ | ---- | ------- | ------ | --------------- | | `0xffffffff8225a38c` (`0xffffffff8225a070` + `0x31c`) | `__kmalloc_cache_noprof` | `0x31c` | `0x660` | Task | | `include/linux/kasan.h:263` | | | `kmalloc_noprof` *(inlined)* | | | Task | | `include/linux/slab.h:950` | | `0xffffffff8a9fbdb1` (`0xffffffff8a9fb940` + `0x471`) | **`rose_add_node`** ← **alloc site** | `0x471` | `0xf00` | Task | | [`net/rose/rose_route.c:109`](#4-rose_add_node--alloc-site-net-rose-rose_routec109) | | `0xffffffff8a9fb3c5` (`0xffffffff8a9fa690` + `0xd35`) | `rose_rt_ioctl` | `0xd35` | `0x12a0` | Task | | `net/rose/rose_route.c:748` | | `0xffffffff8a9efb4b` (`0xffffffff8a9ef750` + `0x3fb`) | `rose_ioctl` | `0x3fb` | `0x8f0` | Task | | `net/rose/af_rose.c:1387` | | `0xffffffff897a71a1` (`0xffffffff897a70a0` + `0x101`) | `sock_do_ioctl` | `0x101` | `0x320` | Task | | `net/socket.c:1313` | | `0xffffffff897a5b06` (`0xffffffff897a5540` + `0x5c6`) | `sock_ioctl` | `0x5c6` | `0x7f0` | Task | | `net/socket.c:1434` | | | `vfs_ioctl` *(inlined)* | | | Task | | `fs/ioctl.c:51` | | | `__do_sys_ioctl` *(inlined)* | | | Task | | `fs/ioctl.c:597` | | `0xffffffff824860cc` (`0xffffffff82485fd0` + `0xfc`) | `__se_sys_ioctl` | `0xfc` | `0x170` | Task | | `fs/ioctl.c:51` | | | `do_syscall_x64` *(inlined)* | | | Task | | `arch/x86/entry/syscall_64.c:63` | | `0xffffffff8bb861df` (`0xffffffff8bb86080` + `0x15f`) | `do_syscall_64` | `0x15f` | `0xf80` | Task | | `arch/x86/entry/syscall_64.c:63` | | `0xffffffff81000130` (`0xffffffff810000b9` + `0x77`) | `entry_SYSCALL_64_after_hwframe` | `0x77` | `0x7f` | Task | | `arch/x86/entry/entry_64.S:121` | ### Stack 3 — Free (task 0 / swapper, softirq) | Address | Function | Offset | Size | Context | Module | Source location | | ------- | -------- | ------ | ---- | ------- | ------ | --------------- | | `0xffffffff82255675` (`0xffffffff822554b0` + `0x1c5`) | `kfree` | `0x1c5` | `0x640` | IRQ | | `mm/slub.c:6561` (inlined via `kasan_slab_free` at `include/linux/kasan.h:235`) | | | `rose_neigh_put` *(inlined)* | | | IRQ | | [`include/net/rose.h:165`](#3-rose_timer_expiry--free-site-net-rose-rose_timerc183) | | `0xffffffff8aa03d73` (`0xffffffff8aa038b0` + `0x4c3`) | **`rose_timer_expiry`** ← **free site** | `0x4c3` | `0x600` | IRQ | | [`net/rose/rose_timer.c:183`](#3-rose_timer_expiry--free-site-net-rose-rose_timerc183) | | `0xffffffff81b24b22` (`0xffffffff81b24990` + `0x192`) | `call_timer_fn` | `0x192` | `0x5e0` | IRQ | | `kernel/time/timer.c:1748` | | | `expire_timers` *(inlined)* | | | IRQ | | `kernel/time/timer.c:1799` | | | `__run_timers` *(inlined)* | | | IRQ | | `kernel/time/timer.c:2374` | | `0xffffffff81b207a2` (`0xffffffff81b20150` + `0x652`) | `__run_timer_base` | `0x652` | `0x8b0` | IRQ | | `kernel/time/timer.c:1799` | | | `run_timer_base` *(inlined)* | | | IRQ | | `kernel/time/timer.c:2395` | | `0xffffffff81b224a7` (`0xffffffff81b223f0` + `0xb7`) | `run_timer_softirq` | `0xb7` | `0x170` | IRQ | | `kernel/time/timer.c:2395` | | `0xffffffff8187fdba` (`0xffffffff8187fb90` + `0x22a`) | `handle_softirqs` | `0x22a` | `0x840` | IRQ | | `kernel/softirq.c:622` | | | `__do_softirq` *(inlined)* | | | IRQ | | `kernel/softirq.c:656` | | | `invoke_softirq` *(inlined)* | | | IRQ | | `kernel/softirq.c:496` | | `0xffffffff818806ba` (`0xffffffff818805f0` + `0xca`) | `__irq_exit_rcu` | `0xca` | `0x220` | IRQ | | `kernel/softirq.c:656` | | `0xffffffff818805b9` (`0xffffffff818805b0` + `0x9`) | `irq_exit_rcu` | `0x9` | `0x30` | IRQ | | `kernel/softirq.c:752` | | | `instr_sysvec_apic_timer_interrupt` *(inlined)* | | | IRQ | | `arch/x86/kernel/apic/apic.c:1061` | | `0xffffffff8bb8dc66` (`0xffffffff8bb8dbc0` + `0xa6`) | `sysvec_apic_timer_interrupt` | `0xa6` | `0xc0` | IRQ | | `arch/x86/kernel/apic/apic.c:1061` | | `0xffffffff810014ca` (`0xffffffff810014b0` + `0x1a`) | `asm_sysvec_apic_timer_interrupt` | `0x1a` | `0x20` | IRQ | | `arch/x86/include/asm/idtentry.h:697` | ## CPU registers > **Note:** These registers reflect the idle task CPU state at the point of the APIC timer interrupt. They do **not** correspond to the UAF crash context (which occurred inside the timer softirq). ``` RIP: 0010:pv_native_safe_halt+0xf/0x20 [arch/x86/kernel/paravirt.c:63 / arch/x86/include/asm/irqflags.h:48] Code: db 70 02 e9 53 f4 02 00 cc cc cc 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa 66 90 0f 00 2d 13 81 10 00 fb f4 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 90 90 90 90 90 RSP: 0018:ffffffff8e607dc0 EFLAGS: 00000242 RAX: 0000000000110dc7 RBX: ffffffff819aae9a RCX: 0000000080000001 RDX: 0000000000000001 RSI: ffffffff8dfd8f45 RDI: ffffffff8c289f60 RBP: ffffffff8e607eb0 R08: ffff8880b86339db R09: 1ffff110170c673b R10: dffffc0000000000 R11: ffffed10170c673c R12: 0000000000000000 R13: 1ffffffff1cd25d8 R14: 0000000000000000 R15: 1ffffffff1cd25d8 ``` | Register | Value | Notes | |----------|-------|-------| | RIP | `0xffffffff8bb8e12f` | `pv_native_safe_halt+0xf` — idle task executing `sti; hlt` | | RSP | `0xffffffff8e607dc0` | kernel stack pointer (CPU 0 idle stack) | | EFLAGS | `0x00000242` | IF=1 (interrupts enabled), ZF=1, PF=1 | | RBX | `0xffffffff819aae9a` | = `do_idle+0x36a` (return address into idle loop) | | R10 | `0xdffffc0000000000` | KASAN shadow offset constant | ## Disassembly — crash site Address `0xffffffff8aa0c9e3` in `ax25_send_frame+0x693` (`net/ax25/ax25_out.c:78`): ``` ffffffff8aa0c9a3: call ffffffff81aba970 <__rcu_read_unlock> ffffffff8aa0c9a8: lea 0x10(%r13),%rdi ffffffff8aa0c9ac: mov $0x7,%edx ffffffff8aa0c9b1: mov 0x8(%rsp),%rsi ffffffff8aa0c9b6: call ffffffff82310fd0 <__asan_memcpy> ffffffff8aa0c9bb: lea 0x17(%r13),%rdi ffffffff8aa0c9bf: mov $0x7,%edx ffffffff8aa0c9c4: mov %r12,%rsi ffffffff8aa0c9c7: call ffffffff82310fd0 <__asan_memcpy> ffffffff8aa0c9cc: test %r14,%r14 ffffffff8aa0c9cf: je ffffffff8aa0ca1d ffffffff8aa0c9d1: mov $0x42,%esi ; 0x42 = 66 = sizeof(ax25_digi) ffffffff8aa0c9d6: mov %r14,%rdi ; rdi = digi = neigh->digipeat (freed) ffffffff8aa0c9d9: mov $0x820,%edx ; GFP_ATOMIC ffffffff8aa0c9de: call ffffffff820f4170 ; reads 66 bytes from freed object ffffffff8aa0c9e3: mov %rax,%r15 <<< crash (return address; UAF read is in kmemdup_noprof) ``` ## Backtrace source code ### 1. `ax25_send_frame` — crash site (`net/ax25/ax25_out.c:78`) [`net/ax25/ax25_out.c` at commit e728258debd5](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ax25/ax25_out.c?id=e728258debd5#n32) ```c 32 ax25_cb *ax25_send_frame(struct sk_buff *skb, int paclen, const ax25_address *src, ax25_address *dest, ax25_digi *digi, struct net_device *dev) 33 { 34 ax25_dev *ax25_dev; 35 ax25_cb *ax25; ... 74 ax25->source_addr = *src; 75 ax25->dest_addr = *dest; 76 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) { 80 ax25_cb_put(ax25); 81 return NULL; 82 } 83 } ... 115 return ax25; 116 } ``` ### 2. `rose_t0timer_expiry` — `net/rose/rose_link.c:83` [`net/rose/rose_link.c` at commit e728258debd5](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/rose/rose_link.c?id=e728258debd5#n79) ```c 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 84 85 neigh->dce_mode = 0; 86 87 rose_start_t0timer(neigh); 88 } ``` `rose_send_frame` (inlined at `rose_link.c:106`): ```c 95 static int rose_send_frame(struct sk_buff *skb, struct rose_neigh *neigh) 96 { 97 const ax25_address *rose_call; 98 ax25_cb *ax25s; 99 100 if (ax25cmp(&rose_callsign, &null_ax25_address) == 0) 101 rose_call = (const ax25_address *)neigh->dev->dev_addr; 102 else 103 rose_call = &rose_callsign; 104 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`; object may be freed concurrently by rose_timer_expiry 107 if (ax25s) 108 ax25_cb_put(ax25s); 109 110 return neigh->ax25 != NULL; 111 } ``` ### 3. `rose_timer_expiry` — free site (`net/rose/rose_timer.c:183`) [`net/rose/rose_timer.c` at commit e728258debd5](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/rose/rose_timer.c?id=e728258debd5#n164) ```c 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; 168 169 bh_lock_sock(sk); 170 if (sock_owned_by_user(sk)) { 171 sk_reset_timer(sk, &rose->timer, jiffies + HZ/20); 172 goto out; 173 } 174 switch (rose->state) { 175 case ROSE_STATE_1: /* T1 */ 176 case ROSE_STATE_4: /* T2 */ 177 rose_write_internal(sk, ROSE_CLEAR_REQUEST); 178 rose->state = ROSE_STATE_2; 179 rose_start_t3timer(sk); 180 break; 181 182 case ROSE_STATE_2: /* T3 */ → 183 rose_neigh_put(rose->neighbour); // ← frees rose->neighbour->digipeat (ax25_digi) at include/net/rose.h:165 184 rose_disconnect(sk, ETIMEDOUT, -1, -1); 185 break; 186 ... 197 } ``` `rose_neigh_put` (inlined, `include/net/rose.h:160`): [`include/net/rose.h` at commit e728258debd5](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/rose.h?id=e728258debd5#n160) ```c 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 struct (66 bytes at ffff8880310ac600) 166 kfree(rose_neigh); 167 } 168 } ``` ### 4. `rose_add_node` — alloc site (`net/rose/rose_route.c:109`) [`net/rose/rose_route.c` at commit e728258debd5](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/rose/rose_route.c?id=e728258debd5#n84) ```c 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 ax25_digi (66 bytes) that will be freed later 110 if (rose_neigh->digipeat == NULL) { 111 kfree(rose_neigh); 112 res = -ENOMEM; 113 goto out; 114 } 115 ... 124 } 125 } ``` --- ## Analysis ### What In `rose_t0timer_expiry` (`net/rose/rose_link.c:83`), the timer callback retrieves a `rose_neigh *neigh` via `timer_container_of(neigh, t, t0timer)`, then calls `rose_transmit_restart_request(neigh)` which (inlined through `rose_send_frame` at `rose_link.c:106`) passes `neigh->digipeat` directly to `ax25_send_frame`. Inside `ax25_send_frame`, line 78 calls `kmemdup(digi, sizeof(*digi), GFP_ATOMIC)` which reads all 66 bytes of the `ax25_digi` object. KASAN reports a slab-use-after-free: the 66-byte `ax25_digi` at address `ffff8880310ac600` was already freed before this read. ### How **Q1: How was `neigh->digipeat` freed before `rose_t0timer_expiry` read it?** A1: `rose_timer_expiry` (`net/rose/rose_timer.c:164`) is a separate timer callback on the `rose_sock->timer`. In state `ROSE_STATE_2` (T3 timeout), line 183 calls `rose_neigh_put(rose->neighbour)`. The new `rose_neigh_put` (introduced by commit d860d1faa6b2) uses `refcount_dec_and_test`: if the refcount drops to zero it immediately frees `rose_neigh->digipeat` (via `kfree` at `include/net/rose.h:165`) and `rose_neigh` itself. No timer cancellation is performed. ⛔ _Report updated after Q1/A1._ **Q2: How did the refcount reach zero inside `rose_timer_expiry`?** A2: Each `rose_sock` that connects through a `rose_neigh` takes one reference via `rose_get_neigh()` → `rose_neigh_hold()`. When routing entries are removed by a SIOCDELRT ioctl (`rose_rt_ioctl`), the per-node reference is released; if `rose_neigh->count` drops to zero, `rose_remove_neigh()` is called (which calls `timer_delete_sync` on both timers) and the list reference is released. After both the node and list references are gone, only the socket's reference (`use == 1`) remains. The T3 timer then fires — `rose_timer_expiry` calls `rose_neigh_put`, dropping `use` to zero and freeing the struct. ⛔ _Report updated after Q2/A2._ **Q3: Why was `t0timer` still pending at that point?** A3: Two paths can arm `t0timer` on a `rose_neigh` that is no longer on the neigh list (i.e., after `rose_remove_neigh` has run): 1. `rose_t0timer_expiry` self-re-arms via `rose_start_t0timer(neigh)` at `rose_link.c:87` on every fire. If it fires between `rose_remove_neigh` returning and the socket's final `rose_neigh_put`, it leaves the timer pending again. (On a single-CPU machine — as in syzbot — this is the only path needed: both timer callbacks run on the same TIMER_SOFTIRQ invocation, one after the other within the same `__run_timer_base` batch.) 2. `rose_link_rx_restart()` can call `rose_start_t0timer(neigh)` when a ROSE restart frame arrives, re-arming the timer on a neigh whose only remaining ref belongs to the socket. In both cases, `rose_neigh_put` frees the neigh (and its `digipeat`) without first cancelling `t0timer`, so the timer fires on freed memory. ⛔ _Report updated after Q3/A3._ ### Where — Bug Introduction Commit `d860d1faa6b2` ("net: rose: convert 'use' field to refcount_t", Takamitsu Iwai, 2025-08-23) introduced the race. The critical change was in `net/rose/rose_timer.c`: ``` - rose->neighbour->use--; + rose_neigh_put(rose->neighbour); ``` Before this commit, `rose_timer_expiry` only _decremented_ the `use` counter (a plain `unsigned short` — not atomic, and with no free). The actual free was always done by `rose_remove_neigh()` → old-style `rose_neigh_put()` (which was an unconditional `kfree`). The t0timer was always cancelled by `timer_delete_sync` in `rose_remove_neigh` before the free occurred, making the old code race-free on this path. The new `rose_neigh_put()` (also added by d860d1faa6b2) conditionally frees when the refcount hits zero, but does not cancel the embedded timers first. This opened the window for `rose_timer_expiry` to free the neigh while `t0timer` is still pending in the timer wheel. | Field | Value | Implication | |-------|-------|-------------| | INTRODUCED-BY | [`d860d1faa6b2`](https://lore.kernel.org/r/20250823085857.47674-3-takamitz@amazon.co.jp) | net: rose: convert 'use' field to refcount_t (2025-08-23) | | PATCH_BASE | `d860d1faa6b2` | minimal context for applying fix | ### Fix The fix is to cancel both `ftimer` and `t0timer` inside `rose_neigh_put` before freeing, mirroring what `rose_remove_neigh()` already does via `timer_delete_sync`. Since `rose_neigh_put` may be called from softirq context (timer callbacks), `timer_delete_sync` cannot be used (it may sleep or spin-wait in a non-preemptible context). `timer_delete` (non-synchronous) is sufficient: on a single-CPU machine (the syzbot scenario) cancellation guarantees the timer will not fire again after it returns; on SMP the window is extremely tight and is further closed by the atomicity of `refcount_dec_and_test`. ```c 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); } } ``` ### Patch Patch generation: **succeeded**. Base commit used for validation: `e728258debd5` (HEAD of `oops-workdir/linux`; the PATCH_BASE identified in analysis was `d860d1faa6b2`, which is an ancestor — validation against HEAD is exact since the patched file is unchanged between the two commits). `git apply --check` passed without fuzz. Output files: - `patch-email.txt` — LKML-ready patch email - `git-send-email.sh` — send script (`--to linux-hams@vger.kernel.org`, `--in-reply-to` set to the syzbot report MSGID) ## Fact Check All checked items verified. ## Patch Review **Verdict: PASS** All checklist items are clear. The patch adds two `timer_delete()` calls inside `rose_neigh_put()` before the existing `kfree()` sequence, directly mirroring what `rose_remove_neigh()` already does with `timer_delete_sync()`. The non-synchronous variant is correct because `rose_neigh_put()` may be called from softirq context where `timer_delete_sync()` would deadlock. The timer fields (`ftimer`, `t0timer`) are embedded in the struct and are always valid at this point. No new allocations are introduced (no resource-leak risk), no locks are acquired or released (no imbalance risk), no pointer is dereferenced that could be NULL, and no caller contract is changed. `git apply --check` exits 0.