Subject: Re: [PATCH net 03/16] net/sched: act_csum: validate the control action inside init()

From: Vlad Buslov <[email-masked]>
Date: Thu, 7 Mar 2019 14:51:07 +0000

On Thu 07 Mar 2019 at 15:56, Davide Caratti <[email-masked]> wrote:
> On Fri, 2019-03-01 at 16:04 -0800, Cong Wang wrote:
>> > On Fri, Mar 1, 2019 at 10:02 AM Davide Caratti <[email-masked]> wrote:
>> >
>> > if I well understand the question, you are worried about
>> > tcf_action_goto_chain_exec(), that can dereference 'oldchain' while we are
>> > overwriting the action. A call to tcf_chain_put_by_act(oldchain) would
>> > decrease refcounts and eventually call kfree(oldchain).
>> >
>> > But this would result in a use-after-free only in case the chain has only
>> > refcount held by 1 action (the one we are overwriting), and 0 filters: is
>> > this a condition where packets can go through this action's data plane?
>>
>> Hmm? Isn't goto chain can be arbitrary? Packets can be routed
>> from this action to any filter chain, so even if the target chain has 0
>> filter this action still has traffic as long as itself is not on the same
>> chain?
>
> hi,
>
> sorry for the delay: it took some time to verify experimentally if we
> really need this or not. So, we want to ensure the control path doesn't do
>
>     tcf_csum_init(..., ovr=1, ...)
>       tcf_chain_put_by_act(oldchain)
>         tcf_chain_destroy(oldchain, ...)
> 	  kfree(oldchain);
>
> while the traffic path of the action is doing
>
>     res->goto_tp = rcu_dereference_bh(oldchain->filter_chain);
>
> I iterated this script many times on a KASAN kernel,
>
> # tc chain add dev crash0 chain 42 ingress protocol ip flower ip_proto udp action pass
> # tc filter add dev crash0 ingress protocol ip flower ip_proto udp action csum udp goto chain 42 index 66
> # tc chain del dev crash0 chain 42 ingress
> (start netperf traffic)
> # tc action replace action csum udp pass index 66
>
> and reproduced twice the following splat:
>
>  ==================================================================
>  BUG: KASAN: null-ptr-deref in tcf_action_exec+0x181/0x200
>  Read of size 8 at addr 0000000000000000 by task netperf/5777
>
>  CPU: 0 PID: 5777 Comm: netperf Not tainted 5.0.0-rc7.goto_chain_fix+ #551
>  Hardware name: Red Hat KVM, BIOS 0.5.1 01/01/2011
>  Call Trace:
>  <IRQ>
>   dump_stack+0xc7/0x15b
>   ? show_regs_print_info+0x5/0x5
>   ? _raw_read_lock_irq+0x40/0x40
>   ? tcf_action_exec+0x181/0x200
>   ? tcf_action_exec+0x181/0x200
>   kasan_report+0x176/0x192
>   ? tcf_action_exec+0x181/0x200
>   tcf_action_exec+0x181/0x200
>   ? find_dump_kind+0x170/0x170
>   ? rcu_irq_exit+0x153/0x210
>   fl_classify+0x81a/0x830
>
> so, I think that the answer to your question:
>
> On Wed, 2019-02-27 at 17:50 -0800, Cong Wang wrote:
>> > > > +       if (oldchain)
>> > > > +               tcf_chain_put_by_act(oldchain);
>> > >
>> > > Do we need to respect RCU grace period here?
>
> is a "yes, we do".
> Now I'm trying something similar to what's done in tcf_bpf_init(), to
> release the bpf program on 'replace' operations:
>
> 365         if (res == ACT_P_CREATED) {
> 366                 tcf_idr_insert(tn, *act);
> 367         } else {
> 368                 /* make sure the program being replaced is no longer executing */
> 369                 synchronize_rcu();
> 370                 tcf_bpf_cfg_cleanup(&old);
> 371         }
>
> do you think it's worth going in this direction?
> thank you in advance!

Hi Davide,

Using synchronize_rcu() will impact rule update rate performance and I
don't think we really need it. I don't see any reason why we can't just
update chain to be rcu-friendly. Data path is already rcu_read
protected, in fact it only needs chain to read rcu-pointer to tp list
when jumping to chain. So it should be enough to do the following:

1) Update tcf_chain_destroy() to free chain after rcu grace period.

2) Convert tc_action->goto_chain to be a proper rcu pointer. (mark it
with "__rcu", assign with rcu_assign_pointer(), read it with
rcu_dereference{_bh}(), etc.)

Am I missing anything?

Regards,
Vlad