Subject: Re: [Patch net-next] net_sched: move the empty tp check from ->destroy() to ->delete()

From: Roi Dayan <[email-masked]>
Date: Thu, 24 Nov 2016 10:29:08 +0200

Hi,

I'm testing this patch with KASAN enabled and got into a new kernel 
crash I didn't hit before.

[ 1860.725065] 
==================================================================
[ 1860.733893] BUG: KASAN: use-after-free in 
__netif_receive_skb_core+0x1ebe/0x29a0 at addr ffff880a68b04028
[ 1860.745415] Read of size 8 by task CPU 0/KVM/5334
[ 1860.751368] CPU: 8 PID: 5334 Comm: CPU 0/KVM Tainted: G O    
4.9.0-rc3+ #18
[ 1860.760547] Hardware name: HP ProLiant DL380p Gen8, BIOS P70 07/01/2015
[ 1860.768036] Call Trace:
[ 1860.771307]  [<ffffffffa9b6dc42>] dump_stack+0x63/0x81
[ 1860.777167]  [<ffffffffa95fb751>] kasan_object_err+0x21/0x70
[ 1860.783826]  [<ffffffffa95fb9dd>] kasan_report_error+0x1ed/0x4e0
[ 1860.790640]  [<ffffffffa9b9b841>] ? csum_partial+0x11/0x20
[ 1860.796871]  [<ffffffffaa44a6b9>] ? csum_partial_ext+0x9/0x10
[ 1860.803571]  [<ffffffffaa453155>] ? __skb_checksum+0x115/0x8d0
[ 1860.810370]  [<ffffffffa95fbe81>] __asan_report_load8_noabort+0x61/0x70
[ 1860.818263]  [<ffffffffaa49c3fe>] ? 
__netif_receive_skb_core+0x1ebe/0x29a0
[ 1860.826215]  [<ffffffffaa49c3fe>] __netif_receive_skb_core+0x1ebe/0x29a0
[ 1860.833991]  [<ffffffffaa49a540>] ? netdev_info+0x100/0x100
[ 1860.840529]  [<ffffffffaa671792>] ? udp4_gro_receive+0x802/0x1090
[ 1860.847783]  [<ffffffffa9bb9a08>] ? find_next_bit+0x18/0x20
[ 1860.854126]  [<ffffffffaa49cf04>] __netif_receive_skb+0x24/0x150
[ 1860.861695]  [<ffffffffaa49d0d1>] netif_receive_skb_internal+0xa1/0x1d0
[ 1860.869366]  [<ffffffffaa49d030>] ? __netif_receive_skb+0x150/0x150
[ 1860.876464]  [<ffffffffaa49f7e9>] ? dev_gro_receive+0x969/0x1660
[ 1860.883924]  [<ffffffffaa4a0e1f>] napi_gro_receive+0x1df/0x300
[ 1860.890744]  [<ffffffffc02e885d>] mlx5e_handle_rx_cqe_rep+0x83d/0xd30 
[mlx5_core]

checking with gdb

