netlink: avoid a double skb free in genlmsg_mcast()
[pandora-kernel.git] / net / core / fib_rules.c
index 3231b46..c7caf3e 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/slab.h>
 #include <linux/list.h>
+#include <linux/module.h>
 #include <net/net_namespace.h>
 #include <net/sock.h>
 #include <net/fib_rules.h>
@@ -442,7 +443,8 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
                if (frh->action && (frh->action != rule->action))
                        continue;
 
-               if (frh->table && (frh_get_table(frh, tb) != rule->table))
+               if (frh_get_table(frh, tb) &&
+                   (frh_get_table(frh, tb) != rule->table))
                        continue;
 
                if (tb[FRA_PRIORITY] &&
@@ -475,8 +477,11 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
 
                list_del_rcu(&rule->list);
 
-               if (rule->action == FR_ACT_GOTO)
+               if (rule->action == FR_ACT_GOTO) {
                        ops->nr_goto_rules--;
+                       if (rtnl_dereference(rule->ctarget) == NULL)
+                               ops->unresolved_rules--;
+               }
 
                /*
                 * Check if this rule is a target to any of them. If so,
@@ -487,7 +492,7 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
                if (ops->nr_goto_rules > 0) {
                        list_for_each_entry(tmp, &ops->rules_list, list) {
                                if (rtnl_dereference(tmp->ctarget) == rule) {
-                                       rcu_assign_pointer(tmp->ctarget, NULL);
+                                       RCU_INIT_POINTER(tmp->ctarget, NULL);
                                        ops->unresolved_rules++;
                                }
                        }
@@ -545,7 +550,7 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,
        frh->flags = rule->flags;
 
        if (rule->action == FR_ACT_GOTO &&
-           rcu_dereference_raw(rule->ctarget) == NULL)
+           rcu_access_pointer(rule->ctarget) == NULL)
                frh->flags |= FIB_RULE_UNRESOLVED;
 
        if (rule->iifname[0]) {
@@ -589,15 +594,17 @@ static int dump_rules(struct sk_buff *skb, struct netlink_callback *cb,
 {
        int idx = 0;
        struct fib_rule *rule;
+       int err = 0;
 
        rcu_read_lock();
        list_for_each_entry_rcu(rule, &ops->rules_list, list) {
                if (idx < cb->args[1])
                        goto skip;
 
-               if (fib_nl_fill_rule(skb, rule, NETLINK_CB(cb->skb).pid,
-                                    cb->nlh->nlmsg_seq, RTM_NEWRULE,
-                                    NLM_F_MULTI, ops) < 0)
+               err = fib_nl_fill_rule(skb, rule, NETLINK_CB(cb->skb).pid,
+                                      cb->nlh->nlmsg_seq, RTM_NEWRULE,
+                                      NLM_F_MULTI, ops);
+               if (err < 0)
                        break;
 skip:
                idx++;
@@ -606,7 +613,7 @@ skip:
        cb->args[1] = idx;
        rules_ops_put(ops);
 
-       return skb->len;
+       return err;
 }
 
 static int fib_nl_dumprule(struct sk_buff *skb, struct netlink_callback *cb)
@@ -622,7 +629,9 @@ static int fib_nl_dumprule(struct sk_buff *skb, struct netlink_callback *cb)
                if (ops == NULL)
                        return -EAFNOSUPPORT;
 
-               return dump_rules(skb, cb, ops);
+               dump_rules(skb, cb, ops);
+
+               return skb->len;
        }
 
        rcu_read_lock();
@@ -713,6 +722,13 @@ static int fib_rules_event(struct notifier_block *this, unsigned long event,
                        attach_rules(&ops->rules_list, dev);
                break;
 
+       case NETDEV_CHANGENAME:
+               list_for_each_entry(ops, &net->rules_ops, list) {
+                       detach_rules(&ops->rules_list, dev);
+                       attach_rules(&ops->rules_list, dev);
+               }
+               break;
+
        case NETDEV_UNREGISTER:
                list_for_each_entry(ops, &net->rules_ops, list)
                        detach_rules(&ops->rules_list, dev);