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

Key elements

Field Value Implication
MSGID <69efb8dd.050a0220.18b4f.0006.GAE@google.com>
MSGID_URL 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 (Linux 7.1-rc1)
WORKQUEUE events_unbound cfg80211_wiphy_work Crash occurred inside a workqueue context
INTRODUCED-BY 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
0xffffffff8b126765 (0xffffffff8b126260 + 0x505) drv_link_info_changed 0x505 0x880 Task (built-in) driver-ops.c:497
0xffffffff8b163635 (0xffffffff8b1632c0 + 0x375) ieee80211_offchannel_return 0x375 0x500 Task (built-in) offchannel.c:160
0xffffffff8b15db6a (0xffffffff8b15d410 + 0x75a) __ieee80211_scan_completed 0x75a 0xb40 Task (built-in) scan.c:519
0xffffffff8af01cbf (0xffffffff8af019f0 + 0x2cf) cfg80211_wiphy_work 0x2cf 0x460 Task (built-in) core.c:513
process_one_work (inlined) Task (built-in) workqueue.c:3302
0xffffffff818e85ad (0xffffffff818e7a50 + 0xb5d) process_scheduled_works 0xb5d 0x1860 Task (built-in) workqueue.c:3385
0xffffffff818f0613 (0xffffffff818efbc0 + 0xa53) worker_thread 0xa53 0xfc0 Task (built-in) workqueue.c:3466
0xffffffff81908118 (0xffffffff81907d90 + 0x388) kthread 0x388 0x470 Task (built-in) kthread.c:436
0xffffffff816bcae4 (0xffffffff816bc5d0 + 0x514) ret_from_fork 0x514 0xb70 Task (built-in) process.c:158
0xffffffff813340aa (0xffffffff81334090 + 0x1a) ret_from_fork_asm 0x1a 0x30 Task (built-in) entry_64.S:245

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),%rcxlink_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

drivers/net/wireless/virtual/mac80211_hwsim.c:2701

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):

 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

net/mac80211/driver-ops.c:460

 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_returnoffchannel.c:160

net/mac80211/offchannel.c:133

 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_completedscan.c:519

net/mac80211/scan.c:519

 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_workcore.c:513

net/wireless/core.c:513

(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_worksworkqueue.c:3385

Standard workqueue processing loop. Source: kernel/workqueue.c:3385.

8. worker_threadworkqueue.c:3466

Standard workqueue worker thread. Source: kernel/workqueue.c:3466.

9. kthreadkthread.c:436

Standard kernel thread entry. Source: kernel/kthread.c:436.

10. ret_from_forkprocess.c:158

Standard fork return path. Source: arch/x86/kernel/process.c:158.

11. ret_from_fork_asmentry_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:

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

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:

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:

--- 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

Fact Check

All checked items verified.