Skip to content

Commit c266f64

Browse files
w1ldptrdavem330
authored andcommitted
net: sched: protect block state with mutex
Currently, tcf_block doesn't use any synchronization mechanisms to protect critical sections that manage lifetime of its chains. block->chain_list and multiple variables in tcf_chain that control its lifetime assume external synchronization provided by global rtnl lock. Converting chain reference counting to atomic reference counters is not possible because cls API uses multiple counters and flags to control chain lifetime, so all of them must be synchronized in chain get/put code. Use single per-block lock to protect block data and manage lifetime of all chains on the block. Always take block->lock when accessing chain_list. Chain get and put modify chain lifetime-management data and parent block's chain_list, so take the lock in these functions. Verify block->lock state with assertions in functions that expect to be called with the lock taken and are called from multiple places. Take block->lock when accessing filter_chain_list. In order to allow parallel update of rules on single block, move all calls to classifiers outside of critical sections protected by new block->lock. Rearrange chain get and put functions code to only access protected chain data while holding block lock: - Rearrange code to only access chain reference counter and chain action reference counter while holding block lock. - Extract code that requires block->lock from tcf_chain_destroy() into standalone tcf_chain_destroy() function that is called by __tcf_chain_put() in same critical section that changes chain reference counters. Signed-off-by: Vlad Buslov <vladbu@mellanox.com> Acked-by: Jiri Pirko <jiri@mellanox.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent b67de69 commit c266f64

File tree

2 files changed

+76
-13
lines changed

2 files changed

+76
-13
lines changed

include/net/sch_generic.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <linux/list.h>
1313
#include <linux/refcount.h>
1414
#include <linux/workqueue.h>
15+
#include <linux/mutex.h>
1516
#include <net/gen_stats.h>
1617
#include <net/rtnetlink.h>
1718

@@ -352,6 +353,10 @@ struct tcf_chain {
352353
};
353354

