RE: [PATCH 2/4] wifi: iwlwifi: mvm: clamp set_freqs iteration to n_nd_channels
From: Korenblit, Miriam Rachel
Date: Tue Jul 14 2026 - 11:51:27 EST
> -----Original Message-----
> From: Michael Bommarito <michael.bommarito@xxxxxxxxx>
> Sent: Friday, May 15, 2026 3:11 PM
> To: Korenblit, Miriam Rachel <miriam.rachel.korenblit@xxxxxxxxx>
> Cc: Berg, Johannes <johannes.berg@xxxxxxxxx>; Grumbach, Emmanuel
> <emmanuel.grumbach@xxxxxxxxx>; linux-wireless@xxxxxxxxxxxxxxx; linux-
> kernel@xxxxxxxxxxxxxxx; stable@xxxxxxxxxxxxxxx
> Subject: [PATCH 2/4] wifi: iwlwifi: mvm: clamp set_freqs iteration to
> n_nd_channels
>
> iwl_mvm_query_set_freqs() iterates over bit positions
> 0 .. SCAN_OFFLOAD_MATCHING_CHANNELS_LEN * 8 - 1 (= 0..55 on the v2 path,
> 0..39 on the v1 path) and, for each set bit, performs:
>
> match->channels[n_channels++] =
> mvm->nd_channels[i]->center_freq;
>
> without constraining i against mvm->n_nd_channels. The pointer table mvm-
> >nd_channels is kmemdup()ed at suspend time with exactly mvm-
> >n_nd_channels entries (whatever the userspace NL80211_CMD_SET_WOWLAN
> request supplied as nd_config->n_channels; typical real-world values are 5..50).
> If the firmware response contains any matching_channels[] bit set at a position
> >= mvm->n_nd_channels, the indexed load reads a u8* slot past the end of the
> pointer-table allocation, then the immediate ->center_freq dereferences that wild
> pointer.
>
> The pre-existing caller guard
>
> if (mvm->n_nd_channels < n_channels)
> continue;
>
> compares the bitmap's popcount to the table length, not the bit positions to the
> table length. A bitmap with three set bits at positions {50, 51, 52} has popcount 3
> and passes the guard unconditionally, then walks 50+ entries off the end of
> mvm->nd_channels.
>
> Reproduced under UML+KASAN via a KUnit harness that lifts the iteration logic.
> With nd_channels allocated as 5 entries and matching_channels bits set at
> positions 7 (immediate redzone) and 50 (far OOB), the kernel panics on the wild
> deref:
>
> Kernel panic - not syncing: Segfault with no mm
> RIP: 0033:set_freqs_buggy.constprop.0+0xc1/0x15e
>
> (The selector 0x0033 in the RIP line is UML's user-mode segment; under UML, in-
> kernel code runs in ring 3 on the host. The trap is a kernel-context page fault on
> the wild-pointer deref.)
>
> Building drivers/net/wireless/intel/iwlwifi/mvm/d3.o under
> x86_64 allmodconfig with the fix applied yields no new warnings.
>
> Clamp the iteration upper bound to min(matching-bits-width,
> mvm->n_nd_channels) so high-position bits, however the firmware
> emitted them, cannot index past the pointer table. Mirror the fix for the v1
> fallback arm.
>
> Cc: stable@xxxxxxxxxxxxxxx
> Fixes: 8ed4e659f34c ("iwlwifi: mvm: add channel information to the netdetect
> notifications")
> Signed-off-by: Michael Bommarito <michael.bommarito@xxxxxxxxx>
> Assisted-by: Claude:claude-opus-4-7
> ---
> drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c
> b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c
> index c17ac62feec3..b04d8dd26cd0 100644
> --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c
> +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c
> @@ -2514,16 +2514,20 @@ static void iwl_mvm_query_set_freqs(struct
> iwl_mvm *mvm,
> IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS)) {
> struct iwl_scan_offload_profile_match *matches =
> (void *)results->matches;
> + int max = min_t(int,
> SCAN_OFFLOAD_MATCHING_CHANNELS_LEN * 8,
> + mvm->n_nd_channels);
>
In case the FW is misbehaving, we want to know about it, and we use
IWL_FW_CHECK. Also in mld.
> - for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN * 8;
> i++)
> + for (i = 0; i < max; i++)
> if (matches[idx].matching_channels[i / 8] & (BIT(i % 8)))
> match->channels[n_channels++] =
> mvm->nd_channels[i]->center_freq;
> } else {
> struct iwl_scan_offload_profile_match_v1 *matches =
> (void *)results->matches;
> + int max = min_t(int,
> SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1 * 8,
> + mvm->n_nd_channels);
>
> - for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1 *
> 8; i++)
> + for (i = 0; i < max; i++)
> if (matches[idx].matching_channels[i / 8] & (BIT(i % 8)))
> match->channels[n_channels++] =
> mvm->nd_channels[i]->center_freq;
> --
> 2.53.0