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>
This commit is contained in:
parent
d4fc6dbb5a
commit
5d944c640b
|
@ -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 {
|
||||||
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,
|
||||||
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)
|
||||||
}
|
}
|
||||||
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)
|
||||||
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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue