diff mbox series

[net] mptcp: validate 'id' when stopping the ADD_ADDR retransmit timer

Message ID 68c6032edaf880a958bf7f65afc7e9520f39d5cd.1620754971.git.dcaratti@redhat.com
State Superseded, archived
Delegated to: Mat Martineau
Headers show
Series [net] mptcp: validate 'id' when stopping the ADD_ADDR retransmit timer | expand

Commit Message

Davide Caratti May 11, 2021, 5:46 p.m. UTC
when Linux receives an echo-ed ADD_ADDR, it checks the IP address against
the list of "announced" addresses. In case of a positive match, the timer
that handles retransmissions is stopped regardless of the 'Address Id' in
the received packet: this behaviour does not comply with RFC8684 §3.4.1.

Fix it by validating the 'Address Id' in received echo-ed ADD_ADDRs.
Tested using packetdrill, with the following captured output:

 unpatched kernel:

 Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0xfd2e62517888fe29,mptcp dss ack 3007449509], length 0
 In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 1 1.2.3.4,mptcp dss ack 3013740213], length 0
 Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0xfd2e62517888fe29,mptcp dss ack 3007449509], length 0
 In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 90 198.51.100.2,mptcp dss ack 3013740213], length 0
        ^^^ retransmission is stopped here, but 'Address Id' is 90

 patched kernel:

 Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0x1cf372d59e05f4b8,mptcp dss ack 3007449509], length 0
 In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 1 1.2.3.4,mptcp dss ack 1672384568], length 0
 Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0x1cf372d59e05f4b8,mptcp dss ack 3007449509], length 0
 In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 90 198.51.100.2,mptcp dss ack 1672384568], length 0
 Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0x1cf372d59e05f4b8,mptcp dss ack 3007449509], length 0
 In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 1 198.51.100.2,mptcp dss ack 1672384568], length 0
        ^^^ retransmission is stopped here, only when both 'Address Id' and 'IP Address' match

Fixes: 00cfd77b9063 ("mptcp: retransmit ADD_ADDR when timeout")
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
---
 net/mptcp/pm_netlink.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Comments

Mat Martineau May 11, 2021, 10:39 p.m. UTC | #1
On Tue, 11 May 2021, Davide Caratti wrote:

