diff options
author | Eric Dumazet <eric.dumazet@gmail.com> | 2010-03-31 03:06:04 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-04-01 21:38:48 -0400 |
commit | 5d944c640b4ae5f37c537acf491c2f0eb89fa0d6 (patch) | |
tree | 9195a0135ec21c1c06ff4d46e8cf7924e2c364c0 | |
parent | d4fc6dbb5ae51430e35b2005f6d68938861f8d8b (diff) |
gen_estimator: deadlock fix
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>
-rw-r--r-- | include/net/pkt_sched.h | 2 | ||||
-rw-r--r-- | include/net/sch_generic.h | 1 | ||||
-rw-r--r-- | net/sched/sch_generic.c | 15 |
3 files changed, 15 insertions, 3 deletions
diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h index b6cdc33b39c1..9d4d87cc970e 100644 --- a/include/net/pkt_sched.h +++ b/include/net/pkt_sched.h | |||
@@ -12,7 +12,7 @@ struct qdisc_walker { | |||
12 | int (*fn)(struct Qdisc *, unsigned long cl, struct qdisc_walker *); | 12 | int (*fn)(struct Qdisc *, unsigned long cl, struct qdisc_walker *); |
13 | }; | 13 | }; |
14 | 14 | ||
15 | #define QDISC_ALIGNTO 32 | 15 | #define QDISC_ALIGNTO 64 |
16 | #define QDISC_ALIGN(len) (((len) + QDISC_ALIGNTO-1) & ~(QDISC_ALIGNTO-1)) | 16 | #define QDISC_ALIGN(len) (((len) + QDISC_ALIGNTO-1) & ~(QDISC_ALIGNTO-1)) |
17 | 17 | ||
18 | static inline void *qdisc_priv(struct Qdisc *q) | 18 | static inline void *qdisc_priv(struct Qdisc *q) |
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 67dc08eaaa45..03ca5d826757 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h | |||
@@ -73,6 +73,7 @@ struct Qdisc { | |||
73 | struct sk_buff_head q; | 73 | struct sk_buff_head q; |
74 | struct gnet_stats_basic_packed bstats; | 74 | struct gnet_stats_basic_packed bstats; |
75 | struct gnet_stats_queue qstats; | 75 | struct gnet_stats_queue qstats; |
76 | struct rcu_head rcu_head; | ||
76 | }; | 77 | }; |
77 | 78 | ||
78 | struct Qdisc_class_ops { | 79 | struct Qdisc_class_ops { |
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 5173c1e1b19c..17513252e83f 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c | |||
@@ -528,7 +528,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, | |||
528 | unsigned int size; | 528 | unsigned int size; |
529 | int err = -ENOBUFS; | 529 | int err = -ENOBUFS; |
530 | 530 | ||
531 | /* ensure that the Qdisc and the private data are 32-byte aligned */ | 531 | /* ensure that the Qdisc and the private data are 64-byte aligned */ |
532 | size = QDISC_ALIGN(sizeof(*sch)); | 532 | size = QDISC_ALIGN(sizeof(*sch)); |
533 | size += ops->priv_size + (QDISC_ALIGNTO - 1); | 533 | size += ops->priv_size + (QDISC_ALIGNTO - 1); |
534 | 534 | ||
@@ -590,6 +590,13 @@ void qdisc_reset(struct Qdisc *qdisc) | |||
590 | } | 590 | } |
591 | EXPORT_SYMBOL(qdisc_reset); | 591 | EXPORT_SYMBOL(qdisc_reset); |
592 | 592 | ||
593 | static void qdisc_rcu_free(struct rcu_head *head) | ||
594 | { | ||
595 | struct Qdisc *qdisc = container_of(head, struct Qdisc, rcu_head); | ||
596 | |||
597 | kfree((char *) qdisc - qdisc->padded); | ||
598 | } | ||
599 | |||
593 | void qdisc_destroy(struct Qdisc *qdisc) | 600 | void qdisc_destroy(struct Qdisc *qdisc) |
594 | { | 601 | { |
595 | const struct Qdisc_ops *ops = qdisc->ops; | 602 | const struct Qdisc_ops *ops = qdisc->ops; |
@@ -613,7 +620,11 @@ void qdisc_destroy(struct Qdisc *qdisc) | |||
613 | dev_put(qdisc_dev(qdisc)); | 620 | dev_put(qdisc_dev(qdisc)); |
614 | 621 | ||
615 | kfree_skb(qdisc->gso_skb); | 622 | kfree_skb(qdisc->gso_skb); |
616 | kfree((char *) qdisc - qdisc->padded); | 623 | /* |
624 | * gen_estimator est_timer() might access qdisc->q.lock, | ||
625 | * wait a RCU grace period before freeing qdisc. | ||
626 | */ | ||
627 | call_rcu(&qdisc->rcu_head, qdisc_rcu_free); | ||
617 | } | 628 | } |
618 | EXPORT_SYMBOL(qdisc_destroy); | 629 | EXPORT_SYMBOL(qdisc_destroy); |
619 | 630 | ||