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, Jun 26, 2023 at 2:09 PM Winston Wen <wentao@xxxxxxxxxxxxx> wrote:
>
> 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.

Yes. The above ses finding is expected to fail during a reconnect.

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

Attaching the above change as a patch.
It replaces this particular patch in the series.

The other two patches are not strictly necessary with this change, but
don't hurt.


--
Regards,
Shyam

Attachment: 0001-cifs-remove-smb-session-from-list-rest-of-the-cleanu.patch
Description: Binary data


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

  Powered by Linux