jcs's openbsd hax
openbsd

add PMF support to qwx

This device needs to handle IGTK/BIP in software. It is possible to send
protected multicast management frames with this device even while running
in station mode. Which is neat for testing but not useful in general so
the code path which makes the driver do this is unreachable without
additional hacks in net80211 that I am not going to commit (sending
protected deauth frames to the broadcast address).

Tested:
qwx QCNFA765: landry, Mark Patruck, kevlo, stsp

stsp ebdf3657 1b86c9c3

+125 -28
+12 -2
share/man/man4/qwx.4
··· 1 - .\" $OpenBSD: qwx.4,v 1.9 2025/08/04 17:57:28 schwarze Exp $ 1 + .\" $OpenBSD: qwx.4,v 1.10 2025/12/01 16:57:36 stsp Exp $ 2 2 .\" 3 3 .\" Copyright (c) 2022 Martin Pieuchot <mpi@openbsd.org> 4 4 .\" Copyright (c) 2024 Stefan Sperling <stsp@openbsd.org> ··· 15 15 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 16 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 17 .\" 18 - .Dd $Mdocdate: August 4 2025 $ 18 + .Dd $Mdocdate: December 1 2025 $ 19 19 .Dt QWX 4 20 20 .Os 21 21 .Sh NAME ··· 63 63 .Nm 64 64 driver offloads both encryption and decryption of unicast data frames to the 65 65 hardware for the CCMP cipher. 66 + .Pp 67 + The 68 + .Nm 69 + driver supports protected management frames (PMF) which prevents spoofing 70 + of management frames such as deauthentication, disassociation, and block-ack 71 + negotiation, on networks using WPA. 72 + .Pp 73 + In BSS mode the driver supports background scanning; 74 + see 75 + .Xr ifconfig 8 . 66 76 .Pp 67 77 The 68 78 .Nm
+107 -23
sys/dev/ic/qwx.c
··· 1 - /* $OpenBSD: qwx.c,v 1.95 2025/11/24 11:01:21 stsp Exp $ */ 1 + /* $OpenBSD: qwx.c,v 1.96 2025/12/01 16:57:36 stsp Exp $ */ 2 2 3 3 /* 4 4 * Copyright 2023 Stefan Sperling <stsp@openbsd.org> ··· 351 351 } 352 352 353 353 void 354 + qwx_mfp_leave_done(struct ieee80211com *ic, struct ieee80211_node *ni) 355 + { 356 + struct qwx_softc *sc = ic->ic_softc; 357 + struct ifnet *ifp = &ic->ic_if; 358 + 359 + if ((ifp->if_flags & IFF_RUNNING) && 360 + ic->ic_state == IEEE80211_S_RUN && 361 + (ni->ni_flags & IEEE80211_NODE_MFP)) { 362 + sc->deauth_sent = 1; 363 + wakeup(&sc->deauth_sent); 364 + } 365 + } 366 + 367 + void 368 + qwx_mfp_leave(struct qwx_softc *sc) 369 + { 370 + struct ieee80211com *ic = &sc->sc_ic; 371 + struct ieee80211_node *ni = (void *)ic->ic_bss; 372 + 373 + ic->ic_xflags |= IEEE80211_F_TX_MGMT_ONLY; 374 + sc->deauth_sent = 0; 375 + 376 + ni->ni_unref_cb = qwx_mfp_leave_done; 377 + ni->ni_unref_arg = NULL; 378 + ni->ni_unref_arg_size = 0; 379 + 380 + /* 381 + * Send an authenticated deauth frame in order to let our AP know we 382 + * are leaving. This allows our AP to tear down MFP state cleanly. 383 + * Otherwise we would remain locked out of this AP until a timeout 384 + * of stale MFP state occurs at the AP, which might take a while. 385 + */ 386 + if (IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH, 387 + IEEE80211_REASON_AUTH_LEAVE) != 0) { 388 + ni->ni_unref_cb = NULL; 389 + return; 390 + } 391 + 392 + if (tsleep_nsec(&sc->deauth_sent, 0, "qwxlv", MSEC_TO_NSEC(500)) != 0) 393 + ni->ni_unref_cb = NULL; 394 + } 395 + 396 + void 354 397 qwx_stop(struct ifnet *ifp) 355 398 { 356 399 struct qwx_softc *sc = ifp->if_softc; ··· 374 417 qwx_del_task(sc, systq, &sc->bgscan_task); 375 418 refcnt_finalize(&sc->task_refs, "qwxstop"); 376 419 420 + clear_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags); 421 + 422 + if (ic->ic_opmode == IEEE80211_M_STA && 423 + ic->ic_state == IEEE80211_S_RUN && 424 + (ic->ic_bss->ni_flags & IEEE80211_NODE_MFP)) 425 + qwx_mfp_leave(sc); 426 + 377 427 qwx_setkey_clear(sc); 378 428 379 429 ifp->if_timer = sc->sc_tx_timer = 0; 380 430 381 431 ifp->if_flags &= ~IFF_RUNNING; 382 432 ifq_clr_oactive(&ifp->if_snd); 383 - 384 - clear_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags); 385 433 386 434 /* 387 435 * Manually run the newstate task's code for switching to INIT state. ··· 662 710 663 711 if (test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags) || 664 712 k->k_cipher == IEEE80211_CIPHER_WEP40 || 665 - k->k_cipher == IEEE80211_CIPHER_WEP104) 713 + k->k_cipher == IEEE80211_CIPHER_WEP104 || 714 + k->k_cipher == IEEE80211_CIPHER_BIP) 666 715 return ieee80211_set_key(ic, ni, k); 667 716 668 717 return qwx_queue_setkey_cmd(ic, ni, k, QWX_ADD_KEY); ··· 676 725 677 726 if (test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags) || 678 727 k->k_cipher == IEEE80211_CIPHER_WEP40 || 679 - k->k_cipher == IEEE80211_CIPHER_WEP104) { 728 + k->k_cipher == IEEE80211_CIPHER_WEP104 || 729 + k->k_cipher == IEEE80211_CIPHER_BIP) { 680 730 ieee80211_delete_key(ic, ni, k); 681 731 return; 682 732 } ··· 13515 13565 13516 13566 wh = mtod(m, struct ieee80211_frame *); 13517 13567 ni = ieee80211_find_rxnode(ic, wh); 13518 - #if 0 13568 + 13519 13569 /* In case of PMF, FW delivers decrypted frames with Protected Bit set. 13520 13570 * Don't clear that. Also, FW delivers broadcast management frames 13521 13571 * (ex: group privacy action frames in mesh) as encrypted payload. 13522 13572 */ 13523 - if (ieee80211_has_protected(hdr->frame_control) && 13524 - !is_multicast_ether_addr(ieee80211_get_DA(hdr))) { 13525 - status->flag |= RX_FLAG_DECRYPTED; 13526 - 13573 + if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && 13574 + !IEEE80211_IS_MULTICAST(wh->i_addr1)) { 13575 + rxi.rxi_flags |= IEEE80211_RXI_HWDEC; 13576 + #if 0 13527 13577 if (!ieee80211_is_robust_mgmt_frame(skb)) { 13528 13578 status->flag |= RX_FLAG_IV_STRIPPED | 13529 13579 RX_FLAG_MMIC_STRIPPED; 13530 13580 hdr->frame_control = __cpu_to_le16(fc & 13531 13581 ~IEEE80211_FCTL_PROTECTED); 13532 13582 } 13583 + #endif 13533 13584 } 13534 13585 13586 + #if 0 13535 13587 if (ieee80211_is_beacon(hdr->frame_control)) 13536 13588 ath11k_mac_handle_beacon(ar, skb); 13537 13589 #endif ··· 19832 19884 frame_tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) | 19833 19885 FIELD_PREP(WMI_TLV_LEN, buf_len); 19834 19886 19835 - memcpy(frame_tlv->value, mtod(frame, void *), buf_len); 19887 + m_copydata(frame, 0, buf_len, frame_tlv->value); 19836 19888 #if 0 /* Not needed on OpenBSD? */ 19837 19889 ath11k_ce_byte_swap(frame_tlv->value, buf_len); 19838 19890 #endif ··· 25152 25204 return ENOSPC; 25153 25205 } 25154 25206 break; 25207 + case IEEE80211_CIPHER_BIP: 25155 25208 default: 25156 25209 ti.encrypt_type = HAL_ENCRYPT_TYPE_OPEN; 25157 25210 break; ··· 25345 25398 qwx_mac_mgmt_tx_wmi(struct qwx_softc *sc, struct qwx_vif *arvif, 25346 25399 uint8_t pdev_id, struct ieee80211_node *ni, struct mbuf *m) 25347 25400 { 25401 + struct ieee80211com *ic = &sc->sc_ic; 25348 25402 struct qwx_txmgmt_queue *txmgmt = &arvif->txmgmt; 25349 25403 struct qwx_tx_data *tx_data; 25404 + struct ieee80211_frame *wh; 25350 25405 int buf_id; 25351 25406 int ret; 25407 + uint8_t subtype; 25352 25408 25353 25409 buf_id = txmgmt->cur; 25354 25410 25355 25411 DNPRINTF(QWX_D_MAC, "%s: tx mgmt frame, buf id %d\n", __func__, buf_id); 25356 25412 25357 - if (txmgmt->queued >= nitems(txmgmt->data)) 25413 + if (txmgmt->queued >= nitems(txmgmt->data)) { 25414 + m_freem(m); 25358 25415 return ENOSPC; 25416 + } 25359 25417 25360 25418 tx_data = &txmgmt->data[buf_id]; 25361 - #if 0 25362 - if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP)) { 25363 - if ((ieee80211_is_action(hdr->frame_control) || 25364 - ieee80211_is_deauth(hdr->frame_control) || 25365 - ieee80211_is_disassoc(hdr->frame_control)) && 25366 - ieee80211_has_protected(hdr->frame_control)) { 25367 - skb_put(skb, IEEE80211_CCMP_MIC_LEN); 25419 + 25420 + wh = mtod(m, struct ieee80211_frame *); 25421 + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; 25422 + 25423 + if ((ni->ni_flags & IEEE80211_NODE_MFP) && 25424 + (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && 25425 + (subtype == IEEE80211_FC0_SUBTYPE_DISASSOC || 25426 + subtype == IEEE80211_FC0_SUBTYPE_DEAUTH || 25427 + subtype == IEEE80211_FC0_SUBTYPE_ACTION)) { 25428 + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { 25429 + struct ieee80211_key *k; 25430 + 25431 + /* BIP needs to be done in software crypto. */ 25432 + k = ieee80211_get_txkey(ic, wh, ni); 25433 + if ((m = ieee80211_encrypt(ic, m, k)) == NULL) 25434 + return ENOBUFS; 25435 + } else { 25436 + int off; 25437 + 25438 + /* Make space for CCMP header. */ 25439 + if (m_makespace(m, ieee80211_get_hdrlen(wh), 25440 + IEEE80211_CCMP_HDRLEN, &off) == NULL) { 25441 + m_freem(m); 25442 + return ENOMEM; 25443 + } 25444 + 25445 + /* Add trailing space for CCMP MIC. */ 25446 + if (m_makespace(m, m->m_pkthdr.len, 25447 + IEEE80211_CCMP_MICLEN, &off) == NULL) { 25448 + m_freem(m); 25449 + return ENOMEM; 25450 + } 25368 25451 } 25369 25452 } 25370 - #endif 25453 + 25371 25454 ret = bus_dmamap_load_mbuf(sc->sc_dmat, tx_data->map, 25372 25455 m, BUS_DMA_WRITE | BUS_DMA_NOWAIT); 25373 25456 if (ret && ret != EFBIG) { 25374 25457 printf("%s: failed to map mgmt Tx buffer: %d\n", 25375 25458 sc->sc_dev.dv_xname, ret); 25459 + m_freem(m); 25376 25460 return ret; 25377 25461 } 25378 25462 if (ret) { ··· 25395 25479 if (ret) { 25396 25480 printf("%s: failed to send mgmt frame: %d\n", 25397 25481 sc->sc_dev.dv_xname, ret); 25482 + m_freem(m); 25398 25483 goto err_unmap_buf; 25399 25484 } 25400 25485 tx_data->ni = ni; ··· 26106 26191 if (ni->ni_rsnprotos == IEEE80211_PROTO_WPA) 26107 26192 arg->need_gtk_2_way = 1; 26108 26193 } 26109 - #if 0 26110 - if (sta->mfp) { 26194 + 26195 + if (ni->ni_flags & IEEE80211_NODE_MFP) { 26111 26196 /* TODO: Need to check if FW supports PMF? */ 26112 26197 arg->is_pmf_enabled = true; 26113 26198 } 26114 - #endif 26115 26199 } 26116 26200 26117 26201 int
+3 -1
sys/dev/ic/qwxvar.h
··· 1 - /* $OpenBSD: qwxvar.h,v 1.31 2025/09/11 11:18:29 stsp Exp $ */ 1 + /* $OpenBSD: qwxvar.h,v 1.32 2025/12/01 16:57:36 stsp Exp $ */ 2 2 3 3 /* 4 4 * Copyright (c) 2018-2019 The Linux Foundation. ··· 1834 1834 struct task newstate_task; 1835 1835 enum ieee80211_state ns_nstate; 1836 1836 int ns_arg; 1837 + 1838 + int deauth_sent; 1837 1839 1838 1840 /* Task for setting encryption keys and its arguments. */ 1839 1841 struct task setkey_task;
+3 -2
sys/dev/pci/if_qwx_pci.c
··· 1 - /* $OpenBSD: if_qwx_pci.c,v 1.29 2025/09/17 07:41:45 stsp Exp $ */ 1 + /* $OpenBSD: if_qwx_pci.c,v 1.30 2025/12/01 16:57:36 stsp Exp $ */ 2 2 3 3 /* 4 4 * Copyright 2023 Stefan Sperling <stsp@openbsd.org> ··· 1084 1084 IEEE80211_C_MONITOR | /* monitor mode supported */ 1085 1085 #endif 1086 1086 IEEE80211_C_SHSLOT | /* short slot time supported */ 1087 - IEEE80211_C_SHPREAMBLE; /* short preamble supported */ 1087 + IEEE80211_C_SHPREAMBLE | /* short preamble supported */ 1088 + IEEE80211_C_MFP; /* management frame protection */ 1088 1089 1089 1090 ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a; 1090 1091 ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b;