> **Root cause**: `mac80211_hwsim_link_info_changed` calls `do_div(tsf, bcn_int)` with `bcn_int = 0` when `ieee80211_offchannel_return` re-enables beaconing on an interface whose beacon interval was never set (or was set to zero by syzbot), causing an x86 #DE divide error at `mac80211_hwsim.c:2734`. # Linux kernel crash report Crash report from syzbot (Google Compute Engine). MSGID: [69efb8dd.050a0220.18b4f.0006.GAE@google.com](https://lore.kernel.org/r/69efb8dd.050a0220.18b4f.0006.GAE@google.com) ## Key elements | Field | Value | Implication | | ----- | ----- | ----------- | | MSGID | `<69efb8dd.050a0220.18b4f.0006.GAE@google.com>` | | | MSGID_URL | [69efb8dd.050a0220.18b4f.0006.GAE@google.com](https://lore.kernel.org/r/69efb8dd.050a0220.18b4f.0006.GAE@google.com) | | | UNAME | `syzkaller` | | | CRASH_TYPE | `divide error: 0000 [#1] SMP KASAN PTI` | x86 #DE exception; integer divide-by-zero | | PROCESS | `kworker/u8:6` | | | PID | 193 | | | CPU | 1 | | | UID | 0 | | | HARDWARE | `Google Google Compute Engine` | | | BIOS | `Google 04/18/2026` | | | TAINT | L — softlockup | Indicates a system stall, thermal issues, or malicious software. | | SOURCEDIR | `oops-workdir/linux` | | | VMLINUX | `oops-workdir/syzbot/vmlinux-254f4963` | | | HEAD_COMMIT | [254f49634ee1](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=254f49634ee1) (Linux 7.1-rc1) | | | WORKQUEUE | `events_unbound cfg80211_wiphy_work` | Crash occurred inside a workqueue context | | INTRODUCED-BY | [c51f878379b1](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c51f878379b1) ("mac80211_hwsim: fix beacon timing", Thomas Pedersen, 2013-01-02) | Introduced `do_div(tsf, bcn_int)` without a zero guard | | PATCH_BASE | `v7.1-rc1` (254f49634ee1) | | ## Kernel modules | Module | Flags | Backtrace | Location | Flag Implication | | ------ | ----- | --------- | -------- | ---------------- | | *(module list not available in this report)* | | | | | ## Backtrace | Address | Function | Offset | Size | Context | Module | Source location | | ------- | -------- | ------ | ---- | ------- | ------ | --------------- | | `0xffffffff870a6c76 (0xffffffff870a6800 + 0x476)` | `mac80211_hwsim_link_info_changed` | `0x476` | `0xfc0` | Task | *(built-in)* | [mac80211_hwsim.c:2734](#1-mac80211_hwsim_link_info_changed--crash-site-mac80211_hwsimc2734) | | `0xffffffff8b126765 (0xffffffff8b126260 + 0x505)` | `drv_link_info_changed` | `0x505` | `0x880` | Task | *(built-in)* | [driver-ops.c:497](#2-drv_link_info_changed--driver-opsc497) | | `0xffffffff8b163635 (0xffffffff8b1632c0 + 0x375)` | `ieee80211_offchannel_return` | `0x375` | `0x500` | Task | *(built-in)* | [offchannel.c:160](#3-ieee80211_offchannel_return--offchannelc160) | | `0xffffffff8b15db6a (0xffffffff8b15d410 + 0x75a)` | `__ieee80211_scan_completed` | `0x75a` | `0xb40` | Task | *(built-in)* | [scan.c:519](#4-__ieee80211_scan_completed--scanc519) | | `0xffffffff8af01cbf (0xffffffff8af019f0 + 0x2cf)` | `cfg80211_wiphy_work` | `0x2cf` | `0x460` | Task | *(built-in)* | [core.c:513](#5-cfg80211_wiphy_work--corec513) | | | `process_one_work (inlined)` | | | Task | *(built-in)* | [workqueue.c:3302](#6-process_one_work-inlined--workqueuec3302) | | `0xffffffff818e85ad (0xffffffff818e7a50 + 0xb5d)` | `process_scheduled_works` | `0xb5d` | `0x1860` | Task | *(built-in)* | [workqueue.c:3385](#7-process_scheduled_works--workqueuec3385) | | `0xffffffff818f0613 (0xffffffff818efbc0 + 0xa53)` | `worker_thread` | `0xa53` | `0xfc0` | Task | *(built-in)* | [workqueue.c:3466](#8-worker_thread--workqueuec3466) | | `0xffffffff81908118 (0xffffffff81907d90 + 0x388)` | `kthread` | `0x388` | `0x470` | Task | *(built-in)* | [kthread.c:436](#9-kthread--kthreadc436) | | `0xffffffff816bcae4 (0xffffffff816bc5d0 + 0x514)` | `ret_from_fork` | `0x514` | `0xb70` | Task | *(built-in)* | [process.c:158](#10-ret_from_fork--processc158) | | `0xffffffff813340aa (0xffffffff81334090 + 0x1a)` | `ret_from_fork_asm` | `0x1a` | `0x30` | Task | *(built-in)* | [entry_64.S:245](#11-ret_from_fork_asm--entry_64s245) | ## CPU Registers ``` RIP: 0010:mac80211_hwsim_link_info_changed+0x476/0xfc0 [drivers/net/wireless/virtual/mac80211_hwsim.c:2734] RSP: 0000:ffffc90002ea7860 EFLAGS: 00010246 RAX: 000650757bb12572 RBX: 0000000000000200 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff88806fbe7b98 RBP: ffffc90002ea7958 R08: ffffffff903096f7 R09: 1ffffffff20612de R10: dffffc0000000000 R11: fffffbfff20612df R12: 000650757bb12572 R13: ffff88806fbe7b18 R14: dffffc0000000000 R15: 1ffff920005d4f18 FS: 0000000000000000(0000) GS:ffff888125393000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000f540c000 CR3: 0000000064b3a000 CR4: 00000000003526f0 ``` **Register annotations:** | Register | Value | Annotation | |----------|-------|------------| | RAX | `0x000650757bb12572` | TSF value (u64 dividend for `do_div`); same as R12 | | RBX | `0x0000000000000200` | `BSS_CHANGED_BEACON_ENABLED` = `1<<9` = `0x200` | | RCX | `0x0000000000000000` | Result of `mov (%rdi),%rcx` — `link_data->beacon_int` loaded as zero | | RDX | `0x0000000000000000` | Cleared by `xor %edx,%edx` before `div` instruction | | RSI | `0x0000000000000000` | Divisor = `bcn_int`; zero → causes #DE | | RDI | `0xffff88806fbe7b98` | Address of `link_data->beacon_int` field (passed via `0x20(%rsp)`) | | R12 | `0x000650757bb12572` | TSF value (u64); high 32 bits = `0x00065075` ≠ 0 → long-division path taken | | R13 | `0xffff88806fbe7b18` | Likely `link_data` base pointer | ## Backtrace source code ### 1. `mac80211_hwsim_link_info_changed` — crash site (`mac80211_hwsim.c:2734`) [`drivers/net/wireless/virtual/mac80211_hwsim.c:2701`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/wireless/virtual/mac80211_hwsim.c?h=v7.1-rc1#n2701) ```c 2701 static void mac80211_hwsim_link_info_changed(struct ieee80211_hw *hw, 2702 struct ieee80211_vif *vif, 2703 struct ieee80211_bss_conf *info, 2704 u64 changed) 2705 { 2706 struct hwsim_vif_priv *vp = (void *)vif->drv_priv; 2707 struct mac80211_hwsim_data *data = hw->priv; 2708 unsigned int link_id = info->link_id; 2709 struct mac80211_hwsim_link_data *link_data = &data->link_data[link_id]; ... 2722 if (changed & BSS_CHANGED_BEACON_ENABLED) { 2723 wiphy_dbg(hw->wiphy, " BCN EN: %d (BI=%u)\n", 2724 info->enable_beacon, info->beacon_int); 2725 vp->bcn_en = info->enable_beacon; 2726 if (data->started && 2727 !hrtimer_active(&link_data->beacon_timer) && 2728 info->enable_beacon) { 2729 u64 tsf, until_tbtt; 2730 u32 bcn_int; 2731 link_data->beacon_int = info->beacon_int * 1024; 2732 tsf = mac80211_hwsim_get_tsf(hw, vif); 2733 bcn_int = link_data->beacon_int; → 2734 until_tbtt = bcn_int - do_div(tsf, bcn_int); // ← bcn_int = 0; div %rsi crashes here 2735 2736 hrtimer_start(&link_data->beacon_timer, 2737 ns_to_ktime(until_tbtt * NSEC_PER_USEC), 2738 HRTIMER_MODE_REL_SOFT); 2739 } else if (!info->enable_beacon) { ... 2779 } ``` **`mac80211_hwsim_link_data` struct** (`mac80211_hwsim.c:660`): ```c 660 struct mac80211_hwsim_link_data { 661 u32 link_id; 662 u64 beacon_int /* beacon interval in us */; 663 struct hrtimer beacon_timer; 664 }; ``` **Disassembly window around crash (from `backtrace_resolve.py`):** ``` ffffffff870a6c3c: add 0x0(%r13),%r12 ffffffff870a6c40: movabs $0xdffffc0000000000,%rax ffffffff870a6c4a: cmpb $0x0,(%r14,%rax,1) ffffffff870a6c4f: mov %rax,%r14 ffffffff870a6c52: mov 0x20(%rsp),%rdi ← load ptr to link_data->beacon_int ffffffff870a6c57: je ffffffff870a6c63 ← skip KASAN report if shadow ok ffffffff870a6c59: call ffffffff8230dea0 <__asan_report_load8_noabort> ffffffff870a6c5e: mov 0x20(%rsp),%rdi ffffffff870a6c63: mov (%rdi),%rcx ← rcx = link_data->beacon_int (= 0) ffffffff870a6c66: mov %ecx,%esi ← esi = low 32 bits = 0 ffffffff870a6c68: mov %r12,%rax ← rax = tsf ffffffff870a6c6b: shr $0x20,%rax ← check high 32 bits of tsf ffffffff870a6c6f: je ffffffff870a6cd1 ← tsf fits in 32 bits? use fast path ffffffff870a6c71: mov %r12,%rax ← rax = tsf (full 64-bit) ffffffff870a6c74: xor %edx,%edx ← rdx = 0 ffffffff870a6c76: div %rsi <<< crash — RSI = 0, divide error ffffffff870a6c79: jmp ffffffff870a6cd8 ``` **git blame for lines 2731–2734:** | Line | Hash | Author | Subject | |------|------|--------|---------| | 2731 | `e57f8a489c29` | Shaul Triebitz (2022-06-06) | `wifi: mac80211_hwsim: send a beacon per link` | | 2732 | `c51f878379b1` | Thomas Pedersen (2013-01-02) | `mac80211_hwsim: fix beacon timing` | | 2733 | `e57f8a489c29` | Shaul Triebitz (2022-06-06) | `wifi: mac80211_hwsim: send a beacon per link` | | 2734 | `c51f878379b1` | Thomas Pedersen (2013-01-02) | `mac80211_hwsim: fix beacon timing` | ### 2. `drv_link_info_changed` — `driver-ops.c:497` [`net/mac80211/driver-ops.c:460`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/mac80211/driver-ops.c?h=v7.1-rc1#n460) ```c 460 void drv_link_info_changed(struct ieee80211_local *local, 461 struct ieee80211_sub_if_data *sdata, 462 struct ieee80211_bss_conf *info, 463 int link_id, u64 changed) 464 { ... 493 trace_drv_link_info_changed(local, sdata, info, changed); 494 if (local->ops->link_info_changed) 495 local->ops->link_info_changed(&local->hw, &sdata->vif, 496 info, changed); → 497 else if (local->ops->bss_info_changed) 498 local->ops->bss_info_changed(&local->hw, &sdata->vif, 499 info, changed); 500 trace_drv_return_void(local); 501 } ``` ### 3. `ieee80211_offchannel_return` — `offchannel.c:160` [`net/mac80211/offchannel.c:133`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/mac80211/offchannel.c?h=v7.1-rc1#n133) ```c 133 void ieee80211_offchannel_return(struct ieee80211_local *local) 134 { 135 struct ieee80211_sub_if_data *sdata; 136 137 lockdep_assert_wiphy(local->hw.wiphy); ... 157 if (test_and_clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED, 158 &sdata->state)) { 159 sdata->vif.bss_conf.enable_beacon = true; → 160 ieee80211_link_info_change_notify( 161 sdata, &sdata->deflink, 162 BSS_CHANGED_BEACON_ENABLED); 163 } ... 169 } ``` ### 4. `__ieee80211_scan_completed` — `scan.c:519` [`net/mac80211/scan.c:519`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/mac80211/scan.c?h=v7.1-rc1#n519) ```c 516 if (!hw_scan && was_scanning) { 517 ieee80211_configure_filter(local); 518 drv_sw_scan_complete(local, scan_sdata); → 519 ieee80211_offchannel_return(local); 520 } ``` ### 5. `cfg80211_wiphy_work` — `core.c:513` [`net/wireless/core.c:513`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/wireless/core.c?h=v7.1-rc1#n513) *(source at line 513 calls the scan completion work handler; this is the workqueue dispatch point)* ### 6. `process_one_work` (inlined) — `workqueue.c:3302` Inlined into `process_scheduled_works`. Source location: `kernel/workqueue.c:3302`. ### 7. `process_scheduled_works` — `workqueue.c:3385` Standard workqueue processing loop. Source: `kernel/workqueue.c:3385`. ### 8. `worker_thread` — `workqueue.c:3466` Standard workqueue worker thread. Source: `kernel/workqueue.c:3466`. ### 9. `kthread` — `kthread.c:436` Standard kernel thread entry. Source: `kernel/kthread.c:436`. ### 10. `ret_from_fork` — `process.c:158` Standard fork return path. Source: `arch/x86/kernel/process.c:158`. ### 11. `ret_from_fork_asm` — `entry_64.S:245` Standard assembly fork return. Source: `arch/x86/entry/entry_64.S:245`. --- ## Analysis ### What An x86 #DE (integer divide-by-zero) exception fires at `mac80211_hwsim.c:2734` inside `mac80211_hwsim_link_info_changed`. The crashing expression is: ```c until_tbtt = bcn_int - do_div(tsf, bcn_int); ``` `bcn_int` is a `u32` loaded from `link_data->beacon_int` (line 2733), which was just set at line 2731 to `info->beacon_int * 1024`. `info->beacon_int` is the 802.11 beacon interval (in time units) from `ieee80211_bss_conf`. In the crashing execution `info->beacon_int = 0`, so `link_data->beacon_int = 0` and `bcn_int = 0`. The disassembly confirms: `mov (%rdi),%rcx` loads `link_data->beacon_int` into RCX = 0; `mov %ecx,%esi` truncates to RSI = 0 (the `do_div` divisor); `div %rsi` with RSI = 0 triggers the #DE. This is complete fact — the register dump shows RCX = RSI = 0 and RAX = R12 = the TSF value `0x000650757bb12572`. [`drivers/net/wireless/virtual/mac80211_hwsim.c:2701`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/wireless/virtual/mac80211_hwsim.c?h=v7.1-rc1#n2701) ```c 2701 static void mac80211_hwsim_link_info_changed(struct ieee80211_hw *hw, 2702 struct ieee80211_vif *vif, 2703 struct ieee80211_bss_conf *info, 2704 u64 changed) 2705 { 2706 struct hwsim_vif_priv *vp = (void *)vif->drv_priv; 2707 struct mac80211_hwsim_data *data = hw->priv; 2708 unsigned int link_id = info->link_id; 2709 struct mac80211_hwsim_link_data *link_data = &data->link_data[link_id]; ... 2722 if (changed & BSS_CHANGED_BEACON_ENABLED) { 2723 wiphy_dbg(hw->wiphy, " BCN EN: %d (BI=%u)\n", 2724 info->enable_beacon, info->beacon_int); 2725 vp->bcn_en = info->enable_beacon; 2726 if (data->started && 2727 !hrtimer_active(&link_data->beacon_timer) && 2728 info->enable_beacon) { 2729 u64 tsf, until_tbtt; 2730 u32 bcn_int; 2731 link_data->beacon_int = info->beacon_int * 1024; 2732 tsf = mac80211_hwsim_get_tsf(hw, vif); 2733 bcn_int = link_data->beacon_int; → 2734 until_tbtt = bcn_int - do_div(tsf, bcn_int); // ← bcn_int = 0; RSI = 0 → #DE 2735 2736 hrtimer_start(&link_data->beacon_timer, 2737 ns_to_ktime(until_tbtt * NSEC_PER_USEC), 2738 HRTIMER_MODE_REL_SOFT); 2739 } else if (!info->enable_beacon) { ... 2748 link_data->beacon_int = 0; ... 2750 } 2751 } ``` ### How **Q1: How did `bcn_int` become zero?** A1: At line 2731, `link_data->beacon_int` is set to `info->beacon_int * 1024`, converting the 802.11 beacon interval from TUs (time units) to microseconds. `info` is `struct ieee80211_bss_conf *`, and `info->beacon_int` (type `u16`) carries whatever value was in `bss_conf.beacon_int` at notification time. When this field is zero, the multiplication produces zero, `link_data->beacon_int` becomes zero, the local `bcn_int` is loaded from this zero value at line 2733, and `do_div(tsf, bcn_int)` faults. **Q2: How did `info->beacon_int` end up as zero at the time of the call?** A2: The notification with `BSS_CHANGED_BEACON_ENABLED` was sent by `ieee80211_offchannel_return` (offchannel.c:155–158) when a software scan completed. That function sets `sdata->vif.bss_conf.enable_beacon = true` and calls `ieee80211_link_info_change_notify(sdata, &sdata->deflink, BSS_CHANGED_BEACON_ENABLED)`. It does **not** update `bss_conf.beacon_int` before sending the notification. If `bss_conf.beacon_int` was never set (syzbot can create AP interfaces with `beacon_int = 0` through fuzzing), the notification arrives with `info->beacon_int = 0`. The call chain (confirmed from backtrace): ``` cfg80211_wiphy_work (workqueue) → __ieee80211_scan_completed [scan.c:519] → ieee80211_offchannel_return [offchannel.c:160] → ieee80211_link_info_change_notify(BSS_CHANGED_BEACON_ENABLED) → drv_link_info_changed [driver-ops.c:497] → mac80211_hwsim_link_info_changed [mac80211_hwsim.c:2734] ← crash ``` The guard at lines 2726–2728 checks `data->started`, `!hrtimer_active`, and `info->enable_beacon` — all true — but does **not** check for `info->beacon_int == 0`. **Comparison with existing guard:** An identical `do_div(tsf, bcn_int)` expression appears in `mac80211_hwsim_config` (mac80211_hwsim.c:2635), protected by: ```c 2630 if (!data->started || !link_data->beacon_int) { 2631 hrtimer_cancel(&link_data->beacon_timer); 2632 } else if (!hrtimer_active(&link_data->beacon_timer)) { 2633 u64 tsf = mac80211_hwsim_get_tsf(hw, NULL); 2634 u32 bcn_int = link_data->beacon_int; 2635 u64 until_tbtt = bcn_int - do_div(tsf, bcn_int); ``` The equivalent guard (`!link_data->beacon_int` → cancel/skip) is absent from the `BSS_CHANGED_BEACON_ENABLED` path in `mac80211_hwsim_link_info_changed`. ### Where The fix belongs in `mac80211_hwsim_link_info_changed` in `drivers/net/wireless/virtual/mac80211_hwsim.c`, immediately after `bcn_int = link_data->beacon_int` (line 2733) and before the `do_div`. A zero `beacon_int` is invalid for beacon timer scheduling. When `info->beacon_int == 0`, setting `link_data->beacon_int = 0` (line 2731) is consistent with the cleanup path at line 2748 — both leave the field at zero to signal "no active beacon". The timer must not be started with a zero period; returning early is correct. **Proposed fix:** ```diff --- a/drivers/net/wireless/virtual/mac80211_hwsim.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim.c @@ -2730,7 +2730,9 @@ static void mac80211_hwsim_link_info_changed(struct ieee80211_hw *hw, u32 bcn_int; link_data->beacon_int = info->beacon_int * 1024; tsf = mac80211_hwsim_get_tsf(hw, vif); bcn_int = link_data->beacon_int; +if (!bcn_int) +return; until_tbtt = bcn_int - do_div(tsf, bcn_int); hrtimer_start(&link_data->beacon_timer, ``` The patch is saved as `report.patch` for the patch agent. **`PATCH_BASE: v7.1-rc1` (commit 254f49634ee1)** **Bug introduction:** Commit `c51f878379b1` ("mac80211_hwsim: fix beacon timing", Thomas Pedersen, 2013-01-02) introduced `do_div(tsf, bcn_int)` without a zero guard. Commit `e57f8a489c29` ("wifi: mac80211_hwsim: send a beacon per link", Shaul Triebitz, 2022-06-06) refactored the beacon setup to use per-link data, setting `link_data->beacon_int = info->beacon_int * 1024` — this is what allows `info->beacon_int = 0` from the notification to reach the division. No upstream fix was found. **Security assessment:** This is a locally-triggerable kernel crash (divide by zero) from userspace via a wireless subsystem syscall sequence. No memory corruption; no privilege escalation vector. Impact: local denial of service (kernel crash). CVSS 5.5 (local, low privilege, no interaction, high availability impact). Not suitable for CVE assignment as it requires root or CAP_NET_ADMIN to create the AP interface. ### Patch - **Status**: Patch generation succeeded. - **Base commit**: `v7.1-rc1` (254f49634ee1) — exact match (PATCH_BASE tag). - **Validation**: `git apply --check` passed cleanly against `v7.1-rc1`. - **Output files**: - `patch-email.txt` — LKML-ready patch email - `git-send-email.sh` — send script (`--to linux-wireless@vger.kernel.org`, `--in-reply-to <69efb8dd.050a0220.18b4f.0006.GAE@google.com>`) ## Fact Check All checked items verified. ## Patch Review **Verdict: PASS** All checklist items clear. The two-line guard (`if (!bcn_int) return;`) directly fixes the divide-by-zero by mirroring the identical pattern already present in `mac80211_hwsim_config()` (line 2630). No allocations, no locks acquired, and no caller-contract changes are introduced. The early `return` skips only the remaining `if (changed & ...)` blocks (lines 2753–2778), which are all pure `wiphy_dbg()` debug-log statements with no state side-effects, so skipping them is functionally harmless. The `Fixes:` tag, `Reported-by:`, and `Link:` trailers are correctly attributed.