Commit 5d944c64 authored by Eric Dumazet's avatar Eric Dumazet Committed by David S. Miller

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: default avatarEric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent d4fc6dbb
...@@ -12,7 +12,7 @@ struct qdisc_walker { ...@@ -12,7 +12,7 @@ struct qdisc_walker {
int (*fn)(struct Qdisc *, unsigned long cl, 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)) #define QDISC_ALIGN(len) (((len) + QDISC_ALIGNTO-1) & ~(QDISC_ALIGNTO-1))
static inline void *qdisc_priv(struct Qdisc *q) static inline void *qdisc_priv(struct Qdisc *q)
......
...@@ -73,6 +73,7 @@ struct Qdisc { ...@@ -73,6 +73,7 @@ struct Qdisc {
struct sk_buff_head q; struct sk_buff_head q;
struct gnet_stats_basic_packed bstats; struct gnet_stats_basic_packed bstats;
struct gnet_stats_queue qstats; struct gnet_stats_queue qstats;
struct rcu_head rcu_head;
}; };
struct Qdisc_class_ops { struct Qdisc_class_ops {
......
...@@ -528,7 +528,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, ...@@ -528,7 +528,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue,
unsigned int size; unsigned int size;
int err = -ENOBUFS; 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 = QDISC_ALIGN(sizeof(*sch));
size += ops->priv_size + (QDISC_ALIGNTO - 1); size += ops->priv_size + (QDISC_ALIGNTO - 1);
...@@ -590,6 +590,13 @@ void qdisc_reset(struct Qdisc *qdisc) ...@@ -590,6 +590,13 @@ void qdisc_reset(struct Qdisc *qdisc)
} }
EXPORT_SYMBOL(qdisc_reset); 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) void qdisc_destroy(struct Qdisc *qdisc)
{ {
const struct Qdisc_ops *ops = qdisc->ops; const struct Qdisc_ops *ops = qdisc->ops;
...@@ -613,7 +620,11 @@ void qdisc_destroy(struct Qdisc *qdisc) ...@@ -613,7 +620,11 @@ void qdisc_destroy(struct Qdisc *qdisc)
dev_put(qdisc_dev(qdisc)); dev_put(qdisc_dev(qdisc));
kfree_skb(qdisc->gso_skb); 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); EXPORT_SYMBOL(qdisc_destroy);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment