diff mbox series

net: Fix potential RCU dereference issue in tcp_assign_congestion_control

Message ID tencent_2A17499A4FFA4D830F7D2F72A95A4ADAB308@qq.com (mailing list archive)
State Rejected
Delegated to: Netdev Maintainers
Headers show
Series net: Fix potential RCU dereference issue in tcp_assign_congestion_control | expand

Checks

Context Check Description
netdev/series_format warning Single patches do not need cover letters; Target tree name not specified in the subject
netdev/tree_selection success Guessed tree name to be net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 16 this patch: 16
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 5 of 5 maintainers
netdev/build_clang success Errors and warnings before: 16 this patch: 16
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 18 this patch: 18
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 14 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-09-20--15-00 (tests: 764)

Commit Message

Jiawei Ye Sept. 20, 2024, 9:08 a.m. UTC
In the `tcp_assign_congestion_control` function, the `ca->flags` is
accessed after the RCU read-side critical section is unlocked. According
to RCU usage rules, this is illegal. Reusing this pointer can lead to
unpredictable behavior, including accessing memory that has been updated
or causing use-after-free issues.

This possible bug was identified using a static analysis tool developed
by myself, specifically designed to detect RCU-related issues.

To resolve this issue, the `rcu_read_unlock` call has been moved to the
end of the function.

Signed-off-by: Jiawei Ye <jiawei.ye@foxmail.com>
---
In another part of the file, `tcp_set_congestion_control` calls
`tcp_reinit_congestion_control`, ensuring that the congestion control
reinitialization process is protected by RCU. The
`tcp_reinit_congestion_control` function contains operations almost
identical to those in `tcp_assign_congestion_control`, but the former
operates under full RCU protection, whereas the latter is only partially
protected. The differing protection strategies between the two may
warrant further unification.
---
 net/ipv4/tcp_cong.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Comments

Florian Westphal Sept. 20, 2024, 9:35 a.m. UTC | #1
Jiawei Ye <jiawei.ye@foxmail.com> wrote:
> In the `tcp_assign_congestion_control` function, the `ca->flags` is
> accessed after the RCU read-side critical section is unlocked. According
> to RCU usage rules, this is illegal. Reusing this pointer can lead to
> unpredictable behavior, including accessing memory that has been updated
> or causing use-after-free issues.
> 
> This possible bug was identified using a static analysis tool developed
> by myself, specifically designed to detect RCU-related issues.
> 
> To resolve this issue, the `rcu_read_unlock` call has been moved to the
> end of the function.
> 
> Signed-off-by: Jiawei Ye <jiawei.ye@foxmail.com>
> ---
> In another part of the file, `tcp_set_congestion_control` calls
> `tcp_reinit_congestion_control`, ensuring that the congestion control
> reinitialization process is protected by RCU. The
> `tcp_reinit_congestion_control` function contains operations almost
> identical to those in `tcp_assign_congestion_control`, but the former
> operates under full RCU protection, whereas the latter is only partially
> protected. The differing protection strategies between the two may
> warrant further unification.
> ---
>  net/ipv4/tcp_cong.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c
> index 0306d257fa64..356a59d316e3 100644
> --- a/net/ipv4/tcp_cong.c
> +++ b/net/ipv4/tcp_cong.c
> @@ -223,13 +223,13 @@ void tcp_assign_congestion_control(struct sock *sk)
>  	if (unlikely(!bpf_try_module_get(ca, ca->owner)))
>  		ca = &tcp_reno;

After this, ca either has module refcount incremented, so it can't
go away anymore, or reno fallback was enabled (always bultin).

>  	icsk->icsk_ca_ops = ca;
> -	rcu_read_unlock();

Therefore its ok to rcu unlock here.
Eric Dumazet Sept. 20, 2024, 2:11 p.m. UTC | #2
On Fri, Sep 20, 2024 at 11:35 AM Florian Westphal <fw@strlen.de> wrote:
>
> Jiawei Ye <jiawei.ye@foxmail.com> wrote:
> > In the `tcp_assign_congestion_control` function, the `ca->flags` is
> > accessed after the RCU read-side critical section is unlocked. According
> > to RCU usage rules, this is illegal. Reusing this pointer can lead to
> > unpredictable behavior, including accessing memory that has been updated
> > or causing use-after-free issues.
> >
> > This possible bug was identified using a static analysis tool developed
> > by myself, specifically designed to detect RCU-related issues.
> >
> > To resolve this issue, the `rcu_read_unlock` call has been moved to the
> > end of the function.
> >
> > Signed-off-by: Jiawei Ye <jiawei.ye@foxmail.com>
> > ---
> > In another part of the file, `tcp_set_congestion_control` calls
> > `tcp_reinit_congestion_control`, ensuring that the congestion control
> > reinitialization process is protected by RCU. The
> > `tcp_reinit_congestion_control` function contains operations almost
> > identical to those in `tcp_assign_congestion_control`, but the former
> > operates under full RCU protection, whereas the latter is only partially
> > protected. The differing protection strategies between the two may
> > warrant further unification.
> > ---
> >  net/ipv4/tcp_cong.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c
> > index 0306d257fa64..356a59d316e3 100644
> > --- a/net/ipv4/tcp_cong.c
> > +++ b/net/ipv4/tcp_cong.c
> > @@ -223,13 +223,13 @@ void tcp_assign_congestion_control(struct sock *sk)
> >       if (unlikely(!bpf_try_module_get(ca, ca->owner)))
> >               ca = &tcp_reno;
>
> After this, ca either has module refcount incremented, so it can't
> go away anymore, or reno fallback was enabled (always bultin).
>
> >       icsk->icsk_ca_ops = ca;
> > -     rcu_read_unlock();
>
> Therefore its ok to rcu unlock here.

I agree, there is no bug here.

Jiawei Ye, I guess your static analysis tool is not ready yet.
Jiawei Ye Sept. 23, 2024, 3:09 a.m. UTC | #3
Thank you very much for your feedback, Florian Westphal and Eric Dumazet. 

On 9/20/24 22:11, Eric Dumazet wrote
>
> On Fri, Sep 20, 2024 at 11:35 AM Florian Westphal <fw@strlen.de> wrote:
> >
> > Jiawei Ye <jiawei.ye@foxmail.com> wrote:
> > > In the `tcp_assign_congestion_control` function, the `ca->flags` is
> > > accessed after the RCU read-side critical section is unlocked. According
> > > to RCU usage rules, this is illegal. Reusing this pointer can lead to
> > > unpredictable behavior, including accessing memory that has been updated
> > > or causing use-after-free issues.
> > >
> > > This possible bug was identified using a static analysis tool developed
> > > by myself, specifically designed to detect RCU-related issues.
> > >
> > > To resolve this issue, the `rcu_read_unlock` call has been moved to the
> > > end of the function.
> > >
> > > Signed-off-by: Jiawei Ye <jiawei.ye@foxmail.com>
> > > ---
> > > In another part of the file, `tcp_set_congestion_control` calls
> > > `tcp_reinit_congestion_control`, ensuring that the congestion control
> > > reinitialization process is protected by RCU. The
> > > `tcp_reinit_congestion_control` function contains operations almost
> > > identical to those in `tcp_assign_congestion_control`, but the former
> > > operates under full RCU protection, whereas the latter is only partially
> > > protected. The differing protection strategies between the two may
> > > warrant further unification.
> > > ---
> > >  net/ipv4/tcp_cong.c | 2 +-
> > >  1 file changed, 1 insertion(+), 1 deletion(-)
> > >
> > > diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c
> > > index 0306d257fa64..356a59d316e3 100644
> > > --- a/net/ipv4/tcp_cong.c
> > > +++ b/net/ipv4/tcp_cong.c
> > > @@ -223,13 +223,13 @@ void tcp_assign_congestion_control(struct sock *sk)
> > >       if (unlikely(!bpf_try_module_get(ca, ca->owner)))
> > >               ca = &tcp_reno;
> >
> > After this, ca either has module refcount incremented, so it can't
> > go away anymore, or reno fallback was enabled (always bultin).
> >
> > >       icsk->icsk_ca_ops = ca;
> > > -     rcu_read_unlock();
> >
> > Therefore its ok to rcu unlock here.
> 
> I agree, there is no bug here.
> 
> Jiawei Ye, I guess your static analysis tool is not ready yet.

