summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorCong Wang <xiyou.wangcong@gmail.com>2017-09-11 19:33:31 -0400
committerDavid S. Miller <davem@davemloft.net>2017-09-12 23:41:02 -0400
commite2ef75445340ca7ec2c4558f84ae6c8c5d650fc8 (patch)
tree7a56e93516b7ddd563fcfaadf508c3f0cc4783cb /net
parentd7fb60b9cafb982cb2e46a267646a8dfd4f2e5da (diff)
net_sched: fix reference counting of tc filter chain
This patch fixes the following ugliness of tc filter chain refcnt: a) tp proto should hold a refcnt to the chain too. This significantly simplifies the logic. b) Chain 0 is no longer special, it is created with refcnt=1 like any other chains. All the ugliness in tcf_chain_put() can be gone! c) No need to handle the flushing oddly, because block still holds chain 0, it can not be released, this guarantees block is the last user. d) The race condition with RCU callbacks is easier to handle with just a rcu_barrier(). Much easier to understand, nothing to hide. Thanks to the previous patch. Please see also the comments in code. e) Make the code understandable by humans, much less error-prone. Fixes: 744a4cf63e52 ("net: sched: fix use after free when tcf_chain_destroy is called multiple times") Fixes: 5bc1701881e3 ("net: sched: introduce multichain support for filters") Cc: Jiri Pirko <jiri@mellanox.com> Cc: Jamal Hadi Salim <jhs@mojatatu.com> Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com> Acked-by: Jiri Pirko <jiri@mellanox.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r--net/sched/cls_api.c47
1 files changed, 26 insertions, 21 deletions
diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
index c743f03cfebd..d29e79d98a69 100644
--- a/net/sched/cls_api.c
+++ b/net/sched/cls_api.c
@@ -182,7 +182,7 @@ static struct tcf_chain *tcf_chain_create(struct tcf_block *block,
182 list_add_tail(&chain->list, &block->chain_list); 182 list_add_tail(&chain->list, &block->chain_list);
183 chain->block = block; 183 chain->block = block;
184 chain->index = chain_index; 184 chain->index = chain_index;
185 chain->refcnt = 0; 185 chain->refcnt = 1;
186 return chain; 186 return chain;
187} 187}
188 188
@@ -194,21 +194,20 @@ static void tcf_chain_flush(struct tcf_chain *chain)
194 RCU_INIT_POINTER(*chain->p_filter_chain, NULL); 194 RCU_INIT_POINTER(*chain->p_filter_chain, NULL);
195 while ((tp = rtnl_dereference(chain->filter_chain)) != NULL) { 195 while ((tp = rtnl_dereference(chain->filter_chain)) != NULL) {
196 RCU_INIT_POINTER(chain->filter_chain, tp->next); 196 RCU_INIT_POINTER(chain->filter_chain, tp->next);
197 tcf_chain_put(chain);
197 tcf_proto_destroy(tp); 198 tcf_proto_destroy(tp);
198 } 199 }
199} 200}
200 201
201static void tcf_chain_destroy(struct tcf_chain *chain) 202static void tcf_chain_destroy(struct tcf_chain *chain)
202{ 203{
203 /* May be already removed from the list by the previous call. */ 204 list_del(&chain->list);
204 if (!list_empty(&chain->list)) 205 kfree(chain);
205 list_del_init(&chain->list); 206}
206 207
207 /* There might still be a reference held when we got here from 208static void tcf_chain_hold(struct tcf_chain *chain)
208 * tcf_block_put. Wait for the user to drop reference before free. 209{
209 */ 210 ++chain->refcnt;
210 if (!chain->refcnt)
211 kfree(chain);
212} 211}
213 212
214struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index, 213struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
@@ -217,24 +216,19 @@ struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
217 struct tcf_chain *chain; 216 struct tcf_chain *chain;
218 217
219 list_for_each_entry(chain, &block->chain_list, list) { 218 list_for_each_entry(chain, &block->chain_list, list) {
220 if (chain->index == chain_index) 219 if (chain->index == chain_index) {
221 goto incref; 220 tcf_chain_hold(chain);
221 return chain;
222 }
222 } 223 }
223 chain = create ? tcf_chain_create(block, chain_index) : NULL;
224 224
225incref: 225 return create ? tcf_chain_create(block, chain_index) : NULL;
226 if (chain)
227 chain->refcnt++;
228 return chain;
229} 226}
230EXPORT_SYMBOL(tcf_chain_get); 227EXPORT_SYMBOL(tcf_chain_get);
231 228
232void tcf_chain_put(struct tcf_chain *chain) 229void tcf_chain_put(struct tcf_chain *chain)
233{ 230{
234 /* Destroy unused chain, with exception of chain 0, which is the 231 if (--chain->refcnt == 0)
235 * default one and has to be always present.
236 */
237 if (--chain->refcnt == 0 && !chain->filter_chain && chain->index != 0)
238 tcf_chain_destroy(chain); 232 tcf_chain_destroy(chain);
239} 233}
240EXPORT_SYMBOL(tcf_chain_put); 234EXPORT_SYMBOL(tcf_chain_put);
@@ -279,10 +273,19 @@ void tcf_block_put(struct tcf_block *block)
279 if (!block) 273 if (!block)
280 return; 274 return;
281 275
276 /* XXX: Standalone actions are not allowed to jump to any chain, and
277 * bound actions should be all removed after flushing. However,
278 * filters are destroyed in RCU callbacks, we have to flush and wait
279 * for them inside the loop, otherwise we race with RCU callbacks on
280 * this list.
281 */
282 list_for_each_entry_safe(chain, tmp, &block->chain_list, list) { 282 list_for_each_entry_safe(chain, tmp, &block->chain_list, list) {
283 tcf_chain_flush(chain); 283 tcf_chain_flush(chain);
284 tcf_chain_destroy(chain); 284 rcu_barrier();
285 } 285 }
286
287 list_for_each_entry_safe(chain, tmp, &block->chain_list, list)
288 tcf_chain_put(chain);
286 kfree(block); 289 kfree(block);
287} 290}
288EXPORT_SYMBOL(tcf_block_put); 291EXPORT_SYMBOL(tcf_block_put);
@@ -360,6 +363,7 @@ static void tcf_chain_tp_insert(struct tcf_chain *chain,
360 rcu_assign_pointer(*chain->p_filter_chain, tp); 363 rcu_assign_pointer(*chain->p_filter_chain, tp);
361 RCU_INIT_POINTER(tp->next, tcf_chain_tp_prev(chain_info)); 364 RCU_INIT_POINTER(tp->next, tcf_chain_tp_prev(chain_info));
362 rcu_assign_pointer(*chain_info->pprev, tp); 365 rcu_assign_pointer(*chain_info->pprev, tp);
366 tcf_chain_hold(chain);
363} 367}
364 368
365static void tcf_chain_tp_remove(struct tcf_chain *chain, 369static void tcf_chain_tp_remove(struct tcf_chain *chain,
@@ -371,6 +375,7 @@ static void tcf_chain_tp_remove(struct tcf_chain *chain,
371 if (chain->p_filter_chain && tp == chain->filter_chain) 375 if (chain->p_filter_chain && tp == chain->filter_chain)
372 RCU_INIT_POINTER(*chain->p_filter_chain, next); 376 RCU_INIT_POINTER(*chain->p_filter_chain, next);
373 RCU_INIT_POINTER(*chain_info->pprev, next); 377 RCU_INIT_POINTER(*chain_info->pprev, next);
378 tcf_chain_put(chain);
374} 379}
375 380
376static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain, 381static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain,