diff mbox series

net: sched: crash on blocks with goto chain action

Message ID 20171119161716.5865-1-code@rkapl.cz
State Superseded, archived
Delegated to: David Miller
Headers show
Series net: sched: crash on blocks with goto chain action | expand

Commit Message

Roman Kapl Nov. 20, 2017, 9:34 p.m. UTC
tcf_block_put_ext has assumed that all filters (and thus their goto
actions) are destroyed in RCU callback and thus can not race with our
list iteration. However, that is not true during netns cleanup (see
tcf_exts_get_net comment).

Prevent the user after free by holding the current list element we are
iterating over (foreach_safe is not enough).

To reproduce, run the following in a netns and then delete the ns:
    ip link add dtest type dummy
    tc qdisc add dev dtest ingress
    tc filter add dev dtest chain 1 parent ffff: handle 1 prio 1 flower action goto chain 2

Signed-off-by: Roman Kapl <code@rkapl.cz>
---
The mail was original rejected by vger, this is a re-send to netdev@vger only
(with the same message ID). Sorry for any confusion.
---
 net/sched/cls_api.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

Comments

Roman Kapl Nov. 20, 2017, 9:41 p.m. UTC | #1
On 11/20/2017 06:54 PM, Cong Wang wrote:
> On Sun, Nov 19, 2017 at 8:17 AM, Roman Kapl <code@rkapl.cz> wrote:
>> tcf_block_put_ext has assumed that all filters (and thus their goto
>> actions) are destroyed in RCU callback and thus can not race with our
>> list iteration. However, that is not true during netns cleanup (see
>> tcf_exts_get_net comment).
>>
>> Prevent the user after free by holding the current list element we are
>> iterating over (foreach_safe is not enough).
> Hmm...
>
> Looks like we need to restore the trick we used previously, that is
> holding refcnt for all list entries before this list iteration.

Was there a reason to hold all list entries in that trick? I thought 
that holding just the current element will be enough, but maybe not.
Cong Wang Nov. 21, 2017, 7:31 p.m. UTC | #2
On Mon, Nov 20, 2017 at 1:41 PM, Roman Kapl <code@rkapl.cz> wrote:
> On 11/20/2017 06:54 PM, Cong Wang wrote:
>>
>> On Sun, Nov 19, 2017 at 8:17 AM, Roman Kapl <code@rkapl.cz> wrote:
>>>
>>> tcf_block_put_ext has assumed that all filters (and thus their goto
>>> actions) are destroyed in RCU callback and thus can not race with our
>>> list iteration. However, that is not true during netns cleanup (see
>>> tcf_exts_get_net comment).
>>>
>>> Prevent the user after free by holding the current list element we are
>>> iterating over (foreach_safe is not enough).
>>
>> Hmm...
>>
>> Looks like we need to restore the trick we used previously, that is
>> holding refcnt for all list entries before this list iteration.
>
>
> Was there a reason to hold all list entries in that trick? I thought that
> holding just the current element will be enough, but maybe not.
>

Yes, let me quote Jiri's explanation:

"
The reason for the hold above was to avoid use after free in this loop.
Consider following example:

chain1
  1 filter with action goto_chain 2
chain2
  empty

Now in your list_for_each_entry_safe loop, chain1 is flushed, action is
removed and chain is put:
tcf_action_goto_chain_fini->tcf_chain_put(2)

Given the fact chain2 is empty, this put would lead to tcf_chain_destroy(2)

Then in another iteration of list_for_each_entry_safe you are using
already freed chain.
"
Roman Kapl Nov. 21, 2017, 8:02 p.m. UTC | #3
On 11/21/2017 08:31 PM, Cong Wang wrote:
> On Mon, Nov 20, 2017 at 1:41 PM, Roman Kapl <code@rkapl.cz> wrote:
>> On 11/20/2017 06:54 PM, Cong Wang wrote:
>>> On Sun, Nov 19, 2017 at 8:17 AM, Roman Kapl <code@rkapl.cz> wrote:
>>>> tcf_block_put_ext has assumed that all filters (and thus their goto
>>>> actions) are destroyed in RCU callback and thus can not race with our
>>>> list iteration. However, that is not true during netns cleanup (see
>>>> tcf_exts_get_net comment).
>>>>
>>>> Prevent the user after free by holding the current list element we are
>>>> iterating over (foreach_safe is not enough).
>>> Hmm...
>>>
>>> Looks like we need to restore the trick we used previously, that is
>>> holding refcnt for all list entries before this list iteration.
>>
>> Was there a reason to hold all list entries in that trick? I thought that
>> holding just the current element will be enough, but maybe not.
>>
> Yes, let me quote Jiri's explanation:
>
> "
> The reason for the hold above was to avoid use after free in this loop.
> Consider following example:
>
> chain1
>    1 filter with action goto_chain 2
> chain2
>    empty
I believe the exact same example is part of the 'how to reproduce' part 
of commit and the patch helped me get rid of that crash.
>
> Now in your list_for_each_entry_safe loop,
Note that list_for_each_entry_safe was replaced by pure 
list_for_each_entry in my proposed patch.
> chain1 is flushed, action is
> removed and chain is put:
> tcf_action_goto_chain_fini->tcf_chain_put(2)
>
> Given the fact chain2 is empty, this put would lead to tcf_chain_destroy(2)
>
> Then in another iteration of list_for_each_entry_safe you are using
> already freed chain.
> "

No, I believe that the last iteration would simply stop, because at the 
point you reach second iteration, chain->next == head.

But maybe the "hold all chains" approach from 822e86d997 (net_sched: 
remove tcf_block_put_deferred())  is simpler to understand?
Cong Wang Nov. 22, 2017, 7:27 p.m. UTC | #4
On Tue, Nov 21, 2017 at 12:02 PM, Roman Kapl <code@rkapl.cz> wrote:
>
> But maybe the "hold all chains" approach from 822e86d997 (net_sched: remove
> tcf_block_put_deferred())  is simpler to understand?
>

Yes, it is much easier to understand for me, probably for others too.
diff mbox series

Patch

diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
index 7d97f612c9b9..58fed2ea3379 100644
--- a/net/sched/cls_api.c
+++ b/net/sched/cls_api.c
@@ -344,16 +344,26 @@  static void tcf_block_put_final(struct work_struct *work)
 }
 
 /* XXX: Standalone actions are not allowed to jump to any chain, and bound
- * actions should be all removed after flushing. However, filters are now
- * destroyed in tc filter workqueue with RTNL lock, they can not race here.
+ * actions should be all removed after flushing.
  */
 void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q,
 		       struct tcf_block_ext_info *ei)
 {
-	struct tcf_chain *chain, *tmp;
+	struct tcf_chain *chain, *last = NULL;
 
-	list_for_each_entry_safe(chain, tmp, &block->chain_list, list)
+	list_for_each_entry(chain, &block->chain_list, list) {
+		if (last)
+			tcf_chain_put(last);
+		/* Flushing a chain may release any other chain in this block,
+		 * so we have to hold on to the chain across flush to known
+		 * which one comes next.
+		 */
+		tcf_chain_hold(chain);
 		tcf_chain_flush(chain);
+		last = chain;
+	}
+	if (last)
+		tcf_chain_put(last);
 
 	tcf_block_offload_unbind(block, q, ei);