Yes, the static analysis tool is still under development and debugging. 

While I've collected and analyzed some relevant RCU commits from
associated repositories, designing an effective static detection tool
remains challenging. 

It's quite difficult without the assistance of experienced developers. If
you have any suggestions or examples, I would greatly appreciate your
help.

Best regards,
Jiawei Ye
Eric Dumazet Sept. 23, 2024, 7:36 a.m. UTC | #4
On Mon, Sep 23, 2024 at 5:16 AM Jiawei Ye <jiawei.ye@foxmail.com> wrote:
>
> Thank you very much for your feedback, Florian Westphal and Eric Dumazet.
>
> On 9/20/24 22:11, Eric Dumazet wrote
> >
> > On Fri, Sep 20, 2024 at 11:35 AM Florian Westphal <fw@strlen.de> wrote:
> > >
> > > Jiawei Ye <jiawei.ye@foxmail.com> wrote:
> > > > In the `tcp_assign_congestion_control` function, the `ca->flags` is
> > > > accessed after the RCU read-side critical section is unlocked. According
> > > > to RCU usage rules, this is illegal. Reusing this pointer can lead to
> > > > unpredictable behavior, including accessing memory that has been updated
> > > > or causing use-after-free issues.
> > > >
> > > > This possible bug was identified using a static analysis tool developed
> > > > by myself, specifically designed to detect RCU-related issues.
> > > >
> > > > To resolve this issue, the `rcu_read_unlock` call has been moved to the
> > > > end of the function.
> > > >
> > > > Signed-off-by: Jiawei Ye <jiawei.ye@foxmail.com>
> > > > ---
> > > > In another part of the file, `tcp_set_congestion_control` calls
> > > > `tcp_reinit_congestion_control`, ensuring that the congestion control
> > > > reinitialization process is protected by RCU. The
> > > > `tcp_reinit_congestion_control` function contains operations almost
> > > > identical to those in `tcp_assign_congestion_control`, but the former
> > > > operates under full RCU protection, whereas the latter is only partially
> > > > protected. The differing protection strategies between the two may
> > > > warrant further unification.
> > > > ---
> > > >  net/ipv4/tcp_cong.c | 2 +-
> > > >  1 file changed, 1 insertion(+), 1 deletion(-)
> > > >
> > > > diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c
> > > > index 0306d257fa64..356a59d316e3 100644
> > > > --- a/net/ipv4/tcp_cong.c
> > > > +++ b/net/ipv4/tcp_cong.c
> > > > @@ -223,13 +223,13 @@ void tcp_assign_congestion_control(struct sock *sk)
> > > >       if (unlikely(!bpf_try_module_get(ca, ca->owner)))
> > > >               ca = &tcp_reno;
> > >
> > > After this, ca either has module refcount incremented, so it can't
> > > go away anymore, or reno fallback was enabled (always bultin).
> > >
> > > >       icsk->icsk_ca_ops = ca;
> > > > -     rcu_read_unlock();
> > >
> > > Therefore its ok to rcu unlock here.
> >
> > I agree, there is no bug here.
> >
> > Jiawei Ye, I guess your static analysis tool is not ready yet.
>
> Yes, the static analysis tool is still under development and debugging.
>
> While I've collected and analyzed some relevant RCU commits from
> associated repositories, designing an effective static detection tool
> remains challenging.
>
> It's quite difficult without the assistance of experienced developers. If
> you have any suggestions or examples, I would greatly appreciate your
> help.
>

This case is explained in Documentation/RCU/rcuref.rst

line 61 : search_and_reference()

