# Collected Data — KASAN slab-use-after-free in ax25_send_frame Report directory: `reports/lkml/69ef2847.170a0220.11de9.001a.GAE_google.com/` --- ## Debug symbols - **vmlinux path**: `oops-workdir/syzbot/vmlinux-e728258d` (absolute: `/sdb1/arjan/git/oops-skill/oops-workdir/syzbot/vmlinux-e728258d`) - **vmlinux size**: 1,782,098,520 bytes - **Download**: obtained by fetcher agent from syzbot URL (`https://storage.googleapis.com/syzbot-assets/78131d1b0e14/vmlinux-e728258d.xz`), decompressed with `unxz` - **Download status**: success (confirmed by prefetch.md) - **nm symbol count**: 402,014 text symbols loaded ## Source tree - **Path**: `oops-workdir/linux` (absolute: `/sdb1/arjan/git/oops-skill/oops-workdir/linux`) - **HEAD commit**: `e728258debd5` — `Merge tag 'net-7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net` - **Checkout**: pre-existing; checked out to `e728258debd5` by fetcher agent - **Semcode index**: refreshed by fetcher agent ## Resolved addresses — complete table All entries processed by `backtrace_resolve.py`. Script ran in parallel mode. | Index | Function | Offset | Address | function_type | Source (primary) | Inlined frames | Note | |-------|----------|--------|---------|---------------|-----------------|----------------|------| | 0 | `ax25_send_frame` | `0x693` | `0xffffffff8aa0c9e3` | normal | `include/linux/fortify-string.h:763` | `ax25_send_frame @ net/ax25/ax25_out.c:78` | inlined kmemdup_noprof; real site is `ax25_out.c:78` | | 1 | `kasan_check_range` | `0x264` | `0xffffffff8230f694` | normal | line 0 returned | — | addr2line incomplete at this address; use source_hint `mm/kasan/generic.c:200` | | 2 | `__asan_memcpy` | `0x29` | `0xffffffff82310ff9` | normal | `mm/kasan/shadow.c:105` | — | | | 3 | `kmemdup_noprof` | `0x55` | `0xffffffff820f41c5` | normal | line 0 returned | — | addr2line incomplete; use source_hint `mm/util.c:140` | | 4 | `rose_t0timer_expiry` | `0x255` | `0xffffffff8a9f7005` | normal | line 0 returned | — | addr2line -i at offset−1 = `0xffffffff8a9f7004` → `rose_send_frame @ rose_link.c:106`, `rose_transmit_restart_request @ rose_link.c:198`, `rose_t0timer_expiry @ rose_link.c:83` | | 5 | `call_timer_fn` | `0x192` | `0xffffffff81b24b22` | normal | `kernel/time/timer.c:1748` | — | | | 6 | `__run_timer_base` | `0x652` | `0xffffffff81b207a2` | normal | `kernel/time/timer.c:1799` | — | | | 7 | `run_timer_softirq` | `0xb7` | `0xffffffff81b224a7` | normal | `kernel/time/timer.c:2395` | — | | | 8 | `handle_softirqs` | `0x22a` | `0xffffffff8187fdba` | normal | line 0 returned | — | manual addr2line at offset−1 → `kernel/softirq.c:622` | | 9 | `__irq_exit_rcu` | `0xca` | `0xffffffff818806ba` | normal | `kernel/softirq.c:656` | — | | | 10 | `irq_exit_rcu` | `0x9` | `0xffffffff818805b9` | normal | `kernel/softirq.c:752` | — | | | 11 | `sysvec_apic_timer_interrupt` | `0xa6` | `0xffffffff8bb8dc66` | normal | line 0 returned | — | source_hint `arch/x86/kernel/apic/apic.c:1061` | | 12 | `asm_sysvec_apic_timer_interrupt` | `0x1a` | `0xffffffff810014ca` | normal | `arch/x86/include/asm/idtentry.h:697` | — | script note: function boundary heuristic misfired | | 13 | `pv_native_safe_halt` | `0xf` | `0xffffffff8bb8e12f` | normal | `arch/x86/include/asm/irqflags.h:48` | — | idle task RIP | | 14 | `default_idle` | `0x9` | `0xffffffff8bb90bc9` | normal | `arch/x86/kernel/process.c:766` | — | | | 15 | `default_idle_call` | `0x72` | `0xffffffff8bb90e12` | normal | `kernel/sched/idle.c:122` | — | | | 16 | `do_idle` | `0x36a` | `0xffffffff819aae9a` | normal | `kernel/sched/idle.c:199` | — | | | 17 | `cpu_startup_entry` | `0x43` | `0xffffffff819ab173` | normal | `kernel/sched/idle.c:451` | — | | | 18 | `rest_init` | `0x2de` | `0xffffffff8bb91cde` | normal | `init/main.c:762` | — | | | 19 | `start_kernel` | `0x38a` | `0xffffffff91a10aca` | normal | `init/main.c:1220` | — | script note: heuristic misfired >150 lines | | 20 | `x86_64_start_reservations` | `0x24` | `0xffffffff91a30484` | normal | `arch/x86/kernel/head64.c:310` | — | | | 21 | `x86_64_start_kernel` | `0x143` | `0xffffffff91a302f3` | normal | `arch/x86/kernel/head64.c:291` | — | | | 22 | `common_startup_64` | `0x13e` | `0xffffffff81688b77` | normal | `arch/x86/kernel/head_64.S:418` | — | | | 23 | `__kmalloc_cache_noprof` | `0x31c` | `0xffffffff8225a38c` | normal | `include/linux/kasan.h:263` | — | | | 24 | `rose_add_node` | `0x471` | `0xffffffff8a9fbdb1` | normal | `include/linux/slab.h:950` | `rose_add_node @ net/rose/rose_route.c:109` | real alloc site is `rose_route.c:109` | | 25 | `rose_rt_ioctl` | `0xd35` | `0xffffffff8a9fb3c5` | normal | `net/rose/rose_route.c:748` | — | | | 26 | `rose_ioctl` | `0x3fb` | `0xffffffff8a9efb4b` | normal | `net/rose/af_rose.c:1387` | — | | | 27 | `sock_do_ioctl` | `0x101` | `0xffffffff897a71a1` | normal | line 0 returned | — | source_hint `net/socket.c:1313` | | 28 | `sock_ioctl` | `0x5c6` | `0xffffffff897a5b06` | normal | `net/socket.c:1434` | — | | | 29 | `__se_sys_ioctl` | `0xfc` | `0xffffffff824860cc` | normal | `fs/ioctl.c:51` | — | | | 30 | `do_syscall_64` | `0x15f` | `0xffffffff8bb861df` | normal | `arch/x86/entry/syscall_64.c:63` | — | | | 31 | `entry_SYSCALL_64_after_hwframe` | `0x77` | `0xffffffff81000130` | normal | `arch/x86/entry/entry_64.S:121` | — | | | 32 | `kfree` | `0x1c5` | `0xffffffff82255675` | normal | `include/linux/kasan.h:235` | `slab_free_hook @ mm/slub.c:2689`, `slab_free @ mm/slub.c:6246`, `kfree @ mm/slub.c:6561` | | | 33 | `rose_timer_expiry` | `0x4c3` | `0xffffffff8aa03d73` | normal | line 0 returned | — | manual addr2line at offset−1 → `rose_neigh_put @ include/net/rose.h:165`, `rose_timer_expiry @ net/rose/rose_timer.c:183` | **Note on `function_type`:** The backtrace_resolve.py script classified `kasan_check_range`, `__asan_memcpy`, and `kmemdup_noprof` as `normal` (the script did not detect these as assert functions in this run). Per `backtrace.md`, these are "assert / canary functions" — they should be treated as assert-type: source shown but not used as crash site or blame target. ## Code bytes — crash site (`pv_native_safe_halt`, idle context RIP) From the oops header (RIP = `pv_native_safe_halt+0xf`): ``` 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 90 90 90 90 90 f3 0f 1e fa 66 90 0f 00 2d 13 81 10 00 fb f4 <<< RIP → c3 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 90 90 90 90 90 ``` `c3` = `ret` — the instruction at RIP is a `ret`. The preceding bytes `fb f4` are `sti; hlt` (safe_halt body). This is the normal idle path, not the crash site. ## Disassembly — actual UAF site (`ax25_send_frame+0x693`) From `backtrace_resolve.py` disasm output (index 0): ``` 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> ; copy callsign bytes ffffffff8aa0c9bb: lea 0x17(%r13),%rdi ffffffff8aa0c9bf: mov $0x7,%edx ffffffff8aa0c9c4: mov %r12,%rsi ffffffff8aa0c9c7: call ffffffff82310fd0 <__asan_memcpy> ; copy more callsign bytes ffffffff8aa0c9cc: test %r14,%r14 ffffffff8aa0c9cf: je ffffffff8aa0ca1d ffffffff8aa0c9d1: mov $0x42,%esi ; 0x42 = 66 = sizeof(ax25_digi) ffffffff8aa0c9d6: mov %r14,%rdi ; rdi = digi = neigh->digipeat ffffffff8aa0c9d9: mov $0x820,%edx ; GFP_ATOMIC ffffffff8aa0c9de: call ffffffff820f4170 ; <<< UAF read of 66 bytes from freed ax25_digi ffffffff8aa0c9e3: mov %rax,%r15 <<< crash (return address; KASAN fires inside kmemdup_noprof) ``` The `call kmemdup_noprof` at `0xffffffff8aa0c9de` is the instruction that triggers the UAF. The address recorded in the backtrace (`ax25_send_frame+0x693` = `0xffffffff8aa0c9e3`) is the **return address** (after the call). The actual read from the freed object is inside `kmemdup_noprof` → `__asan_memcpy` → `check_region_inline` → `kasan_check_range`. ## Register annotations All registers from the idle task interrupt context — **not** the UAF context. | Register | Value | Annotation | |----------|-------|-----------| | RIP | `0xffffffff8bb8e12f` | `pv_native_safe_halt+0xf` — idle task executing `sti; hlt; ret` | | RSP | `0xffffffff8e607dc0` | CPU 0 idle kernel stack | | EFLAGS | `0x00000242` | IF=1 (interrupts enabled), ZF=1, PF=1 | | RAX | `0x0000000000110dc7` | last return value in idle context (no UAF relevance) | | RBX | `0xffffffff819aae9a` | = `do_idle+0x36a` (return address for resume from halt) | | RCX | `0x0000000080000001` | — | | RDX | `0x0000000000000001` | — | | RSI | `0xffffffff8dfd8f45` | — | | RDI | `0xffffffff8c289f60` | — | | RBP | `0xffffffff8e607eb0` | idle stack frame pointer | | R08 | `0xffff8880b86339db` | — | | R09 | `0x1ffff110170c673b` | KASAN shadow region pointer | | R10 | `0xdffffc0000000000` | KASAN shadow offset constant | | R11 | `0xffffed10170c673c` | — | | R12 | `0x0000000000000000` | — | | R13 | `0x1ffffffff1cd25d8` | — | | R14 | `0x0000000000000000` | — | | R15 | `0x1ffffffff1cd25d8` | — | ## Full source excerpts ### `ax25_send_frame` — full function (`net/ax25/ax25_out.c:32–116`) ```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; 36 37 /* 38 * Take the default packet length for the device if zero is 39 * specified. 40 */ 41 if (paclen == 0) { 42 rcu_read_lock(); 43 ax25_dev = ax25_dev_ax25dev(dev); 44 if (!ax25_dev) { 45 rcu_read_unlock(); 46 return NULL; 47 } 48 paclen = ax25_dev->values[AX25_VALUES_PACLEN]; 49 rcu_read_unlock(); 50 } 51 52 /* 53 * Look for an existing connection. 54 */ 55 if ((ax25 = ax25_find_cb(src, dest, digi, dev)) != NULL) { 56 ax25_output(ax25, paclen, skb); 57 return ax25; /* It already existed */ 58 } 59 60 rcu_read_lock(); 61 ax25_dev = ax25_dev_ax25dev(dev); 62 if (!ax25_dev) { 63 rcu_read_unlock(); 64 return NULL; 65 } 66 67 if ((ax25 = ax25_create_cb()) == NULL) { 68 rcu_read_unlock(); 69 return NULL; 70 } 71 ax25_fillin_cb(ax25, ax25_dev); 72 rcu_read_unlock(); 73 74 ax25->source_addr = *src; 75 ax25->dest_addr = *dest; 76 77 if (digi != NULL) { → 78 ax25->digipeat = kmemdup(digi, sizeof(*digi), GFP_ATOMIC); // ← UAF: digi = freed ax25_digi (neigh->digipeat) 79 if (ax25->digipeat == NULL) { 80 ax25_cb_put(ax25); 81 return NULL; 82 } 83 } 84 85 switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) { 86 case AX25_PROTO_STD_SIMPLEX: 87 case AX25_PROTO_STD_DUPLEX: 88 ax25_std_establish_data_link(ax25); 89 break; 90 91 #ifdef CONFIG_AX25_DAMA_SLAVE 92 case AX25_PROTO_DAMA_SLAVE: 93 if (ax25_dev->dama.slave) 94 ax25_ds_establish_data_link(ax25); 95 else 96 ax25_std_establish_data_link(ax25); 97 break; 98 #endif 99 } 100 101 /* 102 * There is one ref for the state machine; a caller needs 103 * one more to put it back, just like with the existing one. 104 */ 105 ax25_cb_hold(ax25); 106 107 ax25_cb_add(ax25); 108 109 ax25->state = AX25_STATE_1; 110 111 ax25_start_heartbeat(ax25); 112 113 ax25_output(ax25, paclen, skb); 114 115 return ax25; /* We had to create it */ 116 } ``` ### `rose_neigh_put` — full function (`include/net/rose.h:160–168`) ```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) 166 kfree(rose_neigh); 167 } 168 } ``` ### `rose_add_node` allocation path (`net/rose/rose_route.c:84–128`) ```c 84 if (rose_neigh == NULL) { 85 rose_neigh = kmalloc_obj(*rose_neigh, GFP_ATOMIC); 86 if (rose_neigh == NULL) { 87 res = -ENOMEM; 88 goto out; 89 } 90 91 rose_neigh->callsign = rose_route->neighbour; 92 rose_neigh->digipeat = NULL; ... 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 ax25_digi (66 bytes); this is the freed object 110 if (rose_neigh->digipeat == NULL) { 111 kfree(rose_neigh); 112 res = -ENOMEM; 113 goto out; 114 } ... 124 } 125 } ``` ## Factual notes 1. **Freed object is `ax25_digi` (digipeat), not `rose_neigh` itself.** The KASAN allocation trace points to `rose_route.c:109` which is `kmalloc_obj(ax25_digi, GFP_ATOMIC)` — this allocates `rose_neigh->digipeat`. The free is at `include/net/rose.h:165` which is `kfree(rose_neigh->digipeat)` inside `rose_neigh_put`. Size 66 = `sizeof(ax25_digi)` = 8×`sizeof(ax25_address)` + 8 + 1 + 1 = 56 + 10 = 66. 2. **Both crash and free paths are in softirq (timer) context.** `rose_t0timer_expiry` (crash path) and `rose_timer_expiry` (free path) are both timer callbacks. Both execute in softirq context under the same APIC timer. `rose_t0timer_expiry` is attached to `rose_neigh->t0timer`; `rose_timer_expiry` is attached to `rose_sock->timer`. 3. **`rose_send_frame` passes `neigh->digipeat` to `ax25_send_frame` without holding any lock or reference on `neigh->digipeat`.** The call is at `net/rose/rose_link.c:106`: `ax25_send_frame(skb, 260, rose_call, &neigh->callsign, neigh->digipeat, neigh->dev)`. 4. **`rose_timer_expiry` frees `neigh` (and its `digipeat`) when `rose->state == ROSE_STATE_2`.** The condition is checked at `rose_timer.c:182–185`. `rose_neigh_put` decrements refcount and frees when it reaches zero. 5. **`rose_t0timer_expiry` accesses `neigh` unconditionally.** At `rose_link.c:81`: `struct rose_neigh *neigh = timer_container_of(neigh, t, t0timer);` — the `neigh` pointer is derived from the timer embedded in the `rose_neigh` struct. After the struct is freed, this pointer becomes dangling. 6. **Prior fix attempts for rose UAF exist in the tree:** - `9cc02ede6962` — "net: rose: fix UAF bugs caused by timer handler" (Duoming Zhou, 2022-06-29): fixed UAF in `rose_heartbeat_expiry`, `rose_timer_expiry`, `rose_idletimer_expiry` using refcount on the socket. - `5de7665e0a07` — "net: rose: fix timer races against user threads" (Eric Dumazet, 2025-01-22): added `sock_owned_by_user` check to rose timers (similar to the sock lock pattern). - `d860d1faa6b2` — "net: rose: convert 'use' field to refcount_t" (Takamitsu Iwai, 2025-08-23): introduced refcount-based `rose_neigh_put` in `rose_timer_expiry` at line 183. This commit itself adds the `rose_neigh_put(rose->neighbour)` call that frees the object. Git blame for `rose_timer.c:183` is this commit. 7. **`rose_t0timer_expiry` does not check if `neigh` is still valid (or hold a reference) before accessing `neigh->digipeat`.** `rose_t0timer_expiry` is in `rose_link.c`; `rose_timer_expiry` is in `rose_timer.c`. These operate on different timer instances (`t0timer` vs `timer`) but may reference the same `rose_neigh` via `rose_sock->neighbour`. 8. **`sizeof(ax25_digi)` = 66**: `ax25_digi` is `typedef struct { ax25_address calls[8]; unsigned char repeated[8]; unsigned char ndigi; signed char lastrepeat; }`. Each `ax25_address` = 7 bytes. Total: 8×7 + 8 + 1 + 1 = 56 + 10 = 66 bytes. Confirmed by KASAN "Read of size 66". 9. **`rose_send_frame` at `rose_link.c:106` also calls `ax25_send_frame` with `neigh->callsign` (a struct by reference) and `neigh->dev`.** These are also members of the `rose_neigh` struct. If `neigh` itself were freed (via `kfree(rose_neigh)` at `rose.h:166`), all these would also be UAF accesses. 10. **`rose_add_node` (allocation path, task 10474) is called from `rose_rt_ioctl` → `rose_ioctl` → userspace `ioctl` syscall.** This means the `rose_neigh` and its `digipeat` are created in response to a userspace ioctl (e.g., `SIOCADDRT` or similar rose routing ioctl). The syzbot reproducer likely sets up the route before triggering the timer race. ## Git blame for key lines | File | Line | Blame commit | Author | Date | Subject | |------|------|-------------|--------|------|---------| | `net/ax25/ax25_out.c:78` | 78 | `0459d70add3f` | Arnaldo Carvalho de Melo | 2006-11-17 | *(early kernel history)* | | `net/rose/rose_timer.c:183` | 183 | `d860d1faa6b2` | Takamitsu Iwai | 2025-08-23 | `net: rose: convert 'use' field to refcount_t` | | `net/rose/rose_route.c:109` | 109 | `69050f8d6d07` | Kees Cook | 2026-02-20 | `treewide: Replace kmalloc with kmalloc_obj for non-scalar types` | ## Recent commits to `net/rose/rose_timer.c` | Commit | Author | Subject | |--------|--------|---------| | `d860d1faa6b2` | Takamitsu Iwai | `net: rose: convert 'use' field to refcount_t` | | `41cb08555c41` | (treewide) | `treewide, timers: Rename from_timer() to timer_container_of()` | | `5de7665e0a07` | Eric Dumazet | `net: rose: fix timer races against user threads` | | `9cc02ede6962` | Duoming Zhou | `net: rose: fix UAF bugs caused by timer handler` |