Re: [PATCH 1/3] cifs: fix session state transition to avoid use-after-free issue

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, 26 Jun 2023 12:24:35 +0530
Shyam Prasad N <nspmangalore@xxxxxxxxx> wrote:

> On Mon, Jun 26, 2023 at 10:54 AM Steve French <smfrench@xxxxxxxxx>
> wrote:
> >
> > Added Cc: stable and Shyam's RB and merged into cifs-2.6.git
> > for-next
> >
> > On Mon, Jun 26, 2023 at 12:15 AM Shyam Prasad N
> > <nspmangalore@xxxxxxxxx> wrote:
> > >
> > > On Mon, Jun 26, 2023 at 9:25 AM Winston Wen
> > > <wentao@xxxxxxxxxxxxx> wrote:
> > > >
> > > > We switch session state to SES_EXITING without
> > > > cifs_tcp_ses_lock now, it may lead to potential use-after-free
> > > > issue.
> > > >
> > > > Consider the following execution processes:
> > > >
> > > > Thread 1:
> > > > __cifs_put_smb_ses()
> > > >     spin_lock(&cifs_tcp_ses_lock)
> > > >     if (--ses->ses_count > 0)
> > > >         spin_unlock(&cifs_tcp_ses_lock)
> > > >         return
> > > >     spin_unlock(&cifs_tcp_ses_lock)
> > > >         ---> **GAP**
> > > >     spin_lock(&ses->ses_lock)
> > > >     if (ses->ses_status == SES_GOOD)
> > > >         ses->ses_status = SES_EXITING
> > > >     spin_unlock(&ses->ses_lock)
> > > >
> > > > Thread 2:
> > > > cifs_find_smb_ses()
> > > >     spin_lock(&cifs_tcp_ses_lock)
> > > >     list_for_each_entry(ses, ...)
> > > >         spin_lock(&ses->ses_lock)
> > > >         if (ses->ses_status == SES_EXITING)
> > > >             spin_unlock(&ses->ses_lock)
> > > >             continue
> > > >         ...
> > > >         spin_unlock(&ses->ses_lock)
> > > >     if (ret)
> > > >         cifs_smb_ses_inc_refcount(ret)
> > > >     spin_unlock(&cifs_tcp_ses_lock)
> > > >
> > > > If thread 1 is preempted in the gap and thread 2 start
> > > > executing, thread 2 will get the session, and soon thread 1
> > > > will switch the session state to SES_EXITING and start
> > > > releasing it, even though thread 1 had increased the session's
> > > > refcount and still uses it.
> > > >
> > > > So switch session state under cifs_tcp_ses_lock to eliminate
> > > > this gap.
> > > >
> > > > Signed-off-by: Winston Wen <wentao@xxxxxxxxxxxxx>
> > > > ---
> > > >  fs/smb/client/connect.c | 7 ++++---
> > > >  1 file changed, 4 insertions(+), 3 deletions(-)
> > > >
> > > > diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
> > > > index 9d16626e7a66..165ecb222c19 100644
> > > > --- a/fs/smb/client/connect.c
> > > > +++ b/fs/smb/client/connect.c
> > > > @@ -1963,15 +1963,16 @@ void __cifs_put_smb_ses(struct cifs_ses
> > > > *ses) spin_unlock(&cifs_tcp_ses_lock);
> > > >                 return;
> > > >         }
> > > > +       spin_lock(&ses->ses_lock);
> > > > +       if (ses->ses_status == SES_GOOD)
> > > > +               ses->ses_status = SES_EXITING;
> > > > +       spin_unlock(&ses->ses_lock);
> > > >         spin_unlock(&cifs_tcp_ses_lock);
> > > >
> > > >         /* ses_count can never go negative */
> > > >         WARN_ON(ses->ses_count < 0);
> > > >
> > > >         spin_lock(&ses->ses_lock);
> > > > -       if (ses->ses_status == SES_GOOD)
> > > > -               ses->ses_status = SES_EXITING;
> > > > -
> > > >         if (ses->ses_status == SES_EXITING &&
> > > > server->ops->logoff) { spin_unlock(&ses->ses_lock);
> > > >                 cifs_free_ipc(ses);
> > > > --
> > > > 2.40.1
> > > >
> > >
> > > Good catch.
> > > Looks good to me.
> > > @Steve French Please CC stable for this one.
> > >
> > > --
> > > Regards,
> > > Shyam
> >
> >
> >
> > --
> > Thanks,
> >
> > Steve
> 
> @Winston Wen I think the following change should be sufficient to fix
> this issue:
> diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
> index 9d16626e7a66..78874eb2537d 100644
> --- a/fs/smb/client/connect.c
> +++ b/fs/smb/client/connect.c
> @@ -1963,10 +1963,11 @@ void __cifs_put_smb_ses(struct cifs_ses *ses)
>                 spin_unlock(&cifs_tcp_ses_lock);
>                 return;
>         }
> -       spin_unlock(&cifs_tcp_ses_lock);
> 
>         /* ses_count can never go negative */
>         WARN_ON(ses->ses_count < 0);
> +       list_del_init(&ses->smb_ses_list);
> +       spin_unlock(&cifs_tcp_ses_lock);
> 
>         spin_lock(&ses->ses_lock);
>         if (ses->ses_status == SES_GOOD)
> @@ -1986,9 +1987,6 @@ void __cifs_put_smb_ses(struct cifs_ses *ses)
>                 cifs_free_ipc(ses);
>         }
> 
> -       spin_lock(&cifs_tcp_ses_lock);
> -       list_del_init(&ses->smb_ses_list);
> -       spin_unlock(&cifs_tcp_ses_lock);
> 
>         chan_count = ses->chan_count;
> 
> The bug was that the ses was kept in the smb ses list, even after the
> ref count had reached 0.
> With the above change, that should be fixed, and no one should be able
> to get to the ses from that point.
> 
> Please let me know if you see a problem with this.
> 

Hi Shyam,

Thanks for the comments! And sorry for my late reply...

It make sense to me that maybe we should remove the session from the
list once its refcount is reduced to 0 to avoid any futher access. In
fact, I did try to do this from the beginning. But I was not sure if we
need to access the session from the list in the free process, such
as the following:

smb2_check_receive()
  smb2_verify_signature()
    server->ops->calc_signature()
      smb2_calc_signature()
        smb2_find_smb_ses()
          /* scan the list and find the session */

Perhaps we need some refactoring here.

So I gave up on this approach and did a small fix to make it work, but
maybe I missed something elsewhere...


-- 
Thanks,
Winston



[Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux