mac80211: fix rx monitor filter refcounters
authorChristian Lamparter <chunkeey@googlemail.com>
Sat, 2 Oct 2010 11:17:07 +0000 (13:17 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Tue, 5 Oct 2010 17:35:21 +0000 (13:35 -0400)
This patch fixes an refcounting bug. Previously it
was possible to corrupt the per-device recv. filter
and monitor management counters when:
iw dev wlanX set monitor [new flags]
was issued on an active monitor interface.

Acked-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
net/mac80211/cfg.c
net/mac80211/ieee80211_i.h
net/mac80211/iface.c

index c981604b71e6d78fa1d13d7ec4d8d03976396819..9e63fc28f8599e0d3e2359e914958570dcdc8e89 100644 (file)
@@ -68,8 +68,36 @@ static int ieee80211_change_iface(struct wiphy *wiphy,
                 params && params->use_4addr >= 0)
                sdata->u.mgd.use_4addr = params->use_4addr;
 
-       if (sdata->vif.type == NL80211_IFTYPE_MONITOR && flags)
-               sdata->u.mntr_flags = *flags;
+       if (sdata->vif.type == NL80211_IFTYPE_MONITOR && flags) {
+               struct ieee80211_local *local = sdata->local;
+
+               if (ieee80211_sdata_running(sdata)) {
+                       /*
+                        * Prohibit MONITOR_FLAG_COOK_FRAMES to be
+                        * changed while the interface is up.
+                        * Else we would need to add a lot of cruft
+                        * to update everything:
+                        *      cooked_mntrs, monitor and all fif_* counters
+                        *      reconfigure hardware
+                        */
+                       if ((*flags & MONITOR_FLAG_COOK_FRAMES) !=
+                           (sdata->u.mntr_flags & MONITOR_FLAG_COOK_FRAMES))
+                               return -EBUSY;
+
+                       ieee80211_adjust_monitor_flags(sdata, -1);
+                       sdata->u.mntr_flags = *flags;
+                       ieee80211_adjust_monitor_flags(sdata, 1);
+
+                       ieee80211_configure_filter(local);
+               } else {
+                       /*
+                        * Because the interface is down, ieee80211_do_stop
+                        * and ieee80211_do_open take care of "everything"
+                        * mentioned in the comment above.
+                        */
+                       sdata->u.mntr_flags = *flags;
+               }
+       }
 
        return 0;
 }
index 945fbf29719dd3f47eb658daebec405f159d16ba..f6a6d78efcf00e287276b15c439660632a2df06e 100644 (file)
@@ -1132,6 +1132,8 @@ void ieee80211_if_remove(struct ieee80211_sub_if_data *sdata);
 void ieee80211_remove_interfaces(struct ieee80211_local *local);
 u32 __ieee80211_recalc_idle(struct ieee80211_local *local);
 void ieee80211_recalc_idle(struct ieee80211_local *local);
+void ieee80211_adjust_monitor_flags(struct ieee80211_sub_if_data *sdata,
+                                   const int offset);
 
 static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
 {
index 66785739dad378fe5bdd10904d8c59586951d131..1300e8859ea73c7b960911db15a71b0bd91f490d 100644 (file)
@@ -148,6 +148,26 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
        return 0;
 }
 
+void ieee80211_adjust_monitor_flags(struct ieee80211_sub_if_data *sdata,
+                                   const int offset)
+{
+       struct ieee80211_local *local = sdata->local;
+       u32 flags = sdata->u.mntr_flags;
+
+#define ADJUST(_f, _s) do {                                    \
+       if (flags & MONITOR_FLAG_##_f)                          \
+               local->fif_##_s += offset;                      \
+       } while (0)
+
+       ADJUST(FCSFAIL, fcsfail);
+       ADJUST(PLCPFAIL, plcpfail);
+       ADJUST(CONTROL, control);
+       ADJUST(CONTROL, pspoll);
+       ADJUST(OTHER_BSS, other_bss);
+
+#undef ADJUST
+}
+
 /*
  * NOTE: Be very careful when changing this function, it must NOT return
  * an error on interface type changes that have been pre-checked, so most
@@ -240,17 +260,7 @@ static int ieee80211_do_open(struct net_device *dev, bool coming_up)
                        hw_reconf_flags |= IEEE80211_CONF_CHANGE_MONITOR;
                }
 
-               if (sdata->u.mntr_flags & MONITOR_FLAG_FCSFAIL)
-                       local->fif_fcsfail++;
-               if (sdata->u.mntr_flags & MONITOR_FLAG_PLCPFAIL)
-                       local->fif_plcpfail++;
-               if (sdata->u.mntr_flags & MONITOR_FLAG_CONTROL) {
-                       local->fif_control++;
-                       local->fif_pspoll++;
-               }
-               if (sdata->u.mntr_flags & MONITOR_FLAG_OTHER_BSS)
-                       local->fif_other_bss++;
-
+               ieee80211_adjust_monitor_flags(sdata, 1);
                ieee80211_configure_filter(local);
 
                netif_carrier_on(dev);
@@ -477,17 +487,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
                        hw_reconf_flags |= IEEE80211_CONF_CHANGE_MONITOR;
                }
 
-               if (sdata->u.mntr_flags & MONITOR_FLAG_FCSFAIL)
-                       local->fif_fcsfail--;
-               if (sdata->u.mntr_flags & MONITOR_FLAG_PLCPFAIL)
-                       local->fif_plcpfail--;
-               if (sdata->u.mntr_flags & MONITOR_FLAG_CONTROL) {
-                       local->fif_pspoll--;
-                       local->fif_control--;
-               }
-               if (sdata->u.mntr_flags & MONITOR_FLAG_OTHER_BSS)
-                       local->fif_other_bss--;
-
+               ieee80211_adjust_monitor_flags(sdata, -1);
                ieee80211_configure_filter(local);
                break;
        case NL80211_IFTYPE_MESH_POINT: