aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorJohn Hurley <john.hurley@netronome.com>2019-11-02 10:17:47 -0400
committerDavid S. Miller <davem@davemloft.net>2019-11-05 20:47:26 -0500
commit59eb87cb52c9f7164804bc8639c4d03ba9b0c169 (patch)
tree086022aca0f7a739d602c65c661e8d99f45239b3 /net
parent2ef17216d732f40dcd96423384064d542e3ff658 (diff)
net: sched: prevent duplicate flower rules from tcf_proto destroy race
When a new filter is added to cls_api, the function tcf_chain_tp_insert_unique() looks up the protocol/priority/chain to determine if the tcf_proto is duplicated in the chain's hashtable. It then creates a new entry or continues with an existing one. In cls_flower, this allows the function fl_ht_insert_unque to determine if a filter is a duplicate and reject appropriately, meaning that the duplicate will not be passed to drivers via the offload hooks. However, when a tcf_proto is destroyed it is removed from its chain before a hardware remove hook is hit. This can lead to a race whereby the driver has not received the remove message but duplicate flows can be accepted. This, in turn, can lead to the offload driver receiving incorrect duplicate flows and out of order add/delete messages. Prevent duplicates by utilising an approach suggested by Vlad Buslov. A hash table per block stores each unique chain/protocol/prio being destroyed. This entry is only removed when the full destroy (and hardware offload) has completed. If a new flow is being added with the same identiers as a tc_proto being detroyed, then the add request is replayed until the destroy is complete. Fixes: 8b64678e0af8 ("net: sched: refactor tp insert/delete for concurrent execution") Signed-off-by: John Hurley <john.hurley@netronome.com> Signed-off-by: Vlad Buslov <vladbu@mellanox.com> Reviewed-by: Simon Horman <simon.horman@netronome.com> Reported-by: Louis Peens <louis.peens@netronome.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r--net/sched/cls_api.c83
1 files changed, 79 insertions, 4 deletions
diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
index 8717c0b26c90..20d60b8fcb70 100644
--- a/net/sched/cls_api.c
+++ b/net/sched/cls_api.c
@@ -21,6 +21,7 @@
21#include <linux/slab.h> 21#include <linux/slab.h>
22#include <linux/idr.h> 22#include <linux/idr.h>
23#include <linux/rhashtable.h> 23#include <linux/rhashtable.h>
24#include <linux/jhash.h>
24#include <net/net_namespace.h> 25#include <net/net_namespace.h>
25#include <net/sock.h> 26#include <net/sock.h>
26#include <net/netlink.h> 27#include <net/netlink.h>
@@ -47,6 +48,62 @@ static LIST_HEAD(tcf_proto_base);
47/* Protects list of registered TC modules. It is pure SMP lock. */ 48/* Protects list of registered TC modules. It is pure SMP lock. */
48static DEFINE_RWLOCK(cls_mod_lock); 49static DEFINE_RWLOCK(cls_mod_lock);
49 50
51static u32 destroy_obj_hashfn(const struct tcf_proto *tp)
52{
53 return jhash_3words(tp->chain->index, tp->prio,
54 (__force __u32)tp->protocol, 0);
55}
56
57static void tcf_proto_signal_destroying(struct tcf_chain *chain,
58 struct tcf_proto *tp)
59{
60 struct tcf_block *block = chain->block;
61
62 mutex_lock(&block->proto_destroy_lock);
63 hash_add_rcu(block->proto_destroy_ht, &tp->destroy_ht_node,
64 destroy_obj_hashfn(tp));
65 mutex_unlock(&block->proto_destroy_lock);
66}
67
68static bool tcf_proto_cmp(const struct tcf_proto *tp1,
69 const struct tcf_proto *tp2)
70{
71 return tp1->chain->index == tp2->chain->index &&
72 tp1->prio == tp2->prio &&
73 tp1->protocol == tp2->protocol;
74}
75
76static bool tcf_proto_exists_destroying(struct tcf_chain *chain,
77 struct tcf_proto *tp)
78{
79 u32 hash = destroy_obj_hashfn(tp);
80 struct tcf_proto *iter;
81 bool found = false;
82
83 rcu_read_lock();
84 hash_for_each_possible_rcu(chain->block->proto_destroy_ht, iter,
85 destroy_ht_node, hash) {
86 if (tcf_proto_cmp(tp, iter)) {
87 found = true;
88 break;
89 }
90 }
91 rcu_read_unlock();
92
93 return found;
94}
95
96static void
97tcf_proto_signal_destroyed(struct tcf_chain *chain, struct tcf_proto *tp)
98{
99 struct tcf_block *block = chain->block;
100
101 mutex_lock(&block->proto_destroy_lock);
102 if (hash_hashed(&tp->destroy_ht_node))
103 hash_del_rcu(&tp->destroy_ht_node);
104 mutex_unlock(&block->proto_destroy_lock);
105}
106
50/* Find classifier type by string name */ 107/* Find classifier type by string name */
51 108
52static const struct tcf_proto_ops *__tcf_proto_lookup_ops(const char *kind) 109static const struct tcf_proto_ops *__tcf_proto_lookup_ops(const char *kind)
@@ -234,9 +291,11 @@ static void tcf_proto_get(struct tcf_proto *tp)
234static void tcf_chain_put(struct tcf_chain *chain); 291static void tcf_chain_put(struct tcf_chain *chain);
235 292
236static void tcf_proto_destroy(struct tcf_proto *tp, bool rtnl_held, 293static void tcf_proto_destroy(struct tcf_proto *tp, bool rtnl_held,
237 struct netlink_ext_ack *extack) 294 bool sig_destroy, struct netlink_ext_ack *extack)
238{ 295{
239 tp->ops->destroy(tp, rtnl_held, extack); 296 tp->ops->destroy(tp, rtnl_held, extack);
297 if (sig_destroy)
298 tcf_proto_signal_destroyed(tp->chain, tp);
240 tcf_chain_put(tp->chain); 299 tcf_chain_put(tp->chain);
241 module_put(tp->ops->owner); 300 module_put(tp->ops->owner);
242 kfree_rcu(tp, rcu); 301 kfree_rcu(tp, rcu);
@@ -246,7 +305,7 @@ static void tcf_proto_put(struct tcf_proto *tp, bool rtnl_held,
246 struct netlink_ext_ack *extack) 305 struct netlink_ext_ack *extack)
247{ 306{
248 if (refcount_dec_and_test(&tp->refcnt)) 307 if (refcount_dec_and_test(&tp->refcnt))
249 tcf_proto_destroy(tp, rtnl_held, extack); 308 tcf_proto_destroy(tp, rtnl_held, true, extack);
250} 309}
251 310
252static int walker_check_empty(struct tcf_proto *tp, void *fh, 311static int walker_check_empty(struct tcf_proto *tp, void *fh,
@@ -370,6 +429,7 @@ static bool tcf_chain_detach(struct tcf_chain *chain)
370static void tcf_block_destroy(struct tcf_block *block) 429static void tcf_block_destroy(struct tcf_block *block)
371{ 430{
372 mutex_destroy(&block->lock); 431 mutex_destroy(&block->lock);
432 mutex_destroy(&block->proto_destroy_lock);
373 kfree_rcu(block, rcu); 433 kfree_rcu(block, rcu);
374} 434}
375 435
@@ -545,6 +605,12 @@ static void tcf_chain_flush(struct tcf_chain *chain, bool rtnl_held)
545 605
546 mutex_lock(&chain->filter_chain_lock); 606 mutex_lock(&chain->filter_chain_lock);
547 tp = tcf_chain_dereference(chain->filter_chain, chain); 607 tp = tcf_chain_dereference(chain->filter_chain, chain);
608 while (tp) {
609 tp_next = rcu_dereference_protected(tp->next, 1);
610 tcf_proto_signal_destroying(chain, tp);
611 tp = tp_next;
612 }
613 tp = tcf_chain_dereference(chain->filter_chain, chain);
548 RCU_INIT_POINTER(chain->filter_chain, NULL); 614 RCU_INIT_POINTER(chain->filter_chain, NULL);
549 tcf_chain0_head_change(chain, NULL); 615 tcf_chain0_head_change(chain, NULL);
550 chain->flushing = true; 616 chain->flushing = true;
@@ -844,6 +910,7 @@ static struct tcf_block *tcf_block_create(struct net *net, struct Qdisc *q,
844 return ERR_PTR(-ENOMEM); 910 return ERR_PTR(-ENOMEM);
845 } 911 }
846 mutex_init(&block->lock); 912 mutex_init(&block->lock);
913 mutex_init(&block->proto_destroy_lock);
847 init_rwsem(&block->cb_lock); 914 init_rwsem(&block->cb_lock);
848 flow_block_init(&block->flow_block); 915 flow_block_init(&block->flow_block);
849 INIT_LIST_HEAD(&block->chain_list); 916 INIT_LIST_HEAD(&block->chain_list);
@@ -1621,6 +1688,12 @@ static struct tcf_proto *tcf_chain_tp_insert_unique(struct tcf_chain *chain,
1621 1688
1622 mutex_lock(&chain->filter_chain_lock); 1689 mutex_lock(&chain->filter_chain_lock);
1623 1690
1691 if (tcf_proto_exists_destroying(chain, tp_new)) {
1692 mutex_unlock(&chain->filter_chain_lock);
1693 tcf_proto_destroy(tp_new, rtnl_held, false, NULL);
1694 return ERR_PTR(-EAGAIN);
1695 }
1696
1624 tp = tcf_chain_tp_find(chain, &chain_info, 1697 tp = tcf_chain_tp_find(chain, &chain_info,
1625 protocol, prio, false); 1698 protocol, prio, false);
1626 if (!tp) 1699 if (!tp)
@@ -1628,10 +1701,10 @@ static struct tcf_proto *tcf_chain_tp_insert_unique(struct tcf_chain *chain,
1628 mutex_unlock(&chain->filter_chain_lock); 1701 mutex_unlock(&chain->filter_chain_lock);
1629 1702
1630 if (tp) { 1703 if (tp) {
1631 tcf_proto_destroy(tp_new, rtnl_held, NULL); 1704 tcf_proto_destroy(tp_new, rtnl_held, false, NULL);
1632 tp_new = tp; 1705 tp_new = tp;
1633 } else if (err) { 1706 } else if (err) {
1634 tcf_proto_destroy(tp_new, rtnl_held, NULL); 1707 tcf_proto_destroy(tp_new, rtnl_held, false, NULL);
1635 tp_new = ERR_PTR(err); 1708 tp_new = ERR_PTR(err);
1636 } 1709 }
1637 1710
@@ -1669,6 +1742,7 @@ static void tcf_chain_tp_delete_empty(struct tcf_chain *chain,
1669 return; 1742 return;
1670 } 1743 }
1671 1744
1745 tcf_proto_signal_destroying(chain, tp);
1672 next = tcf_chain_dereference(chain_info.next, chain); 1746 next = tcf_chain_dereference(chain_info.next, chain);
1673 if (tp == chain->filter_chain) 1747 if (tp == chain->filter_chain)
1674 tcf_chain0_head_change(chain, next); 1748 tcf_chain0_head_change(chain, next);
@@ -2188,6 +2262,7 @@ static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
2188 err = -EINVAL; 2262 err = -EINVAL;
2189 goto errout_locked; 2263 goto errout_locked;
2190 } else if (t->tcm_handle == 0) { 2264 } else if (t->tcm_handle == 0) {
2265 tcf_proto_signal_destroying(chain, tp);
2191 tcf_chain_tp_remove(chain, &chain_info, tp); 2266 tcf_chain_tp_remove(chain, &chain_info, tp);
2192 mutex_unlock(&chain->filter_chain_lock); 2267 mutex_unlock(&chain->filter_chain_lock);
2193 2268