netdevice: Fix promiscuity and allmulti overflow
authorWang Chen <wangchen@cn.fujitsu.com>
Wed, 18 Jun 2008 08:48:28 +0000 (01:48 -0700)
committerDavid S. Miller <davem@davemloft.net>
Wed, 18 Jun 2008 08:48:28 +0000 (01:48 -0700)
Max of promiscuity and allmulti plus positive @inc can cause overflow.
Fox example: when allmulti=0xFFFFFFFF, any caller give dev_set_allmulti() a
positive @inc will cause allmulti be off.
This is not what we want, though it's rare case.
The fix is that only negative @inc will cause allmulti or promiscuity be off
and when any caller makes the counters touch the roof, we return error.

Change of v2:
Change void function dev_set_promiscuity/allmulti to return int.
So callers can get the overflow error.
Caller's fix will be done later.

Change of v3:
1. Since we return error to caller, we don't need to print KERN_ERROR,
KERN_WARNING is enough.
2. In dev_set_promiscuity(), if __dev_set_promiscuity() failed, we
return at once.

Signed-off-by: Wang Chen <wangchen@cn.fujitsu.com>
Acked-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/netdevice.h
net/core/dev.c

index 4bf613cd9e2da21a03138b31b1f80fb20ac8542a..45dce2b58d4ca41dbcd7ae523d459acff16ae948 100644 (file)
@@ -1476,8 +1476,8 @@ extern int                __dev_addr_delete(struct dev_addr_list **list, int *count, void *ad
 extern int             __dev_addr_add(struct dev_addr_list **list, int *count, void *addr, int alen, int newonly);
 extern int             __dev_addr_sync(struct dev_addr_list **to, int *to_count, struct dev_addr_list **from, int *from_count);
 extern void            __dev_addr_unsync(struct dev_addr_list **to, int *to_count, struct dev_addr_list **from, int *from_count);
-extern void            dev_set_promiscuity(struct net_device *dev, int inc);
-extern void            dev_set_allmulti(struct net_device *dev, int inc);
+extern int             dev_set_promiscuity(struct net_device *dev, int inc);
+extern int             dev_set_allmulti(struct net_device *dev, int inc);
 extern void            netdev_state_change(struct net_device *dev);
 extern void            netdev_bonding_change(struct net_device *dev);
 extern void            netdev_features_change(struct net_device *dev);
index 0e45742e7158f719d5106ded650d60bff9a5cc6e..a495f712d38ce4683b2a77cde1b55156ac3b619f 100644 (file)
@@ -2771,16 +2771,29 @@ int netdev_set_master(struct net_device *slave, struct net_device *master)
        return 0;
 }
 
-static void __dev_set_promiscuity(struct net_device *dev, int inc)
+static int __dev_set_promiscuity(struct net_device *dev, int inc)
 {
        unsigned short old_flags = dev->flags;
 
        ASSERT_RTNL();
 
-       if ((dev->promiscuity += inc) == 0)
-               dev->flags &= ~IFF_PROMISC;
-       else
-               dev->flags |= IFF_PROMISC;
+       dev->flags |= IFF_PROMISC;
+       dev->promiscuity += inc;
+       if (dev->promiscuity == 0) {
+               /*
+                * Avoid overflow.
+                * If inc causes overflow, untouch promisc and return error.
+                */
+               if (inc < 0)
+                       dev->flags &= ~IFF_PROMISC;
+               else {
+                       dev->promiscuity -= inc;
+                       printk(KERN_WARNING "%s: promiscuity touches roof, "
+                               "set promiscuity failed, promiscuity feature "
+                               "of device might be broken.\n", dev->name);
+                       return -EOVERFLOW;
+               }
+       }
        if (dev->flags != old_flags) {
                printk(KERN_INFO "device %s %s promiscuous mode\n",
                       dev->name, (dev->flags & IFF_PROMISC) ? "entered" :
@@ -2798,6 +2811,7 @@ static void __dev_set_promiscuity(struct net_device *dev, int inc)
                if (dev->change_rx_flags)
                        dev->change_rx_flags(dev, IFF_PROMISC);
        }
+       return 0;
 }
 
 /**
@@ -2809,14 +2823,19 @@ static void __dev_set_promiscuity(struct net_device *dev, int inc)
  *     remains above zero the interface remains promiscuous. Once it hits zero
  *     the device reverts back to normal filtering operation. A negative inc
  *     value is used to drop promiscuity on the device.
+ *     Return 0 if successful or a negative errno code on error.
  */
-void dev_set_promiscuity(struct net_device *dev, int inc)
+int dev_set_promiscuity(struct net_device *dev, int inc)
 {
        unsigned short old_flags = dev->flags;
+       int err;
 
-       __dev_set_promiscuity(dev, inc);
+       err = __dev_set_promiscuity(dev, inc);
+       if (!err)
+               return err;
        if (dev->flags != old_flags)
                dev_set_rx_mode(dev);
+       return err;
 }
 
 /**
@@ -2829,22 +2848,38 @@ void dev_set_promiscuity(struct net_device *dev, int inc)
  *     to all interfaces. Once it hits zero the device reverts back to normal
  *     filtering operation. A negative @inc value is used to drop the counter
  *     when releasing a resource needing all multicasts.
+ *     Return 0 if successful or a negative errno code on error.
  */
 
-void dev_set_allmulti(struct net_device *dev, int inc)
+int dev_set_allmulti(struct net_device *dev, int inc)
 {
        unsigned short old_flags = dev->flags;
 
        ASSERT_RTNL();
 
        dev->flags |= IFF_ALLMULTI;
-       if ((dev->allmulti += inc) == 0)
-               dev->flags &= ~IFF_ALLMULTI;
+       dev->allmulti += inc;
+       if (dev->allmulti == 0) {
+               /*
+                * Avoid overflow.
+                * If inc causes overflow, untouch allmulti and return error.
+                */
+               if (inc < 0)
+                       dev->flags &= ~IFF_ALLMULTI;
+               else {
+                       dev->allmulti -= inc;
+                       printk(KERN_WARNING "%s: allmulti touches roof, "
+                               "set allmulti failed, allmulti feature of "
+                               "device might be broken.\n", dev->name);
+                       return -EOVERFLOW;
+               }
+       }
        if (dev->flags ^ old_flags) {
                if (dev->change_rx_flags)
                        dev->change_rx_flags(dev, IFF_ALLMULTI);
                dev_set_rx_mode(dev);
        }
+       return 0;
 }
 
 /*