Synopsis: urtw(4) panics with fatal page fault on "ifconfig urtw0 up" (riscv64)
Category: kernel
Environment:
        System      : OpenBSD 7.9
        Details     : OpenBSD 7.9 (GENERIC.MP) #5: Wed May  6 13:53:42 MDT 2026
        Architecture: OpenBSD.riscv64
        Machine     : riscv64 (StarFive VisionFive 2)

Description:

        Bringing up a urtw(4) interface (Realtek RTL8187, rev 0x04, RFv2)
        with no prior configuration (no nwid/wpakey set) panics the kernel
        with a fatal page fault, triggered by the very first INIT -> SCAN
        802.11 state transition.

        Root cause:

        ic->ic_bss->ni_chan is initialized to IEEE80211_CHAN_ANYC by
        ieee80211_node_attach() (sys/net80211/ieee80211_node.c). This macro
        is literally defined as ((struct ieee80211_channel *) NULL)
        (sys/net80211/ieee80211_var.h).

        urtw_newstate() (sys/dev/usb/if_urtw.c) only records the requested
        state/arg and defers to urtw_task() via usb_add_task(); it never
        assigns a real channel to ic->ic_bss->ni_chan before the generic
        state machine runs. urtw_task() itself handles the NULL/ANYC value
        gracefully in its own urtw_set_chan() call (ieee80211_chan2ieee()
        returns IEEE80211_CHAN_ANY for a NULL/ANYC channel, and
        urtw_set_chan() returns early on that), but it then calls
        sc->sc_newstate() -- the original generic ieee80211_newstate() --
        which does not tolerate this.

        Inside ieee80211_newstate()'s IEEE80211_S_SCAN case,
        ieee80211_node_abg_mode(ic, ni) is called with ni->ni_chan still
        NULL. That function evaluates IEEE80211_IS_CHAN_5GHZ(ni->ni_chan),
        which expands to ((ni->ni_chan)->ic_flags & IEEE80211_CHAN_5GHZ),
        dereferencing ic_flags -- the *second* field (u_int16_t) of
        struct ieee80211_channel -- with no NULL/ANYC check. This is
        exactly consistent with the crash data below: stval == 0x2 is
        precisely the byte offset of ic_flags within the struct when read
        through a NULL base pointer.

        Other drivers apparently avoid this by assigning a concrete
        channel (typically ic->ic_ibss_chan, already computed by
        ieee80211_setmode() during ieee80211_ifattach()) to
        ic->ic_bss->ni_chan before or during their newstate handling.
        urtw(4) never does this.

How-To-Repeat:

        On riscv64 with a urtw(4)-attached RTL8187 adapter and no prior
        /etc/hostname.urtw0 configuration:

                # ifconfig urtw0 up

        This immediately panics.

        Crash data (ddb):

                sepc   == 0xffffffc00053365e
                stval  == 0x0000000000000002
                scause == 0x000000000000000d   (load page fault)
                panic: Fatal page fault at 0xffffffc00053365e: 0x000002

                panic() at panic+0xfc
                do_trap_supervisor() at do_trap_supervisor+0x1f4
                cpu_exception_handler_supervisor() at 
cpu_exception_handler_supervisor+0x7a
                ieee80211_newstate() at ieee80211_newstate+0x2e6
                urtw_task() at urtw_task+0x216
                usb_task_thread() at usb_task_thread+0xe0
                proc_trampoline() at proc_trampoline+0xc

Fix:

        Give ic->ic_bss->ni_chan a real channel in urtw_newstate() before
        deferring to the generic state machine, mirroring what other
        drivers do. Tested on the hardware above: urtw0 now brings up and
        scans without panicking.

Index: sys/dev/usb/if_urtw.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/if_urtw.c,v
retrieving revision 1.74
diff -u -p -r1.74 if_urtw.c
--- sys/dev/usb/if_urtw.c
+++ sys/dev/usb/if_urtw.c
@@ -1018,6 +1018,16 @@ urtw_newstate(struct ieee80211com *ic, e
        usb_rem_task(sc->sc_udev, &sc->sc_task);
        timeout_del(&sc->scan_to);

+       /*
+        * ic->ic_bss->ni_chan starts out as IEEE80211_CHAN_ANYC (NULL).
+        * Give it a real channel before the generic 802.11 state machine,
+        * invoked below via sc->sc_newstate() in urtw_task(), dereferences
+        * ni_chan on the first INIT -> SCAN transition and panics.
+        */
+       if (nstate == IEEE80211_S_SCAN &&
+           ic->ic_bss->ni_chan == IEEE80211_CHAN_ANYC)
+               ic->ic_bss->ni_chan = ic->ic_ibss_chan;
+
        /* do it in a process context */
        sc->sc_state = nstate;
        sc->sc_arg = arg;

Reply via email to