354355
struct tcf_block {
356+
/* Lock protects tcf_block and lifetime-management data of chains
357+
* attached to the block (refcnt, action_refcnt, explicitly_created).
358+
*/
359+
struct mutex lock;
355360
struct list_head chain_list;
356361
u32 index; /* block index for shared blocks */
357362
refcount_t refcnt;

net/sched/cls_api.c

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ static void tcf_proto_destroy(struct tcf_proto *tp,
201201
kfree_rcu(tp, rcu);
202202
}
203203

204+
#define ASSERT_BLOCK_LOCKED(block) \
205+
lockdep_assert_held(&(block)->lock)
206+
204207
struct tcf_filter_chain_list_item {
205208
struct list_head list;
206209
tcf_chain_head_change_t *chain_head_change;
@@ -212,6 +215,8 @@ static struct tcf_chain *tcf_chain_create(struct tcf_block *block,
212215
{
213216
struct tcf_chain *chain;
214217

218+
ASSERT_BLOCK_LOCKED(block);
219+
215220
chain = kzalloc(sizeof(*chain), GFP_KERNEL);
216221
if (!chain)
217222
return NULL;
@@ -243,25 +248,51 @@ static void tcf_chain0_head_change(struct tcf_chain *chain,
243248
tcf_chain_head_change_item(item, tp_head);
244249
}
245250

246-
static void tcf_chain_destroy(struct tcf_chain *chain)
251+
/* Returns true if block can be safely freed. */
252+
253+
static bool tcf_chain_detach(struct tcf_chain *chain)
247254
{
248255
struct tcf_block *block = chain->block;
249256

257+
ASSERT_BLOCK_LOCKED(block);
258+
250259
list_del(&chain->list);
251260
if (!chain->index)
252261
block->chain0.chain = NULL;
262+
263+
if (list_empty(&block->chain_list) &&
264+
refcount_read(&block->refcnt) == 0)
265+
return true;
266+
267+
return false;
268+
}
269+
270+
static void tcf_block_destroy(struct tcf_block *block)
271+
{
272+
mutex_destroy(&block->lock);
273+
kfree_rcu(block, rcu);
274+
}
275+
276+
static void tcf_chain_destroy(struct tcf_chain *chain, bool free_block)
277+
{
278+
struct tcf_block *block = chain->block;
279+
253280
kfree(chain);
254-
if (list_empty(&block->chain_list) && !refcount_read(&block->refcnt))
255-
kfree_rcu(block, rcu);
281+
if (free_block)
282+
tcf_block_destroy(block);
256283
}
257284

258285
static void tcf_chain_hold(struct tcf_chain *chain)
259286
{
287+
ASSERT_BLOCK_LOCKED(chain->block);
288+
260289
++chain->refcnt;
261290
}
262291

263292
static bool tcf_chain_held_by_acts_only(struct tcf_chain *chain)
264293
{
294+
ASSERT_BLOCK_LOCKED(chain->block);
295+
265296
/* In case all the references are action references, this
266297
* chain should not be shown to the user.
267298
*/
@@ -273,6 +304,8 @@ static struct tcf_chain *tcf_chain_lookup(struct tcf_block *block,
273304
{
274305
struct tcf_chain *chain;
275306

307+
ASSERT_BLOCK_LOCKED(block);
308+
276309
list_for_each_entry(chain, &block->chain_list, list) {
277310
if (chain->index == chain_index)
278311
return chain;
@@ -287,31 +320,40 @@ static struct tcf_chain *__tcf_chain_get(struct tcf_block *block,
287320
u32 chain_index, bool create,
288321
bool by_act)
289322
{
290-
struct tcf_chain *chain = tcf_chain_lookup(block, chain_index);
323+
struct tcf_chain *chain = NULL;
324+
bool is_first_reference;
291325

326+
mutex_lock(&block->lock);
327+
chain = tcf_chain_lookup(block, chain_index);
292328
if (chain) {
293329
tcf_chain_hold(chain);
294330
} else {
295331
if (!create)
296-
return NULL;
332+
goto errout;
297333
chain = tcf_chain_create(block, chain_index);
298334
if (!chain)
299-
return NULL;
335+
goto errout;
300336
}
301337

302338
if (by_act)
303339
++chain->action_refcnt;
340+
is_first_reference = chain->refcnt - chain->action_refcnt == 1;
341+
mutex_unlock(&block->lock);
304342

305343
/* Send notification only in case we got the first
306344
* non-action reference. Until then, the chain acts only as
307345
* a placeholder for actions pointing to it and user ought
308346
* not know about them.
309347
*/
310-
if (chain->refcnt - chain->action_refcnt == 1 && !by_act)
348+
if (is_first_reference && !by_act)
311349
tc_chain_notify(chain, NULL, 0, NLM_F_CREATE | NLM_F_EXCL,
312350
RTM_NEWCHAIN, false);
313351

314352
return chain;
353+
354+
errout:
355+
mutex_unlock(&block->lock);
356+
return chain;
315357
}
316358

317359
static struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
@@ -330,17 +372,31 @@ static void tc_chain_tmplt_del(struct tcf_chain *chain);
330372

331373
static void __tcf_chain_put(struct tcf_chain *chain, bool by_act)
332374
{
375+
struct tcf_block *block = chain->block;
376+
bool is_last, free_block = false;
377+
unsigned int refcnt;
378+
379+
mutex_lock(&block->lock);
333380
if (by_act)
334381
chain->action_refcnt--;
335-
chain->refcnt--;
382+
383+
/* tc_chain_notify_delete can't be called while holding block lock.
384+
* However, when block is unlocked chain can be changed concurrently, so
385+
* save these to temporary variables.
386+
*/
387+
refcnt = --chain->refcnt;
388+
is_last = refcnt - chain->action_refcnt == 0;
389+
if (refcnt == 0)
390+
free_block = tcf_chain_detach(chain);
391+
mutex_unlock(&block->lock);
336392

337393
/* The last dropped non-action reference will trigger notification. */
338-
if (chain->refcnt - chain->action_refcnt == 0 && !by_act)
394+
if (is_last && !by_act)
339395
tc_chain_notify(chain, NULL, 0, 0, RTM_DELCHAIN, false);
340396

341-
if (chain->refcnt == 0) {
397+
if (refcnt == 0) {
342398
tc_chain_tmplt_del(chain);
343-
tcf_chain_destroy(chain);
399+
tcf_chain_destroy(chain, free_block);
344400
}
345401
}
346402

@@ -772,6 +828,7 @@ static struct tcf_block *tcf_block_create(struct net *net, struct Qdisc *q,
772828
NL_SET_ERR_MSG(extack, "Memory allocation for block failed");
773829
return ERR_PTR(-ENOMEM);
774830
}
831+
mutex_init(&block->lock);
775832
INIT_LIST_HEAD(&block->chain_list);
776833
INIT_LIST_HEAD(&block->cb_list);
777834
INIT_LIST_HEAD(&block->owner_list);
@@ -835,7 +892,7 @@ static void tcf_block_put_all_chains(struct tcf_block *block)
835892
static void __tcf_block_put(struct tcf_block *block, struct Qdisc *q,
836893
struct tcf_block_ext_info *ei)
837894
{
838-
if (refcount_dec_and_test(&block->refcnt)) {
895+
if (refcount_dec_and_mutex_lock(&block->refcnt, &block->lock)) {
839896
/* Flushing/putting all chains will cause the block to be
840897
* deallocated when last chain is freed. However, if chain_list
841898
* is empty, block has to be manually deallocated. After block
@@ -844,6 +901,7 @@ static void __tcf_block_put(struct tcf_block *block, struct Qdisc *q,
844901
*/
845902
bool free_block = list_empty(&block->chain_list);
846903

904+
mutex_unlock(&block->lock);
847905
if (tcf_block_shared(block))
848906
tcf_block_remove(block, block->net);
849907
if (!free_block)
@@ -853,7 +911,7 @@ static void __tcf_block_put(struct tcf_block *block, struct Qdisc *q,
853911
tcf_block_offload_unbind(block, q, ei);
854912

855913
if (free_block)
856-
kfree_rcu(block, rcu);
914+
tcf_block_destroy(block);
857915
else
858916
tcf_block_put_all_chains(block);
859917
} else if (q) {

0 commit comments

Comments
 (0)