net: icmp: fix data-race in cmp_global_allow()
[linux-2.6-block.git] / net / ipv4 / icmp.c
index 4298aae74e0ee6266286fa49e9cfda602b2a9cd0..18068ed42f258349a07248a85acdf93b8c2f1749 100644 (file)
@@ -249,10 +249,11 @@ bool icmp_global_allow(void)
        bool rc = false;
 
        /* Check if token bucket is empty and cannot be refilled
-        * without taking the spinlock.
+        * without taking the spinlock. The READ_ONCE() are paired
+        * with the following WRITE_ONCE() in this same function.
         */
-       if (!icmp_global.credit) {
-               delta = min_t(u32, now - icmp_global.stamp, HZ);
+       if (!READ_ONCE(icmp_global.credit)) {
+               delta = min_t(u32, now - READ_ONCE(icmp_global.stamp), HZ);
                if (delta < HZ / 50)
                        return false;
        }
@@ -262,14 +263,14 @@ bool icmp_global_allow(void)
        if (delta >= HZ / 50) {
                incr = sysctl_icmp_msgs_per_sec * delta / HZ ;
                if (incr)
-                       icmp_global.stamp = now;
+                       WRITE_ONCE(icmp_global.stamp, now);
        }
        credit = min_t(u32, icmp_global.credit + incr, sysctl_icmp_msgs_burst);
        if (credit) {
                credit--;
                rc = true;
        }
-       icmp_global.credit = credit;
+       WRITE_ONCE(icmp_global.credit, credit);
        spin_unlock(&icmp_global.lock);
        return rc;
 }
@@ -682,7 +683,8 @@ void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
                        dev = dev_get_by_index_rcu(net, inet_iif(skb_in));
 
                if (dev)
-                       saddr = inet_select_addr(dev, 0, RT_SCOPE_LINK);
+                       saddr = inet_select_addr(dev, iph->saddr,
+                                                RT_SCOPE_LINK);
                else
                        saddr = 0;
                rcu_read_unlock();