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 *); | ||||
| }; | ||||
| 
 | ||||
| #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) | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user