gen_estimator: deadlock fix
authorEric Dumazet <eric.dumazet@gmail.com>
Wed, 31 Mar 2010 07:06:04 +0000 (07:06 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 2 Apr 2010 01:38:48 +0000 (18:38 -0700)
One of my test machine got a deadlock during "tc" sessions,
adding/deleting classes & filters, using traffic estimators.

After some analysis, I believe we have a potential use after free case
in est_timer() :

spin_lock(e->stats_lock); << HERE >>
read_lock(&est_lock);
if (e->bstats == NULL)   << TEST >>
goto skip;

Test is done a bit late, because after estimator is killed, and before
rcu grace period elapsed, we might already have freed/reuse memory where
e->stats_locks points to (some qdisc->q.lock)

A possible fix is to respect a rcu grace period at Qdisc dismantle time.

On 64bit, sizeof(struct Qdisc) is exactly 192 bytes. Adding 16 bytes to
it (for struct rcu_head) is a problem because it might change
performance, given QDISC_ALIGNTO is 32 bytes.

This is why I also change QDISC_ALIGNTO to 64 bytes, to satisfy most
current alignment requirements.

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/pkt_sched.h
include/net/sch_generic.h
net/sched/sch_generic.c

index b6cdc33..9d4d87c 100644 (file)
@@ -12,7 +12,7 @@ struct qdisc_walker {
        int     (*fn)(struct Qdisc *, unsigned long cl, struct qdisc_walker *);
 };
 
-#define QDISC_ALIGNTO          32
+#define QDISC_ALIGNTO          64
 #define QDISC_ALIGN(len)       (((len) + QDISC_ALIGNTO-1) & ~(QDISC_ALIGNTO-1))
 
 static inline void *qdisc_priv(struct Qdisc *q)
index 67dc08e..03ca5d8 100644 (file)
@@ -73,6 +73,7 @@ struct Qdisc {
        struct sk_buff_head     q;
        struct gnet_stats_basic_packed bstats;
        struct gnet_stats_queue qstats;
+       struct rcu_head     rcu_head;
 };
 
 struct Qdisc_class_ops {
index 5173c1e..1751325 100644 (file)
@@ -528,7 +528,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue,
        unsigned int size;
        int err = -ENOBUFS;
 
-       /* ensure that the Qdisc and the private data are 32-byte aligned */
+       /* ensure that the Qdisc and the private data are 64-byte aligned */
        size = QDISC_ALIGN(sizeof(*sch));
        size += ops->priv_size + (QDISC_ALIGNTO - 1);
 
@@ -590,6 +590,13 @@ void qdisc_reset(struct Qdisc *qdisc)
 }
 EXPORT_SYMBOL(qdisc_reset);
 
+static void qdisc_rcu_free(struct rcu_head *head)
+{
+       struct Qdisc *qdisc = container_of(head, struct Qdisc, rcu_head);
+
+       kfree((char *) qdisc - qdisc->padded);
+}
+
 void qdisc_destroy(struct Qdisc *qdisc)
 {
        const struct Qdisc_ops  *ops = qdisc->ops;
@@ -613,7 +620,11 @@ void qdisc_destroy(struct Qdisc *qdisc)
        dev_put(qdisc_dev(qdisc));
 
        kfree_skb(qdisc->gso_skb);
-       kfree((char *) qdisc - qdisc->padded);
+       /*
+        * gen_estimator est_timer() might access qdisc->q.lock,
+        * wait a RCU grace period before freeing qdisc.
+        */
+       call_rcu(&qdisc->rcu_head, qdisc_rcu_free);
 }
 EXPORT_SYMBOL(qdisc_destroy);