> when Linux receives an echo-ed ADD_ADDR, it checks the IP address against
> the list of "announced" addresses. In case of a positive match, the timer
> that handles retransmissions is stopped regardless of the 'Address Id' in
> the received packet: this behaviour does not comply with RFC8684 §3.4.1.
>
> Fix it by validating the 'Address Id' in received echo-ed ADD_ADDRs.
> Tested using packetdrill, with the following captured output:
>
> unpatched kernel:
>
> Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0xfd2e62517888fe29,mptcp dss ack 3007449509], length 0
> In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 1 1.2.3.4,mptcp dss ack 3013740213], length 0
> Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0xfd2e62517888fe29,mptcp dss ack 3007449509], length 0
> In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 90 198.51.100.2,mptcp dss ack 3013740213], length 0
>        ^^^ retransmission is stopped here, but 'Address Id' is 90
>
> patched kernel:
>
> Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0x1cf372d59e05f4b8,mptcp dss ack 3007449509], length 0
> In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 1 1.2.3.4,mptcp dss ack 1672384568], length 0
> Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0x1cf372d59e05f4b8,mptcp dss ack 3007449509], length 0
> In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 90 198.51.100.2,mptcp dss ack 1672384568], length 0
> Out <...> Flags [.], ack 1, win 256, options [mptcp add-addr v1 id 1 198.51.100.2 hmac 0x1cf372d59e05f4b8,mptcp dss ack 3007449509], length 0
> In  <...> Flags [.], ack 1, win 257, options [mptcp add-addr v1-echo id 1 198.51.100.2,mptcp dss ack 1672384568], length 0
>        ^^^ retransmission is stopped here, only when both 'Address Id' and 'IP Address' match
>
> Fixes: 00cfd77b9063 ("mptcp: retransmit ADD_ADDR when timeout")
> Signed-off-by: Davide Caratti <dcaratti@redhat.com>
> ---
> net/mptcp/pm_netlink.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/net/mptcp/pm_netlink.c b/net/mptcp/pm_netlink.c
> index d094588afad8..4318d4f5902e 100644
> --- a/net/mptcp/pm_netlink.c
> +++ b/net/mptcp/pm_netlink.c
> @@ -353,11 +353,11 @@ mptcp_pm_del_add_timer(struct mptcp_sock *msk,
>
> 	spin_lock_bh(&msk->pm.lock);
> 	entry = mptcp_lookup_anno_list_by_saddr(msk, addr);
> -	if (entry)
> +	if (entry && entry->addr.id == addr->id)
> 		entry->retrans_times = ADD_ADDR_RETRANS_MAX;
> 	spin_unlock_bh(&msk->pm.lock);
>
> -	if (entry)
> +	if (entry && entry->addr.id == addr->id)
> 		sk_stop_timer_sync(sk, &entry->add_timer);
>
> 	return entry;

Hi Davide -

The code path for ADD_ADDR echo ignores the return value of 
mptcp_pm_del_add_timer(), so there's no chance there of leaving 
entry->add_timer running if the addr.id doesn't match.

The function is also called by remove_anno_list_by_saddr(), which will 
free 'entry' if a non-NULL value is returned by mptcp_pm_del_add_timer(). 
There's more of a chance here that the sk_stop_timer_sync() would be 
skipped and entry would be freed anyway. Looking at the various paths to 
this in the path manager it seems like it *should* not happen, but I'm not 
convinced it's impossible. Did you look at the effect of this change on 
these other code paths?


--
Mat Martineau
Intel
Davide Caratti May 14, 2021, 9:13 a.m. UTC | #2
hello Mat, thanks for reviewing!

On Tue, May 11, 2021 at 03:39:14PM -0700, Mat Martineau wrote:
> On Tue, 11 May 2021, Davide Caratti wrote:
> 
> > when Linux receives an echo-ed ADD_ADDR, it checks the IP address against
> > the list of "announced" addresses. In case of a positive match, the timer
> > that handles retransmissions is stopped regardless of the 'Address Id' in
> > the received packet: this behaviour does not comply with RFC8684 §3.4.1.
> > 
> > Fix it by validating the 'Address Id' in received echo-ed ADD_ADDRs.
> > Tested using packetdrill, with the following captured output:
> > 
> 
> Hi Davide -
> 
> The code path for ADD_ADDR echo ignores the return value of
> mptcp_pm_del_add_timer(), so there's no chance there of leaving
> entry->add_timer running if the addr.id doesn't match.

true, and that's the purpose of this patch. When addr.id doesn't match,
it leaves the timer running to allow another attempt of ADD_ADDR + HMAC.

> The function is also called by remove_anno_list_by_saddr(), which will free
> 'entry' if a non-NULL value is returned by mptcp_pm_del_add_timer(). There's
> more of a chance here that the sk_stop_timer_sync() would be skipped and
> entry would be freed anyway. Looking at the various paths to this in the
> path manager it seems like it *should* not happen, but I'm not convinced
> it's impossible. Did you look at the effect of this change on these other
> code paths?

good point, I only tried packetdrill and the kselftests. I see

mptcp_pm_remove_anno_addr()
  remove_anno_list_by_saddr()

and its callers: in this case,either 'addr.id' is always read from the return
value of 

 __lookup_addr_by_id(pernet, addr.addr.id);

or by iterating over

&pernet->local_addr_list

, so those should all be 'signal' endpoints previously added by netlink:
the value of addr.id should always match and the timer should always
be stopped.

However, I also don't feel 100% safe - because 'entry' is freed by
remove_anno_list_by_saddr() and the value of 'address.id' is never checked
in addresses_equal(). Maybe it's safer to do soemething like:

 347 struct mptcp_pm_add_entry *
 348 mptcp_pm_del_add_timer(struct mptcp_sock *msk,
 349                        struct mptcp_addr_info *addr, bool strict)
 350 {
 351         struct mptcp_pm_add_entry *entry;
 352         struct sock *sk = (struct sock *)msk;
 353 
 354         spin_lock_bh(&msk->pm.lock);
 355         entry = mptcp_lookup_anno_list_by_saddr(msk, addr);
 356         if (entry && strict && entry->addr.id == addr->id)
 357                 entry->retrans_times = ADD_ADDR_RETRANS_MAX;
 358         spin_unlock_bh(&msk->pm.lock);
 359 
 360         if (entry && strict && entry->addr.id == addr->id)
 361                 sk_stop_timer_sync(sk, &entry->add_timer);
 362 
 363         return entry;
 364 }


so that mptcp_pm_del_add_timer(..., true) is called only in the receive
path of echo-ed ADD_ADDR. WDYT?

thanks!
Mat Martineau May 14, 2021, 6:41 p.m. UTC | #3
On Fri, 14 May 2021, Davide Caratti wrote:

> hello Mat, thanks for reviewing!
>
> On Tue, May 11, 2021 at 03:39:14PM -0700, Mat Martineau wrote:
>> On Tue, 11 May 2021, Davide Caratti wrote:
>>
>>> when Linux receives an echo-ed ADD_ADDR, it checks the IP address against
>>> the list of "announced" addresses. In case of a positive match, the timer
>>> that handles retransmissions is stopped regardless of the 'Address Id' in
>>> the received packet: this behaviour does not comply with RFC8684 §3.4.1.
>>>
>>> Fix it by validating the 'Address Id' in received echo-ed ADD_ADDRs.
>>> Tested using packetdrill, with the following captured output:
>>>
>>
>> Hi Davide -
>>
>> The code path for ADD_ADDR echo ignores the return value of
>> mptcp_pm_del_add_timer(), so there's no chance there of leaving
>> entry->add_timer running if the addr.id doesn't match.
>
> true, and that's the purpose of this patch. When addr.id doesn't match,
> it leaves the timer running to allow another attempt of ADD_ADDR + HMAC.
>
>> The function is also called by remove_anno_list_by_saddr(), which will free
>> 'entry' if a non-NULL value is returned by mptcp_pm_del_add_timer(). There's
>> more of a chance here that the sk_stop_timer_sync() would be skipped and
>> entry would be freed anyway. Looking at the various paths to this in the
>> path manager it seems like it *should* not happen, but I'm not convinced
>> it's impossible. Did you look at the effect of this change on these other
>> code paths?
>
> good point, I only tried packetdrill and the kselftests. I see
>
> mptcp_pm_remove_anno_addr()
>  remove_anno_list_by_saddr()
>
> and its callers: in this case,either 'addr.id' is always read from the return
> value of
>
> __lookup_addr_by_id(pernet, addr.addr.id);
>
> or by iterating over
>
> &pernet->local_addr_list
>
> , so those should all be 'signal' endpoints previously added by netlink:
> the value of addr.id should always match and the timer should always
> be stopped.
>
> However, I also don't feel 100% safe - because 'entry' is freed by
> remove_anno_list_by_saddr() and the value of 'address.id' is never checked
> in addresses_equal(). Maybe it's safer to do soemething like:
>
> 347 struct mptcp_pm_add_entry *
> 348 mptcp_pm_del_add_timer(struct mptcp_sock *msk,
> 349                        struct mptcp_addr_info *addr, bool strict)
> 350 {
> 351         struct mptcp_pm_add_entry *entry;
> 352         struct sock *sk = (struct sock *)msk;
> 353
> 354         spin_lock_bh(&msk->pm.lock);
> 355         entry = mptcp_lookup_anno_list_by_saddr(msk, addr);
> 356         if (entry && strict && entry->addr.id == addr->id)
> 357                 entry->retrans_times = ADD_ADDR_RETRANS_MAX;
> 358         spin_unlock_bh(&msk->pm.lock);
> 359
> 360         if (entry && strict && entry->addr.id == addr->id)
> 361                 sk_stop_timer_sync(sk, &entry->add_timer);
> 362
> 363         return entry;
> 364 }
>
>
> so that mptcp_pm_del_add_timer(..., true) is called only in the receive
> path of echo-ed ADD_ADDR. WDYT?
>

I think that's the right general direction, but (entry && (!strict || 
entry->addr.id == addr->id)) is the logic needed for the !strict callers 
to work like before.

--
Mat Martineau
Intel
diff mbox series

Patch

diff --git a/net/mptcp/pm_netlink.c b/net/mptcp/pm_netlink.c
index d094588afad8..4318d4f5902e 100644
--- a/net/mptcp/pm_netlink.c
+++ b/net/mptcp/pm_netlink.c
@@ -353,11 +353,11 @@  mptcp_pm_del_add_timer(struct mptcp_sock *msk,
 
 	spin_lock_bh(&msk->pm.lock);
 	entry = mptcp_lookup_anno_list_by_saddr(msk, addr);
-	if (entry)
+	if (entry && entry->addr.id == addr->id)
 		entry->retrans_times = ADD_ADDR_RETRANS_MAX;
 	spin_unlock_bh(&msk->pm.lock);
 
-	if (entry)
+	if (entry && entry->addr.id == addr->id)
 		sk_stop_timer_sync(sk, &entry->add_timer);
 
 	return entry;