Report from LKML message: 69ef2847.170a0220.11de9.001a.GAE@google.com
Root cause:
rose_neigh_put(introduced by d860d1faa6b2) can freerose_neighand its embeddeddigipeatpointer from softirq context without cancellingt0timer, leaving a pending timer callback that reads the freed object.
| 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 | |
| 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 |
| Module | Flags | Backtrace | Location | Flag Implication |
|---|---|---|---|---|
| (module list not available in this report) |
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 inax25_send_framewhile executing in the timer softirq (IRQ context, stack 1).
| 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 |
||||
0xffffffff8aa0c9e3 (0xffffffff8aa0c350 +
0x693) |
ax25_send_frame ← crash
site |
0x693 |
0x9f0 |
IRQ | net/ax25/ax25_out.c:78 |
|
rose_send_frame (inlined into
rose_t0timer_expiry) |
IRQ | net/rose/rose_link.c:106 |
||||
rose_transmit_restart_request (inlined into
rose_t0timer_expiry) |
IRQ | net/rose/rose_link.c:198 |
||||
0xffffffff8a9f7005 (0xffffffff8a9f6db0 +
0x255) |
rose_t0timer_expiry |
0x255 |
0x560 |
IRQ | net/rose/rose_link.c:83 |
|
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 |
| 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 |
|
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 |
| 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 |
||||
0xffffffff8aa03d73 (0xffffffff8aa038b0 +
0x4c3) |
rose_timer_expiry ← free
site |
0x4c3 |
0x600 |
IRQ | net/rose/rose_timer.c:183 |
|
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 |
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 <c3> 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 |
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 <ax25_send_frame+0x6cd>
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 <kmemdup_noprof> ; reads 66 bytes from freed object
ffffffff8aa0c9e3: mov %rax,%r15 <<< crash (return address; UAF read is in kmemdup_noprof)
ax25_send_frame — crash site
(net/ax25/ax25_out.c:78)net/ax25/ax25_out.c
at commit e728258debd5
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 }rose_t0timer_expiry —
net/rose/rose_link.c:83net/rose/rose_link.c
at commit e728258debd5
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):
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 }rose_timer_expiry — free site
(net/rose/rose_timer.c:183)net/rose/rose_timer.c
at commit e728258debd5
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
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 }rose_add_node — alloc site
(net/rose/rose_route.c:109)net/rose/rose_route.c
at commit e728258debd5
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 }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.
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):
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.)
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.
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 |
net: rose: convert ‘use’ field to refcount_t (2025-08-23) |
| PATCH_BASE | d860d1faa6b2 |
minimal context for applying 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.
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 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.
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.