(gdb) l *(__netif_receive_skb_core+0x1ebe)
0xffffffff8249c3fe is in __netif_receive_skb_core (net/core/dev.c:3937).
3932                    *pt_prev = NULL;
3933            }
3934
3935            qdisc_skb_cb(skb)->pkt_len = skb->len;
3936            skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);
3937            qdisc_bstats_cpu_update(cl->q, skb);
3938
3939            switch (tc_classify(skb, cl, &cl_res, false)) {
3940            case TC_ACT_OK:
3941            case TC_ACT_RECLASSIFY:

Thanks,
Roi

On 24/11/2016 03:58, Cong Wang wrote:
> Roi reported we could have a race condition where in ->classify() path
> we dereference tp->root and meanwhile a parallel ->destroy() makes it
> a NULL.
>
> This is possible because ->destroy() could be called when deleting
> a filter to check if we are the last one in tp, this tp is still
> linked and visible at that time.
>
> The root cause of this problem is the semantic of ->destroy(), it
> does two things (for non-force case):
>
> 1) check if tp is empty
> 2) if tp is empty we could really destroy it
>
> and its caller, if cares, needs to check its return value to see if
> it is really destroyed. Therefore we can't unlink tp unless we know
> it is empty.
>
> As suggested by Daniel, we could actually move the test logic to ->delete()
> so that we can safely unlink tp after ->delete() tells us the last one is
> just deleted and before ->destroy().
>
> What's more, even we unlink it before ->destroy(), it could still have
> readers since we don't wait for a grace period here, we should not modify
> tp->root in ->destroy() either.
>
> Fixes: 1e052be69d04 ("net_sched: destroy proto tp when all filters are gone")
> Reported-by: Roi Dayan <[email-masked]>
> Cc: Daniel Borkmann <[email-masked]>
> Cc: John Fastabend <[email-masked]>
> Signed-off-by: Cong Wang <[email-masked]>
> ---
>   include/net/sch_generic.h |  6 ++--
>   net/sched/cls_api.c       | 18 +++++++-----
>   net/sched/cls_basic.c     | 11 +++-----
>   net/sched/cls_bpf.c       | 11 +++-----
>   net/sched/cls_cgroup.c    | 12 ++------
>   net/sched/cls_flow.c      | 11 +++-----
>   net/sched/cls_flower.c    | 10 ++-----
>   net/sched/cls_fw.c        | 30 +++++++++++---------
>   net/sched/cls_matchall.c  | 10 ++-----
>   net/sched/cls_route.c     | 30 ++++++++++----------
>   net/sched/cls_rsvp.h      | 34 +++++++++++------------
>   net/sched/cls_tcindex.c   | 15 +++++-----
>   net/sched/cls_u32.c       | 71 +++++++++++++++++++++++++++--------------------
>   net/sched/sch_api.c       | 14 ++++------
>   14 files changed, 137 insertions(+), 146 deletions(-)
>
> diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
> index e6aa0a2..27cd1bd 100644
> --- a/include/net/sch_generic.h
> +++ b/include/net/sch_generic.h
> @@ -203,14 +203,14 @@ struct tcf_proto_ops {
>   					    const struct tcf_proto *,
>   					    struct tcf_result *);
>   	int			(*init)(struct tcf_proto*);
> -	bool			(*destroy)(struct tcf_proto*, bool);
> +	void			(*destroy)(struct tcf_proto*);
>   
>   	unsigned long		(*get)(struct tcf_proto*, u32 handle);
>   	int			(*change)(struct net *net, struct sk_buff *,
>   					struct tcf_proto*, unsigned long,
>   					u32 handle, struct nlattr **,
>   					unsigned long *, bool);
> -	int			(*delete)(struct tcf_proto*, unsigned long);
> +	int			(*delete)(struct tcf_proto*, unsigned long, bool*);
>   	void			(*walk)(struct tcf_proto*, struct tcf_walker *arg);
>   
>   	/* rtnetlink specific */
> @@ -405,7 +405,7 @@ struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue,
>   				const struct Qdisc_ops *ops, u32 parentid);
>   void __qdisc_calculate_pkt_len(struct sk_buff *skb,
>   			       const struct qdisc_size_table *stab);
> -bool tcf_destroy(struct tcf_proto *tp, bool force);
> +void tcf_destroy(struct tcf_proto *tp);
>   void tcf_destroy_chain(struct tcf_proto __rcu **fl);
>   int skb_do_redirect(struct sk_buff *);
>   
> diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
> index 8e93d4a..f159aeb 100644
> --- a/net/sched/cls_api.c
> +++ b/net/sched/cls_api.c
> @@ -321,7 +321,7 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n)
>   
>   			tfilter_notify(net, skb, n, tp, fh,
>   				       RTM_DELTFILTER, false);
> -			tcf_destroy(tp, true);
> +			tcf_destroy(tp);
>   			err = 0;
>   			goto errout;
>   		}
> @@ -331,25 +331,29 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n)
>   		    !(n->nlmsg_flags & NLM_F_CREATE))
>   			goto errout;
>   	} else {
> +		bool last;
> +
>   		switch (n->nlmsg_type) {
>   		case RTM_NEWTFILTER:
>   			err = -EEXIST;
>   			if (n->nlmsg_flags & NLM_F_EXCL) {
>   				if (tp_created)
> -					tcf_destroy(tp, true);
> +					tcf_destroy(tp);
>   				goto errout;
>   			}
>   			break;
>   		case RTM_DELTFILTER:
> -			err = tp->ops->delete(tp, fh);
> +			err = tp->ops->delete(tp, fh, &last);
>   			if (err == 0) {
> -				struct tcf_proto *next = rtnl_dereference(tp->next);
> -
>   				tfilter_notify(net, skb, n, tp,
>   					       t->tcm_handle,
>   					       RTM_DELTFILTER, false);
> -				if (tcf_destroy(tp, false))
> +				if (last) {
> +					struct tcf_proto *next = rtnl_dereference(tp->next);
> +
>   					RCU_INIT_POINTER(*back, next);
> +					tcf_destroy(tp);
> +				}
>   			}
>   			goto errout;
>   		case RTM_GETTFILTER:
> @@ -372,7 +376,7 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n)
>   		tfilter_notify(net, skb, n, tp, fh, RTM_NEWTFILTER, false);
>   	} else {
>   		if (tp_created)
> -			tcf_destroy(tp, true);
> +			tcf_destroy(tp);
>   	}
>   
>   errout:
> diff --git a/net/sched/cls_basic.c b/net/sched/cls_basic.c
> index eb219b7..dd63230 100644
> --- a/net/sched/cls_basic.c
> +++ b/net/sched/cls_basic.c
> @@ -96,31 +96,28 @@ static void basic_delete_filter(struct rcu_head *head)
>   	kfree(f);
>   }
>   
> -static bool basic_destroy(struct tcf_proto *tp, bool force)
> +static void basic_destroy(struct tcf_proto *tp)
>   {
>   	struct basic_head *head = rtnl_dereference(tp->root);
>   	struct basic_filter *f, *n;
>   
> -	if (!force && !list_empty(&head->flist))
> -		return false;
> -
>   	list_for_each_entry_safe(f, n, &head->flist, link) {
>   		list_del_rcu(&f->link);
>   		tcf_unbind_filter(tp, &f->res);
>   		call_rcu(&f->rcu, basic_delete_filter);
>   	}
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	kfree_rcu(head, rcu);
> -	return true;
>   }
>   
> -static int basic_delete(struct tcf_proto *tp, unsigned long arg)
> +static int basic_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
> +	struct basic_head *head = rtnl_dereference(tp->root);
>   	struct basic_filter *f = (struct basic_filter *) arg;
>   
>   	list_del_rcu(&f->link);
>   	tcf_unbind_filter(tp, &f->res);
>   	call_rcu(&f->rcu, basic_delete_filter);
> +	*last = list_empty(&head->flist);
>   	return 0;
>   }
>   
> diff --git a/net/sched/cls_bpf.c b/net/sched/cls_bpf.c
> index 52dc85a..770984c0 100644
> --- a/net/sched/cls_bpf.c
> +++ b/net/sched/cls_bpf.c
> @@ -265,26 +265,25 @@ static void __cls_bpf_delete_prog(struct rcu_head *rcu)
>   	cls_bpf_delete_prog(prog->tp, prog);
>   }
>   
> -static int cls_bpf_delete(struct tcf_proto *tp, unsigned long arg)
> +static int cls_bpf_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct cls_bpf_prog *prog = (struct cls_bpf_prog *) arg;
> +	struct cls_bpf_head *head = rtnl_dereference(tp->root);
>   
>   	cls_bpf_stop_offload(tp, prog);
>   	list_del_rcu(&prog->link);
>   	tcf_unbind_filter(tp, &prog->res);
>   	call_rcu(&prog->rcu, __cls_bpf_delete_prog);
> +	*last = list_empty(&head->plist);
>   
>   	return 0;
>   }
>   
> -static bool cls_bpf_destroy(struct tcf_proto *tp, bool force)
> +static void cls_bpf_destroy(struct tcf_proto *tp)
>   {
>   	struct cls_bpf_head *head = rtnl_dereference(tp->root);
>   	struct cls_bpf_prog *prog, *tmp;
>   
> -	if (!force && !list_empty(&head->plist))
> -		return false;
> -
>   	list_for_each_entry_safe(prog, tmp, &head->plist, link) {
>   		cls_bpf_stop_offload(tp, prog);
>   		list_del_rcu(&prog->link);
> @@ -292,9 +291,7 @@ static bool cls_bpf_destroy(struct tcf_proto *tp, bool force)
>   		call_rcu(&prog->rcu, __cls_bpf_delete_prog);
>   	}
>   
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	kfree_rcu(head, rcu);
> -	return true;
>   }
>   
>   static unsigned long cls_bpf_get(struct tcf_proto *tp, u32 handle)
> diff --git a/net/sched/cls_cgroup.c b/net/sched/cls_cgroup.c
> index 85233c47..fa9405e 100644
> --- a/net/sched/cls_cgroup.c
> +++ b/net/sched/cls_cgroup.c
> @@ -131,21 +131,15 @@ static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb,
>   	return err;
>   }
>   
> -static bool cls_cgroup_destroy(struct tcf_proto *tp, bool force)
> +static void cls_cgroup_destroy(struct tcf_proto *tp)
>   {
>   	struct cls_cgroup_head *head = rtnl_dereference(tp->root);
>   
> -	if (!force)
> -		return false;
> -
> -	if (head) {
> -		RCU_INIT_POINTER(tp->root, NULL);
> +	if (head)
>   		call_rcu(&head->rcu, cls_cgroup_destroy_rcu);
> -	}
> -	return true;
>   }
>   
> -static int cls_cgroup_delete(struct tcf_proto *tp, unsigned long arg)
> +static int cls_cgroup_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	return -EOPNOTSUPP;
>   }
> diff --git a/net/sched/cls_flow.c b/net/sched/cls_flow.c
> index e396723..ea2be75 100644
> --- a/net/sched/cls_flow.c
> +++ b/net/sched/cls_flow.c
> @@ -563,12 +563,14 @@ static int flow_change(struct net *net, struct sk_buff *in_skb,
>   	return err;
>   }
>   
> -static int flow_delete(struct tcf_proto *tp, unsigned long arg)
> +static int flow_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
> +	struct flow_head *head = rtnl_dereference(tp->root);
>   	struct flow_filter *f = (struct flow_filter *)arg;
>   
>   	list_del_rcu(&f->list);
>   	call_rcu(&f->rcu, flow_destroy_filter);
> +	*last = list_empty(&head->filters);
>   	return 0;
>   }
>   
> @@ -584,21 +586,16 @@ static int flow_init(struct tcf_proto *tp)
>   	return 0;
>   }
>   
> -static bool flow_destroy(struct tcf_proto *tp, bool force)
> +static void flow_destroy(struct tcf_proto *tp)
>   {
>   	struct flow_head *head = rtnl_dereference(tp->root);
>   	struct flow_filter *f, *next;
>   
> -	if (!force && !list_empty(&head->filters))
> -		return false;
> -
>   	list_for_each_entry_safe(f, next, &head->filters, list) {
>   		list_del_rcu(&f->list);
>   		call_rcu(&f->rcu, flow_destroy_filter);
>   	}
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	kfree_rcu(head, rcu);
> -	return true;
>   }
>   
>   static unsigned long flow_get(struct tcf_proto *tp, u32 handle)
> diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c
> index e8dd09a..495d63224 100644
> --- a/net/sched/cls_flower.c
> +++ b/net/sched/cls_flower.c
> @@ -280,21 +280,16 @@ static void __fl_delete(struct tcf_proto *tp, struct cls_fl_filter *f)
>   	call_rcu(&f->rcu, fl_destroy_filter);
>   }
>   
> -static bool fl_destroy(struct tcf_proto *tp, bool force)
> +static void fl_destroy(struct tcf_proto *tp)
>   {
>   	struct cls_fl_head *head = rtnl_dereference(tp->root);
>   	struct cls_fl_filter *f, *next;
>   
> -	if (!force && !list_empty(&head->filters))
> -		return false;
> -
>   	list_for_each_entry_safe(f, next, &head->filters, list)
>   		__fl_delete(tp, f);
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	if (head->mask_assigned)
>   		rhashtable_destroy(&head->ht);
>   	kfree_rcu(head, rcu);
> -	return true;
>   }
>   
>   static unsigned long fl_get(struct tcf_proto *tp, u32 handle)
> @@ -777,7 +772,7 @@ static int fl_change(struct net *net, struct sk_buff *in_skb,
>   	return err;
>   }
>   
> -static int fl_delete(struct tcf_proto *tp, unsigned long arg)
> +static int fl_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct cls_fl_head *head = rtnl_dereference(tp->root);
>   	struct cls_fl_filter *f = (struct cls_fl_filter *) arg;
> @@ -785,6 +780,7 @@ static int fl_delete(struct tcf_proto *tp, unsigned long arg)
>   	rhashtable_remove_fast(&head->ht, &f->ht_node,
>   			       head->ht_params);
>   	__fl_delete(tp, f);
> +	*last = list_empty(&head->filters);
>   	return 0;
>   }
>   
> diff --git a/net/sched/cls_fw.c b/net/sched/cls_fw.c
> index 9dc63d5..bc8ceb7 100644
> --- a/net/sched/cls_fw.c
> +++ b/net/sched/cls_fw.c
> @@ -127,20 +127,14 @@ static void fw_delete_filter(struct rcu_head *head)
>   	kfree(f);
>   }
>   
> -static bool fw_destroy(struct tcf_proto *tp, bool force)
> +static void fw_destroy(struct tcf_proto *tp)
>   {
>   	struct fw_head *head = rtnl_dereference(tp->root);
>   	struct fw_filter *f;
>   	int h;
>   
>   	if (head == NULL)
> -		return true;
> -
> -	if (!force) {
> -		for (h = 0; h < HTSIZE; h++)
> -			if (rcu_access_pointer(head->ht[h]))
> -				return false;
> -	}
> +		return;
>   
>   	for (h = 0; h < HTSIZE; h++) {
>   		while ((f = rtnl_dereference(head->ht[h])) != NULL) {
> @@ -150,17 +144,17 @@ static bool fw_destroy(struct tcf_proto *tp, bool force)
>   			call_rcu(&f->rcu, fw_delete_filter);
>   		}
>   	}
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	kfree_rcu(head, rcu);
> -	return true;
>   }
>   
> -static int fw_delete(struct tcf_proto *tp, unsigned long arg)
> +static int fw_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct fw_head *head = rtnl_dereference(tp->root);
>   	struct fw_filter *f = (struct fw_filter *)arg;
>   	struct fw_filter __rcu **fp;
>   	struct fw_filter *pfp;
> +	int ret = -EINVAL;
> +	int h;
>   
>   	if (head == NULL || f == NULL)
>   		goto out;
> @@ -173,11 +167,21 @@ static int fw_delete(struct tcf_proto *tp, unsigned long arg)
>   			RCU_INIT_POINTER(*fp, rtnl_dereference(f->next));
>   			tcf_unbind_filter(tp, &f->res);
>   			call_rcu(&f->rcu, fw_delete_filter);
> -			return 0;
> +			ret = 0;
> +			break;
>   		}
>   	}
> +
> +	*last = true;
> +	for (h = 0; h < HTSIZE; h++) {
> +		if (rcu_access_pointer(head->ht[h])) {
> +			*last = false;
> +			break;
> +		}
> +	}
> +
>   out:
> -	return -EINVAL;
> +	return ret;
>   }
>   
>   static const struct nla_policy fw_policy[TCA_FW_MAX + 1] = {
> diff --git a/net/sched/cls_matchall.c b/net/sched/cls_matchall.c
> index 25927b6..7d54805 100644
> --- a/net/sched/cls_matchall.c
> +++ b/net/sched/cls_matchall.c
> @@ -99,24 +99,19 @@ static void mall_destroy_hw_filter(struct tcf_proto *tp,
>   					     &offload);
>   }
>   
> -static bool mall_destroy(struct tcf_proto *tp, bool force)
> +static void mall_destroy(struct tcf_proto *tp)
>   {
>   	struct cls_mall_head *head = rtnl_dereference(tp->root);
>   	struct net_device *dev = tp->q->dev_queue->dev;
>   	struct cls_mall_filter *f = head->filter;
>   
> -	if (!force && f)
> -		return false;
> -
>   	if (f) {
>   		if (tc_should_offload(dev, tp, f->flags))
>   			mall_destroy_hw_filter(tp, f, (unsigned long) f);
>   
>   		call_rcu(&f->rcu, mall_destroy_filter);
>   	}
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	kfree_rcu(head, rcu);
> -	return true;
>   }
>   
>   static unsigned long mall_get(struct tcf_proto *tp, u32 handle)
> @@ -225,7 +220,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
>   	return err;
>   }
>   
> -static int mall_delete(struct tcf_proto *tp, unsigned long arg)
> +static int mall_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct cls_mall_head *head = rtnl_dereference(tp->root);
>   	struct cls_mall_filter *f = (struct cls_mall_filter *) arg;
> @@ -237,6 +232,7 @@ static int mall_delete(struct tcf_proto *tp, unsigned long arg)
>   	RCU_INIT_POINTER(head->filter, NULL);
>   	tcf_unbind_filter(tp, &f->res);
>   	call_rcu(&f->rcu, mall_destroy_filter);
> +	*last = true;
>   	return 0;
>   }
>   
> diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c
> index 455fc8f..1a38e41 100644
> --- a/net/sched/cls_route.c
> +++ b/net/sched/cls_route.c
> @@ -276,20 +276,13 @@ static void route4_delete_filter(struct rcu_head *head)
>   	kfree(f);
>   }
>   
> -static bool route4_destroy(struct tcf_proto *tp, bool force)
> +static void route4_destroy(struct tcf_proto *tp)
>   {
>   	struct route4_head *head = rtnl_dereference(tp->root);
>   	int h1, h2;
>   
>   	if (head == NULL)
> -		return true;
> -
> -	if (!force) {
> -		for (h1 = 0; h1 <= 256; h1++) {
> -			if (rcu_access_pointer(head->table[h1]))
> -				return false;
> -		}
> -	}
> +		return;
>   
>   	for (h1 = 0; h1 <= 256; h1++) {
>   		struct route4_bucket *b;
> @@ -312,12 +305,10 @@ static bool route4_destroy(struct tcf_proto *tp, bool force)
>   			kfree_rcu(b, rcu);
>   		}
>   	}
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	kfree_rcu(head, rcu);
> -	return true;
>   }
>   
> -static int route4_delete(struct tcf_proto *tp, unsigned long arg)
> +static int route4_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct route4_head *head = rtnl_dereference(tp->root);
>   	struct route4_filter *f = (struct route4_filter *)arg;
> @@ -325,7 +316,7 @@ static int route4_delete(struct tcf_proto *tp, unsigned long arg)
>   	struct route4_filter *nf;
>   	struct route4_bucket *b;
>   	unsigned int h = 0;
> -	int i;
> +	int i, h1;
>   
>   	if (!head || !f)
>   		return -EINVAL;
> @@ -356,16 +347,25 @@ static int route4_delete(struct tcf_proto *tp, unsigned long arg)
>   
>   				rt = rtnl_dereference(b->ht[i]);
>   				if (rt)
> -					return 0;
> +					goto out;
>   			}
>   
>   			/* OK, session has no flows */
>   			RCU_INIT_POINTER(head->table[to_hash(h)], NULL);
>   			kfree_rcu(b, rcu);
> +			break;
> +		}
> +	}
>   
> -			return 0;
> +out:
> +	*last = true;
> +	for (h1 = 0; h1 <= 256; h1++) {
> +		if (rcu_access_pointer(head->table[h1])) {
> +			*last = false;
> +			break;
>   		}
>   	}
> +
>   	return 0;
>   }
>   
> diff --git a/net/sched/cls_rsvp.h b/net/sched/cls_rsvp.h
> index 4f05a19..e8ba81a 100644
> --- a/net/sched/cls_rsvp.h
> +++ b/net/sched/cls_rsvp.h
> @@ -301,22 +301,13 @@ static void rsvp_delete_filter(struct tcf_proto *tp, struct rsvp_filter *f)
>   	call_rcu(&f->rcu, rsvp_delete_filter_rcu);
>   }
>   
> -static bool rsvp_destroy(struct tcf_proto *tp, bool force)
> +static void rsvp_destroy(struct tcf_proto *tp)
>   {
>   	struct rsvp_head *data = rtnl_dereference(tp->root);
>   	int h1, h2;
>   
>   	if (data == NULL)
> -		return true;
> -
> -	if (!force) {
> -		for (h1 = 0; h1 < 256; h1++) {
> -			if (rcu_access_pointer(data->ht[h1]))
> -				return false;
> -		}
> -	}
> -
> -	RCU_INIT_POINTER(tp->root, NULL);
> +		return;
>   
>   	for (h1 = 0; h1 < 256; h1++) {
>   		struct rsvp_session *s;
> @@ -336,10 +327,9 @@ static bool rsvp_destroy(struct tcf_proto *tp, bool force)
>   		}
>   	}
>   	kfree_rcu(data, rcu);
> -	return true;
>   }
>   
> -static int rsvp_delete(struct tcf_proto *tp, unsigned long arg)
> +static int rsvp_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct rsvp_head *head = rtnl_dereference(tp->root);
>   	struct rsvp_filter *nfp, *f = (struct rsvp_filter *)arg;
> @@ -347,7 +337,7 @@ static int rsvp_delete(struct tcf_proto *tp, unsigned long arg)
>   	unsigned int h = f->handle;
>   	struct rsvp_session __rcu **sp;
>   	struct rsvp_session *nsp, *s = f->sess;
> -	int i;
> +	int i, h1;
>   
>   	fp = &s->ht[(h >> 8) & 0xFF];
>   	for (nfp = rtnl_dereference(*fp); nfp;
> @@ -360,7 +350,7 @@ static int rsvp_delete(struct tcf_proto *tp, unsigned long arg)
>   
>   			for (i = 0; i <= 16; i++)
>   				if (s->ht[i])
> -					return 0;
> +					goto out;
>   
>   			/* OK, session has no flows */
>   			sp = &head->ht[h & 0xFF];
> @@ -369,13 +359,23 @@ static int rsvp_delete(struct tcf_proto *tp, unsigned long arg)
>   				if (nsp == s) {
>   					RCU_INIT_POINTER(*sp, s->next);
>   					kfree_rcu(s, rcu);
> -					return 0;
> +					goto out;
>   				}
>   			}
>   
> -			return 0;
> +			break;
>   		}
>   	}
> +
> +out:
> +	*last = true;
> +	for (h1 = 0; h1 < 256; h1++) {
> +		if (rcu_access_pointer(head->ht[h1])) {
> +			*last = false;
> +			break;
> +		}
> +	}
> +
>   	return 0;
>   }
>   
> diff --git a/net/sched/cls_tcindex.c b/net/sched/cls_tcindex.c
> index 96144bd..9149a03 100644
> --- a/net/sched/cls_tcindex.c
> +++ b/net/sched/cls_tcindex.c
> @@ -150,7 +150,7 @@ static void tcindex_destroy_fexts(struct rcu_head *head)
>   	kfree(f);
>   }
>   
> -static int tcindex_delete(struct tcf_proto *tp, unsigned long arg)
> +static int tcindex_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct tcindex_data *p = rtnl_dereference(tp->root);
>   	struct tcindex_filter_result *r = (struct tcindex_filter_result *) arg;
> @@ -186,6 +186,8 @@ static int tcindex_delete(struct tcf_proto *tp, unsigned long arg)
>   		call_rcu(&f->rcu, tcindex_destroy_fexts);
>   	else
>   		call_rcu(&r->rcu, tcindex_destroy_rexts);
> +
> +	*last = false;
>   	return 0;
>   }
>   
> @@ -193,7 +195,9 @@ static int tcindex_destroy_element(struct tcf_proto *tp,
>   				   unsigned long arg,
>   				   struct tcf_walker *walker)
>   {
> -	return tcindex_delete(tp, arg);
> +	bool last;
> +
> +	return tcindex_delete(tp, arg, &last);
>   }
>   
>   static void __tcindex_destroy(struct rcu_head *head)
> @@ -529,23 +533,18 @@ static void tcindex_walk(struct tcf_proto *tp, struct tcf_walker *walker)
>   	}
>   }
>   
> -static bool tcindex_destroy(struct tcf_proto *tp, bool force)
> +static void tcindex_destroy(struct tcf_proto *tp)
>   {
>   	struct tcindex_data *p = rtnl_dereference(tp->root);
>   	struct tcf_walker walker;
>   
> -	if (!force)
> -		return false;
> -
>   	pr_debug("tcindex_destroy(tp %p),p %p\n", tp, p);
>   	walker.count = 0;
>   	walker.skip = 0;
>   	walker.fn = tcindex_destroy_element;
>   	tcindex_walk(tp, &walker);
>   
> -	RCU_INIT_POINTER(tp->root, NULL);
>   	call_rcu(&p->rcu, __tcindex_destroy);
> -	return true;
>   }
>   
>   
> diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c
> index ae83c3ae..787573b 100644
> --- a/net/sched/cls_u32.c
> +++ b/net/sched/cls_u32.c
> @@ -582,37 +582,13 @@ static bool ht_empty(struct tc_u_hnode *ht)
>   	return true;
>   }
>   
> -static bool u32_destroy(struct tcf_proto *tp, bool force)
> +static void u32_destroy(struct tcf_proto *tp)
>   {
>   	struct tc_u_common *tp_c = tp->data;
>   	struct tc_u_hnode *root_ht = rtnl_dereference(tp->root);
>   
>   	WARN_ON(root_ht == NULL);
>   
> -	if (!force) {
> -		if (root_ht) {
> -			if (root_ht->refcnt > 1)
> -				return false;
> -			if (root_ht->refcnt == 1) {
> -				if (!ht_empty(root_ht))
> -					return false;
> -			}
> -		}
> -
> -		if (tp_c->refcnt > 1)
> -			return false;
> -
> -		if (tp_c->refcnt == 1) {
> -			struct tc_u_hnode *ht;
> -
> -			for (ht = rtnl_dereference(tp_c->hlist);
> -			     ht;
> -			     ht = rtnl_dereference(ht->next))
> -				if (!ht_empty(ht))
> -					return false;
> -		}
> -	}
> -
>   	if (root_ht && --root_ht->refcnt == 0)
>   		u32_destroy_hnode(tp, root_ht);
>   
> @@ -637,20 +613,22 @@ static bool u32_destroy(struct tcf_proto *tp, bool force)
>   	}
>   
>   	tp->data = NULL;
> -	return true;
>   }
>   
> -static int u32_delete(struct tcf_proto *tp, unsigned long arg)
> +static int u32_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
>   {
>   	struct tc_u_hnode *ht = (struct tc_u_hnode *)arg;
>   	struct tc_u_hnode *root_ht = rtnl_dereference(tp->root);
> +	struct tc_u_common *tp_c = tp->data;
> +	int ret = 0;
>   
>   	if (ht == NULL)
> -		return 0;
> +		goto out;
>   
>   	if (TC_U32_KEY(ht->handle)) {
>   		u32_remove_hw_knode(tp, ht->handle);
> -		return u32_delete_key(tp, (struct tc_u_knode *)ht);
> +		ret = u32_delete_key(tp, (struct tc_u_knode *)ht);
> +		goto out;
>   	}
>   
>   	if (root_ht == ht)
> @@ -663,7 +641,40 @@ static int u32_delete(struct tcf_proto *tp, unsigned long arg)
>   		return -EBUSY;
>   	}
>   
> -	return 0;
> +out:
> +	*last = true;
> +	if (root_ht) {
> +		if (root_ht->refcnt > 1) {
> +			*last = false;
> +			goto ret;
> +		}
> +		if (root_ht->refcnt == 1) {
> +			if (!ht_empty(root_ht)) {
> +				*last = false;
> +				goto ret;
> +			}
> +		}
> +	}
> +
> +	if (tp_c->refcnt > 1) {
> +		*last = false;
> +		goto ret;
> +	}
> +
> +	if (tp_c->refcnt == 1) {
> +		struct tc_u_hnode *ht;
> +
> +		for (ht = rtnl_dereference(tp_c->hlist);
> +		     ht;
> +		     ht = rtnl_dereference(ht->next))
> +			if (!ht_empty(ht)) {
> +				*last = false;
> +				break;
> +			}
> +	}
> +
> +ret:
> +	return ret;
>   }
>   
>   #define NR_U32_NODE (1<<12)
> diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
> index f337f1b..7fc48b3 100644
> --- a/net/sched/sch_api.c
> +++ b/net/sched/sch_api.c
> @@ -1899,15 +1899,11 @@ int tc_classify(struct sk_buff *skb, const struct tcf_proto *tp,
>   }
>   EXPORT_SYMBOL(tc_classify);
>   
> -bool tcf_destroy(struct tcf_proto *tp, bool force)
> +void tcf_destroy(struct tcf_proto *tp)
>   {
> -	if (tp->ops->destroy(tp, force)) {
> -		module_put(tp->ops->owner);
> -		kfree_rcu(tp, rcu);
> -		return true;
> -	}
> -
> -	return false;
> +	tp->ops->destroy(tp);
> +	module_put(tp->ops->owner);
> +	kfree_rcu(tp, rcu);
>   }
>   
>   void tcf_destroy_chain(struct tcf_proto __rcu **fl)
> @@ -1916,7 +1912,7 @@ void tcf_destroy_chain(struct tcf_proto __rcu **fl)
>   
>   	while ((tp = rtnl_dereference(*fl)) != NULL) {
>   		RCU_INIT_POINTER(*fl, tp->next);
> -		tcf_destroy(tp, true);
> +		tcf_destroy(tp);
>   	}
>   }
>   EXPORT_SYMBOL(tcf_destroy_chain);