For congestion control modules, we call try_module_get() which calls
atomic_inc_not_zero(&module->refcnt)
Jiawei Ye Sept. 24, 2024, 3:57 a.m. UTC | #5
On 9/23/24 15:36, Eric Dumazet wrote:
> On Mon, Sep 23, 2024 at 5:16 AM Jiawei Ye <jiawei.ye@foxmail.com> wrote:
> >
> > Thank you very much for your feedback, Florian Westphal and Eric Dumazet.
> >
> > On 9/20/24 22:11, Eric Dumazet wrote
> > >
> > > On Fri, Sep 20, 2024 at 11:35 AM Florian Westphal <fw@strlen.de> wrote:
> > > >
> > > > Jiawei Ye <jiawei.ye@foxmail.com> wrote:
> > > > > In the `tcp_assign_congestion_control` function, the `ca->flags` is
> > > > > accessed after the RCU read-side critical section is unlocked. According
> > > > > to RCU usage rules, this is illegal. Reusing this pointer can lead to
> > > > > unpredictable behavior, including accessing memory that has been updated
> > > > > or causing use-after-free issues.
> > > > >
> > > > > This possible bug was identified using a static analysis tool developed
> > > > > by myself, specifically designed to detect RCU-related issues.
> > > > >
> > > > > To resolve this issue, the `rcu_read_unlock` call has been moved to the
> > > > > end of the function.
> > > > >
> > > > > Signed-off-by: Jiawei Ye <jiawei.ye@foxmail.com>
> > > > > ---
> > > > > In another part of the file, `tcp_set_congestion_control` calls
> > > > > `tcp_reinit_congestion_control`, ensuring that the congestion control
> > > > > reinitialization process is protected by RCU. The
> > > > > `tcp_reinit_congestion_control` function contains operations almost
> > > > > identical to those in `tcp_assign_congestion_control`, but the former
> > > > > operates under full RCU protection, whereas the latter is only partially
> > > > > protected. The differing protection strategies between the two may
> > > > > warrant further unification.
> > > > > ---
> > > > >  net/ipv4/tcp_cong.c | 2 +-
> > > > >  1 file changed, 1 insertion(+), 1 deletion(-)
> > > > >
> > > > > diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c
> > > > > index 0306d257fa64..356a59d316e3 100644
> > > > > --- a/net/ipv4/tcp_cong.c
> > > > > +++ b/net/ipv4/tcp_cong.c
> > > > > @@ -223,13 +223,13 @@ void tcp_assign_congestion_control(struct sock *sk)
> > > > >       if (unlikely(!bpf_try_module_get(ca, ca->owner)))
> > > > >               ca = &tcp_reno;
> > > >
> > > > After this, ca either has module refcount incremented, so it can't
> > > > go away anymore, or reno fallback was enabled (always bultin).
> > > >
> > > > >       icsk->icsk_ca_ops = ca;
> > > > > -     rcu_read_unlock();
> > > >
> > > > Therefore its ok to rcu unlock here.
> > >
> > > I agree, there is no bug here.
> > >
> > > Jiawei Ye, I guess your static analysis tool is not ready yet.
> >
> > Yes, the static analysis tool is still under development and debugging.
> >
> > While I've collected and analyzed some relevant RCU commits from
> > associated repositories, designing an effective static detection tool
> > remains challenging.
> >
> > It's quite difficult without the assistance of experienced developers. If
> > you have any suggestions or examples, I would greatly appreciate your
> > help.
> >
> 
> This case is explained in Documentation/RCU/rcuref.rst
> 
> line 61 : search_and_reference()
> 
> For congestion control modules, we call try_module_get() which calls
> atomic_inc_not_zero(&module->refcnt)

Thank you, Eric Dumazet. I will further explore the details in
Documentation/RCU/rcuref.rst and related documents.
diff mbox series

Patch

diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c
index 0306d257fa64..356a59d316e3 100644
--- a/net/ipv4/tcp_cong.c
+++ b/net/ipv4/tcp_cong.c
@@ -223,13 +223,13 @@  void tcp_assign_congestion_control(struct sock *sk)
 	if (unlikely(!bpf_try_module_get(ca, ca->owner)))
 		ca = &tcp_reno;
 	icsk->icsk_ca_ops = ca;
-	rcu_read_unlock();
 
 	memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv));
 	if (ca->flags & TCP_CONG_NEEDS_ECN)
 		INET_ECN_xmit(sk);
 	else
 		INET_ECN_dontxmit(sk);
+	rcu_read_unlock();
 }
 
 void tcp_init_congestion_control(struct sock *sk)