From 5c86fb47f1619011f51724fbb0b17c6eb1c8066e Mon Sep 17 00:00:00 2001 From: Travis Weathers <59842250+tweathers-sec@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:19:25 -0400 Subject: [PATCH] fix(armsrc): poll the BWM link in data_available() data_available() and data_available_fast() are how the device notices that the host has asked a running command to stop. They branch on USB and on WITH_FPC_USART_HOST, and never learned about the Proxmark5 BWM. A PM5 with the wireless module builds PLATFORM=PM5 PLATFORM_EXTRAS=BWM, which defines WITH_BWM_FORWARD. BTADDON errors out on PM5 and BWM errors out on anything else (common_arm/Makefile.hal), so WITH_FPC_USART_HOST is never defined on that build and both functions compile down to a USB-only check. Commands still arrive, because receive_ng() does poll the module (armsrc/cmd.c). What was blind was the abort path: CMD_BREAK_LOOP is never observed over BLE or WiFi, so every loop whose only exit is this check runs until the button is pressed. That is 75 call sites across 26 files in armsrc/, including the sniff, simulate and reader loops, so in practice a wireless session cannot be interrupted at all. ReaderHitag() is the worst case: with no tag on the antenna it has no other exit, so `lf search` alone leaves the device answering nothing. This is reachable with upstream tooling only: client/src/uart/ble_posix.c is the native BLE transport, selected with `-p ble:`, and doc/md/PM5_Start_Here/PM5-BWM-USAGE.md documents both that and `-p tcp::` over the module's WiFi. Both share the same inbound de-framer, so both are affected. Folded into the existing preprocessor chain as an #elif, mirroring receive_ng(), since the two transports are mutually exclusive by build. USB is still tested first, so the module is only polled when USB has nothing, exactly as the FPC branch behaves. bwm_fwd_rxdata_available() is the same call receive_ng() already uses. When the de-frame FIFO is empty it costs a flag test and one DMA counter read (bwm_uart_rx_available()); it only walks bytes when bytes have actually arrived, and those bytes are the abort. That mirrors the FPC branch, where usart_rxdata_available() already calls usart_rx_poll() from these same two functions. data_available_fast()'s only caller is the ReadLF_realtime() sampling loop, which reaches it once per 64 saved samples; it is included here so the two functions cannot drift apart again, which is how this bug arose. Object code is byte-identical on every platform that does not define WITH_BWM_FORWARD. Built PLATFORM=PM3RDV4, PM3RDV4 with BTADDON, PM5 without BWM and PM5 with BWM; only the last one changes: PM3RDV4 74f7f7a1... -> 74f7f7a1... identical PM3RDV4 BTADDON f94c31de... -> f94c31de... identical PM5 e3f0a203... -> e3f0a203... identical PM5 BWM e3f0a203... -> efbba2f0... changed Passes `make style` unmodified. Verified on a Proxmark5 with the BWM fitted, over BLE, with the stock client. Before, one command left the device deaf for good: [fpc|tcp] pm5 --> hw ping [+] Ping response received in 111 ms and content ( ok ) [fpc|tcp] pm5 --> lf hitag read --ht2 --pwd -k 4D494B52 [!] timeout while waiting for reply [fpc|tcp] pm5 --> hw ping [!] Ping response timeout [fpc|tcp] pm5 --> hw ping [!] Ping response timeout After, the same sequence on the same device: [fpc|tcp] pm5 --> hw ping [+] Ping response received in 107 ms and content ( ok ) [fpc|tcp] pm5 --> lf hitag read --ht2 --pwd -k 4D494B52 [!] timeout while waiting for reply [fpc|tcp] pm5 --> hw ping [+] Ping response received in 102 ms and content ( ok ) [fpc|tcp] pm5 --> hw ping [+] Ping response received in 85 ms and content ( ok ) A USB command recovers a device stuck this way, which is why the bug is easy to miss on a bench with a cable attached: after the failing sequence above, `hw ping` over USB answered in 1 ms and the wireless link resumed. --- CHANGELOG.md | 1 + armsrc/util.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c4d2ce34..c0f94769f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Fixed `PLATFORM_EXTRAS=BWM` - a command started over the Proxmark5 wireless module can be aborted again, `data_available()` only polled USB so CMD_BREAK_LOOP was never seen on the BLE or WiFi link (@tweathers-sec) - Added `hf mfdes sim` - answers GetDFNames and GetISOFileIDs, so a reader sees the ISO file ids and DF names of the simulated card (@iceman1001) - Fixed `hf 14a` - an FPGA bitstream download no longer destroys the emulator memory that `eload` just filled (@iceman1001) - Changed `hf mfdes sim` - now a self contained DESFire simulation, answering both native and ISO 7816 wrapped commands (@iceman1001) diff --git a/armsrc/util.c b/armsrc/util.c index 72333dd5b..d163f47b3 100644 --- a/armsrc/util.c +++ b/armsrc/util.c @@ -25,6 +25,9 @@ #include "string.h" #include "usb_cdc_apis.h" #include "usart.h" +#ifdef WITH_BWM_FORWARD +#include "bwm_forward.h" +#endif #include "BigBuf.h" // trace_restart_timeline #ifdef WITH_SMARTCARD #include "i2c.h" // sc_log_trace_reset @@ -327,7 +330,11 @@ int BUTTON_HELD(int ms) { // This function returns false if no data is available or // the USB connection is invalid. bool data_available(void) { -#ifdef WITH_FPC_USART_HOST +#if defined(WITH_BWM_FORWARD) + // The BWM (BLE / WiFi) link is the host connection on a Proxmark5, so it + // has to be polled here too or CMD_BREAK_LOOP is never seen over it. + return usb_poll_validate_length() || (bwm_fwd_rxdata_available() > 0); +#elif defined(WITH_FPC_USART_HOST) return usb_poll_validate_length() || (usart_rxdata_available() > 0); #else return usb_poll_validate_length(); @@ -338,7 +345,9 @@ bool data_available(void) { // In most of the cases, you should use data_available() unless // the timing is critical. bool data_available_fast(void) { -#ifdef WITH_FPC_USART_HOST +#if defined(WITH_BWM_FORWARD) + return usb_available_length() || (bwm_fwd_rxdata_available() > 0); +#elif defined(WITH_FPC_USART_HOST) return usb_available_length() || (usart_rxdata_available() > 0); #else return usb_available_length();