pkt_sched: gen_kill_estimator() rcu fixes
authorEric Dumazet <eric.dumazet@gmail.com>
Wed, 9 Jun 2010 02:09:23 +0000 (02:09 +0000)
committerDavid S. Miller <davem@davemloft.net>
Sat, 12 Jun 2010 01:37:08 +0000 (18:37 -0700)
gen_kill_estimator() API is incomplete or not well documented, since
caller should make sure an RCU grace period is respected before
freeing stats_lock.

This was partially addressed in commit 5d944c640b4
(gen_estimator: deadlock fix), but same problem exist for all
gen_kill_estimator() users, if lock they use is not already RCU
protected.

A code review shows xt_RATEEST.c, act_api.c, act_police.c have this
problem. Other are ok because they use qdisc lock, already RCU
protected.

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/act_api.h
include/net/netfilter/xt_rateest.h
net/core/gen_estimator.c
net/netfilter/xt_RATEEST.c
net/sched/act_api.c
net/sched/act_police.c

index c05fd717c588801a72c4fe7a18a000b4bf46e35c..bab385f13ac32e81fd758e88811397a11964b64e 100644 (file)
@@ -20,6 +20,7 @@ struct tcf_common {
        struct gnet_stats_queue         tcfc_qstats;
        struct gnet_stats_rate_est      tcfc_rate_est;
        spinlock_t                      tcfc_lock;
+       struct rcu_head                 tcfc_rcu;
 };
 #define tcf_next       common.tcfc_next
 #define tcf_index      common.tcfc_index
@@ -32,6 +33,7 @@ struct tcf_common {
 #define tcf_qstats     common.tcfc_qstats
 #define tcf_rate_est   common.tcfc_rate_est
 #define tcf_lock       common.tcfc_lock
+#define tcf_rcu                common.tcfc_rcu
 
 struct tcf_police {
        struct tcf_common       common;
index ddbf37e19616a486b73b15a7e5314c47e1ed80a8..5e1427795928541d3eb37bd3c46e87b4b90c434d 100644 (file)
@@ -9,6 +9,7 @@ struct xt_rateest {
        struct gnet_estimator           params;
        struct gnet_stats_rate_est      rstats;
        struct gnet_stats_basic_packed  bstats;
+       struct rcu_head                 rcu;
 };
 
 extern struct xt_rateest *xt_rateest_lookup(const char *name);
index 785e5276a300e6f14c634e785eb1be8e0633c18e..9fbe7f7429b0efd036cc9e4a7df06293f85a8f0b 100644 (file)
@@ -263,6 +263,7 @@ static void __gen_kill_estimator(struct rcu_head *head)
  *
  * Removes the rate estimator specified by &bstats and &rate_est.
  *
+ * Note : Caller should respect an RCU grace period before freeing stats_lock
  */
 void gen_kill_estimator(struct gnet_stats_basic_packed *bstats,
                        struct gnet_stats_rate_est *rate_est)
index 69c01e10f8afe00ebcdefd1becdec4875fa84a06..de079abd5bc873ae270abdfaa8d9c28af7df11ba 100644 (file)
@@ -60,13 +60,22 @@ struct xt_rateest *xt_rateest_lookup(const char *name)
 }
 EXPORT_SYMBOL_GPL(xt_rateest_lookup);
 
+static void xt_rateest_free_rcu(struct rcu_head *head)
+{
+       kfree(container_of(head, struct xt_rateest, rcu));
+}
+
 void xt_rateest_put(struct xt_rateest *est)
 {
        mutex_lock(&xt_rateest_mutex);
        if (--est->refcnt == 0) {
                hlist_del(&est->list);
                gen_kill_estimator(&est->bstats, &est->rstats);
-               kfree(est);
+               /*
+                * gen_estimator est_timer() might access est->lock or bstats,
+                * wait a RCU grace period before freeing 'est'
+                */
+               call_rcu(&est->rcu, xt_rateest_free_rcu);
        }
        mutex_unlock(&xt_rateest_mutex);
 }
@@ -179,6 +188,7 @@ static int __init xt_rateest_tg_init(void)
 static void __exit xt_rateest_tg_fini(void)
 {
        xt_unregister_target(&xt_rateest_tg_reg);
+       rcu_barrier(); /* Wait for completion of call_rcu()'s (xt_rateest_free_rcu) */
 }
 
 
index 972378f47f3cfd2ef03b33ac3a3a5ea7801e7afe..23b25f89e7e00d7c9219a3ec2e0a0b33e49fc552 100644 (file)
 #include <net/act_api.h>
 #include <net/netlink.h>
 
+static void tcf_common_free_rcu(struct rcu_head *head)
+{
+       kfree(container_of(head, struct tcf_common, tcfc_rcu));
+}
+
 void tcf_hash_destroy(struct tcf_common *p, struct tcf_hashinfo *hinfo)
 {
        unsigned int h = tcf_hash(p->tcfc_index, hinfo->hmask);
@@ -38,7 +43,11 @@ void tcf_hash_destroy(struct tcf_common *p, struct tcf_hashinfo *hinfo)
                        write_unlock_bh(hinfo->lock);
                        gen_kill_estimator(&p->tcfc_bstats,
                                           &p->tcfc_rate_est);
-                       kfree(p);
+                       /*
+                        * gen_estimator est_timer() might access p->tcfc_lock
+                        * or bstats, wait a RCU grace period before freeing p
+                        */
+                       call_rcu(&p->tcfc_rcu, tcf_common_free_rcu);
                        return;
                }
        }
index 654f73dff7c1c8d7f9d5fb6541174299c4b32f05..537a48732e9e0b8513dd324801c342ec00cd3820 100644 (file)
@@ -97,6 +97,11 @@ nla_put_failure:
        goto done;
 }
 
+static void tcf_police_free_rcu(struct rcu_head *head)
+{
+       kfree(container_of(head, struct tcf_police, tcf_rcu));
+}
+
 static void tcf_police_destroy(struct tcf_police *p)
 {
        unsigned int h = tcf_hash(p->tcf_index, POL_TAB_MASK);
@@ -113,7 +118,11 @@ static void tcf_police_destroy(struct tcf_police *p)
                                qdisc_put_rtab(p->tcfp_R_tab);
                        if (p->tcfp_P_tab)
                                qdisc_put_rtab(p->tcfp_P_tab);
-                       kfree(p);
+                       /*
+                        * gen_estimator est_timer() might access p->tcf_lock
+                        * or bstats, wait a RCU grace period before freeing p
+                        */
+                       call_rcu(&p->tcf_rcu, tcf_police_free_rcu);
                        return;
                }
        }
@@ -397,6 +406,7 @@ static void __exit
 police_cleanup_module(void)
 {
        tcf_unregister_action(&act_police_ops);
+       rcu_barrier(); /* Wait for completion of call_rcu()'s (tcf_police_free_rcu) */
 }
 
 module_init(police_init_module);