Files
kernel-agent/patches/driver/bes2600/decrypt-storm-fast-recover-danctnix/0001-bes2600-pre-empt-AP-deauth-6-mac80211-reassoc-on-decrypt-fail-storm.patch
T
claude-noether b04c8cd501 patches/driver/bes2600/*-danctnix + arch/arm64/scs-...: rebased on danctnix baseline (#29 redo)
PR #33's per-series mirrors were generated against the bes2600-dkms
cleanups branch (rooted at fe73571) without rebasing onto the
v7.0-danctnix1 kernel baseline. Result: per-commit diffs carried
stale baseline context (e.g. from_timer rather than the new
timer_container_of API), so the cumulative no longer applied cleanly
to ohm's actual base. pkgrel=6 build #1 failed with 'Hunk #3 FAILED'
in Patch D's sta.c.

Fix: in marfrit/bes2600-dkms, create danctnix-sync branch
(fe73571 + drop-in replace bes2600/ with v7.0-danctnix1's
drivers/staging/bes2600/), rebase cleanups onto it as
cleanups-rebased-on-danctnix, manually resolve the resulting conflicts
keeping each commit's intent + the new baseline context, rebase
Patch H accordingly. Format-patch and re-route to the same series-dir
names as PR #33.

Conflict resolution notes:
- 'remove userspace /dev/bes2600 character device interface' commit:
  the chardev wrapper was removed but two utility funcs that danctnix's
  bes2600_btuart.c depends on (bes2600_chrdev_is_bus_error,
  bes2600_chrdev_switch_subsys_glb) were re-added with EXPORT_SYMBOL_GPL.
  bes2600_switch_bt re-added as static (file-local, called only from
  bes2600_chrdev_switch_subsys_glb).
- Patch D (atomicize ba_lock): re-resolved bes2600_ba_timer's
  timer_container_of() vs from_timer() to keep the new API.
- SCS Makefile @@ hunk counts corrected from -9,6 +9,10 to -9,6 +9,11
  (the original was actually wrong; build-via-fuzz was masking it).

Cumulative b2sum: ka-promote ohm now emits
  eb179c03f35a4dbaec2e40036f0033ef04985bb6b14ab22419d68e5caaa5874f...
  (279 554 bytes, 32 patches resolved).

pkgrel=6 built from this manifest + installed on ohm 2026-05-19 ~23:39.
Functional verification: bes2600 + bes2600_btuart both load, Pattern A
0 over fresh boot, wlan0 associates to newton. srcversion
1A919EED0E6DC2478559B17 differs from pkgrel=5's BEB625FA... — the
reconstruction is functionally equivalent (5 GHz working, no
firmware/driver race conditions) but NOT byte-equivalent (the chardev
utility re-add chose different formatting than the original danctnix
code). Byte-equivalence is not a goal; per-series traceability and
working hardware are.

Closes (proper this time): #29.
Refs: #28, #30, #33 (the half-working attempt), #31, #32.
2026-05-19 23:44:29 +02:00

222 lines
9.0 KiB
Diff

From 91640bd96d36dd5769b1325e1b2130a95277e0e7 Mon Sep 17 00:00:00 2001
From: "Claude (noether)" <claude@reauktion.de>
Date: Wed, 6 May 2026 19:50:52 +0200
Subject: [PATCH 20/29] bes2600: pre-empt AP-deauth-6 with mac80211 reassoc on
decrypt-fail storm
When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a burst
of received frames (typically because the host's PTK or GTK has fallen
out of sync with the AP), the AP eventually concludes that the STA is
not authenticated and emits an unprotected deauth-reason-6 ("Class 2
frame received from non-authenticated station"). On the deployed
pinetab2 + bes2600 stack this AP-initiated deauth has been observed to
leave the link blackholed for up to 109 s before userspace finds a
different SSID/channel to recover on. (Receipts at
https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.)
Add a sliding-window counter on each bes2600_vif: when 5 decrypt
failures fire within 5 s, schedule a worker that calls
ieee80211_connection_loss(vif). mac80211 then performs immediate
disassociation; userspace (NetworkManager / wpa_supplicant) reconnects
with fresh keys before the AP gets a chance to fire its unprotected
deauth.
Predicted Phase 7 delta vs the unpatched baseline:
- decrypt-burst rate: unchanged (this does not address root cause)
- AP-deauth-6 rate: <= 0.2 of baseline
- conditional probability of >5s blackhole given a burst:
100% -> <= 10%
- worst-case recovery time: 109s -> <5s
Contract pin: ieee80211_connection_loss() per
include/net/mac80211.h: "may also be called if the connection needs to
be terminated for some other reason... will cause immediate change to
disassociated state, without connection recovery attempts." Userspace
recovery is the existing NM/wpa_supplicant path. The worker context
satisfies the implicit process-context expectation.
Files touched:
- bes2600/bes2600.h: 4 new fields on struct bes2600_vif + 2 prototypes
- bes2600/txrx.c: new helpers + the call site at the existing
WSM_STATUS_DECRYPTFAILURE log point (the unconditional "goto drop"
branch in bes2600_rx_cb)
- bes2600/sta.c: bes2600_decrypt_storm_init() in bes2600_vif_setup;
cancel_work_sync() in bes2600_remove_interface, alongside the
existing per-vif cancel_*_work_sync block. Safe under the kernel
cancel_work_sync contract: the work_struct is INIT_WORK'd in setup,
so the call is valid; it blocks until any in-flight handler returns,
ensuring no use-after-free of priv when mac80211 frees the vif; and
it is idempotent (subsequent calls just return false).
- bes2600/debug.c: DecryptStormRecoveries seq_printf in the per-vif
status seq_file output
Threshold (5/5s) is set well above the steady-state per-vif decrypt-
fail rate observed in measurement (~1/min even under sustained 1 MB/s
load), so a true storm is required to trip it. The cw1200/cw1260
ancestor has no equivalent storm-recovery; this is a clean addition.
checkpatch.pl --no-tree --strict: clean (0/0/0).
Signed-off-by: Claude (noether) <claude@reauktion.de>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
bes2600/bes2600.h | 9 ++++++
bes2600/debug.c | 2 ++
bes2600/sta.c | 2 ++
bes2600/txrx.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 87 insertions(+)
diff --git a/drivers/staging/bes2600/bes2600.h b/drivers/staging/bes2600/bes2600.h
index 0e60960..66482f7 100644
--- a/drivers/staging/bes2600/bes2600.h
+++ b/drivers/staging/bes2600/bes2600.h
@@ -596,6 +596,11 @@ struct bes2600_vif {
unsigned long rx_timestamp;
u32 cipherType;
+ /* Decrypt-storm fast-recover (Trigger B). See txrx.c. */
+ unsigned long decrypt_storm_window_start;
+ unsigned int decrypt_storm_count;
+ unsigned int decrypt_storm_recoveries;
+ struct work_struct decrypt_storm_recover_work;
/* AP powersave */
u32 link_id_map;
@@ -856,4 +861,8 @@ int bes2600_btusb_setup_pipes(struct sbus_priv *sbus_priv);
void bes2600_btusb_uninit(struct usb_interface *interface);
#endif
+/* Decrypt-storm fast-recover helpers — see txrx.c. */
+void bes2600_decrypt_storm_init(struct bes2600_vif *priv);
+void bes2600_decrypt_storm_account(struct bes2600_vif *priv);
+
#endif /* BES2600_H */
diff --git a/drivers/staging/bes2600/debug.c b/drivers/staging/bes2600/debug.c
index 5228b22..ca223dd 100644
--- a/drivers/staging/bes2600/debug.c
+++ b/drivers/staging/bes2600/debug.c
@@ -542,6 +542,8 @@ static int bes2600_status_show_priv(struct seq_file *seq, void *v)
priv->listening ? " (listening)" : "");
seq_printf(seq, "Assoc: %s\n",
bes2600_debug_join_status[priv->join_status]);
+ seq_printf(seq, "DecryptStormRecoveries: %u\n",
+ priv->decrypt_storm_recoveries);
if (priv->rx_filter.promiscuous)
seq_puts(seq, "Filter: promisc\n");
else if (priv->rx_filter.fcs)
diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c
index bc6d483..139bdae 100644
--- a/drivers/staging/bes2600/sta.c
+++ b/drivers/staging/bes2600/sta.c
@@ -464,6 +464,7 @@ void bes2600_remove_interface(struct ieee80211_hw *dev,
cancel_delayed_work_sync(&priv->join_timeout);
cancel_delayed_work_sync(&priv->set_cts_work);
cancel_delayed_work_sync(&priv->pending_offchanneltx_work);
+ cancel_work_sync(&priv->decrypt_storm_recover_work);
timer_delete_sync(&priv->mcast_timeout);
/* TODO:COMBO: May be reset of these variables "delayed_link_loss and
@@ -2639,6 +2640,7 @@ int bes2600_vif_setup(struct bes2600_vif *priv)
/* Setup per vif workitems and locks */
spin_lock_init(&priv->vif_lock);
+ bes2600_decrypt_storm_init(priv);
INIT_WORK(&priv->join_work, bes2600_join_work);
INIT_DELAYED_WORK(&priv->join_timeout, bes2600_join_timeout);
INIT_WORK(&priv->unjoin_work, bes2600_unjoin_work);
diff --git a/drivers/staging/bes2600/txrx.c b/drivers/staging/bes2600/txrx.c
index 017f0d8..f6a66d6 100644
--- a/drivers/staging/bes2600/txrx.c
+++ b/drivers/staging/bes2600/txrx.c
@@ -26,6 +26,78 @@
#define BES2600_INVALID_RATE_ID (0xFF)
+/*
+ * Decrypt-storm fast-recover (Trigger B).
+ *
+ * When the BES2600 firmware reports WSM_STATUS_DECRYPTFAILURE for a
+ * burst of received frames (typically because the host's PTK or GTK
+ * has fallen out of sync with the AP), the AP eventually concludes that
+ * the STA is not authenticated and emits an unprotected deauth-reason-6
+ * ("Class 2 frame received from non-authenticated station"). On the
+ * deployed pinetab2 + bes2600 stack this AP-initiated deauth has been
+ * observed to leave the link blackholed for up to 109 s before
+ * userspace finds a different SSID/channel to recover on. (Receipts at
+ * https://git.reauktion.de/marfrit/besser, notes/phase5-2026-05-06.md.)
+ *
+ * Recovery here pre-empts the AP: when we see THRESHOLD decrypt
+ * failures within WINDOW, we ask mac80211 for a clean reassoc via
+ * ieee80211_connection_loss(), which causes immediate disassociation
+ * and lets userspace auto-reconnect with fresh keys.
+ *
+ * mac80211 contract: ieee80211_connection_loss() may be called
+ * regardless of IEEE80211_HW_CONNECTION_MONITOR; it causes immediate
+ * disassociation without driver-side recovery attempts. See
+ * include/net/mac80211.h for the canonical doc-comment.
+ *
+ * The threshold is set well above the steady-state per-vif
+ * decrypt-fail rate observed in measurement (~1/min even under
+ * sustained 1 MB/s load), so a true storm is required to trip it.
+ */
+#define BES2600_DECRYPT_STORM_THRESHOLD 5
+#define BES2600_DECRYPT_STORM_WINDOW_MS 5000
+
+static void bes2600_decrypt_storm_recover_work(struct work_struct *work)
+{
+ struct bes2600_vif *priv = container_of(work, struct bes2600_vif,
+ decrypt_storm_recover_work);
+
+ if (!priv->vif)
+ return;
+
+ bes_warn("[bes2600] decrypt-storm fast-recover: forcing reassoc\n");
+ ieee80211_connection_loss(priv->vif);
+ priv->decrypt_storm_recoveries++;
+}
+
+void bes2600_decrypt_storm_init(struct bes2600_vif *priv)
+{
+ INIT_WORK(&priv->decrypt_storm_recover_work,
+ bes2600_decrypt_storm_recover_work);
+ priv->decrypt_storm_window_start = 0;
+ priv->decrypt_storm_count = 0;
+ priv->decrypt_storm_recoveries = 0;
+}
+
+void bes2600_decrypt_storm_account(struct bes2600_vif *priv)
+{
+ unsigned long now = jiffies;
+ unsigned long window = msecs_to_jiffies(BES2600_DECRYPT_STORM_WINDOW_MS);
+
+ if (priv->decrypt_storm_window_start == 0 ||
+ time_after(now, priv->decrypt_storm_window_start + window)) {
+ priv->decrypt_storm_window_start = now;
+ priv->decrypt_storm_count = 1;
+ return;
+ }
+
+ if (++priv->decrypt_storm_count >= BES2600_DECRYPT_STORM_THRESHOLD) {
+ priv->decrypt_storm_count = 0;
+ /* Skew the window so we don't re-fire on the same storm. */
+ priv->decrypt_storm_window_start = now + window;
+ schedule_work(&priv->decrypt_storm_recover_work);
+ }
+}
+
#ifdef CONFIG_BES2600_TESTMODE
#include "bes_nl80211_testmode_msg.h"
#endif /* CONFIG_BES2600_TESTMODE */
@@ -1694,6 +1766,8 @@ void bes2600_rx_cb(struct bes2600_vif *priv,
goto drop;
} else {
bes_warn("[RX] Receive failure: %d.\n", arg->status);
+ if (arg->status == WSM_STATUS_DECRYPTFAILURE)
+ bes2600_decrypt_storm_account(priv);
goto drop;
}
}
--
2.54.0