[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CACzwLxgVJ2jROr8RWHXv++2m2tD9fvskp_MqTL7VhCPr-Eeeiw@mail.gmail.com>
Date: Wed, 13 Nov 2024 00:30:08 +0500
From: Sabyrzhan Tasbolatov <snovitoll@...il.com>
To: Alan Stern <stern@...land.harvard.edu>, oneukum@...e.com
Cc: gregkh@...uxfoundation.org, linux-kernel@...r.kernel.org, 
	linux-usb@...r.kernel.org, 
	syzbot+9760fbbd535cee131f81@...kaller.appspotmail.com, 
	syzkaller-bugs@...glegroups.com
Subject: Re: [PATCH v4] usb/cdc-wdm: fix memory info leak in wdm_read
On Tue, Nov 12, 2024 at 8:52 PM Alan Stern <stern@...land.harvard.edu> wrote:
>
> On Tue, Nov 12, 2024 at 06:29:31PM +0500, Sabyrzhan Tasbolatov wrote:
> > syzbot reported "KMSAN: kernel-infoleak in wdm_read", though there is no
> > reproducer and the only report for this issue.
> >
> > The check:
> >
> >       if (cntr > count)
> >               cntr = count;
> >
> > only limits `cntr` to `count` (the number of bytes requested by
> > userspace), but it doesn't verify that `desc->ubuf` actually has `count`
> > bytes. This oversight can lead to situations where `copy_to_user` reads
> > uninitialized data from `desc->ubuf`.
> >
> > This patch makes sure `cntr` respects` both the `desc->length` and the
> > `count` requested by userspace, preventing any uninitialized memory from
> > leaking into userspace.
>
> > ---
> >  drivers/usb/class/cdc-wdm.c | 5 +++--
> >  1 file changed, 3 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
> > index 86ee39db013f..5a500973b463 100644
> > --- a/drivers/usb/class/cdc-wdm.c
> > +++ b/drivers/usb/class/cdc-wdm.c
> > @@ -598,8 +598,9 @@ static ssize_t wdm_read
> >               spin_unlock_irq(&desc->iuspin);
> >       }
>
> Note that the code immediately before the "if" statement which ends here
> does:
>
>         cntr = READ_ONCE(desc->length);
>
> And the code at the end of the "if" block does:
>
>                 cntr = desc->length;
>
> (while holding the spinlock).  Thus it is guaranteed that either way,
> cntr is equal to desc->length when we reach this point.
>
> >
> > -     if (cntr > count)
> > -             cntr = count;
> > +     /* Ensure cntr does not exceed available data in ubuf. */
> > +     cntr = min_t(size_t, count, desc->length);
>
> And therefore this line does exactly the same computation as the code
> you removed.  Except for one thing: At this point the spinlock is not
> held, and your new code does not call READ_ONCE().  That is an
> oversight.
I've re-read your and Oliver's comments and come up with this diff,
which is the same as v4 except it is within a spinlock.
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
index 86ee39db013f..47b299e03e11 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -598,8 +598,11 @@ static ssize_t wdm_read
                spin_unlock_irq(&desc->iuspin);
        }
-       if (cntr > count)
-               cntr = count;
+       spin_lock_irq(&desc->iuspin);
+       /* Ensure cntr does not exceed available data in ubuf. */
+       cntr = min_t(size_t, count, desc->length);
+       spin_unlock_irq(&desc->iuspin);
+
        rv = copy_to_user(buffer, desc->ubuf, cntr);
        if (rv > 0) {
                rv = -EFAULT;
>
> Since the new code does the same thing as the old code, it cannot
> possibly fix any bugs.
Without the reproducer I can not confirm that this fixes the hypothetical bug,
however here is my understand how the diff above can fix the memory info leak:
static ssize_t wdm_read() {
        cntr = READ_ONCE(desc->length);
        if (cntr == 0) {
                spin_lock_irq(&desc->iuspin);
                /* can remain 0 if not increased in wdm_in_callback() */
                cntr = desc->length;
                spin_unlock_irq(&desc->iuspin);
        }
        spin_lock_irq(&desc->iuspin);
        /* take the minimum of whatever user requests `count` and
desc->length = 0 */
        cntr = min_t(size_t, count, desc->length);
        spin_lock_irq(&desc->iuspin);
        /* cntr is 0, nothing to copy to the user space. */
        rv = copy_to_user(buffer, desc->ubuf, cntr);
>
> (Actually there is one other thing to watch out for: the difference
> between signed and unsigned values.  Here cntr and desc->length are
> signed whereas count is unsigned.  In theory that could cause problems
> -- it might even be related to the cause of the original bug report.
> Can you prove that desc->length will never be negative?)
desc->length can not be negative if I understand the following correctly:
static void wdm_in_callback(struct urb *urb)
{
        ...
        int length = urb->actual_length;
       ...
       if (length + desc->length > desc->wMaxCommand) {
              /* The buffer would overflow */
             ...
       } else {
              /* we may already be in overflow */
              if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
                     ...
                     desc->length += length;
                     desc->reslength = length;
       }
}
urb->actual_length is u32, actually, need to change `int length` to
`u32 length` though.
>
> Alan Stern
Please let me know if the diff makes sense and I will proceed with v5.
Thanks
Powered by blists - more mailing lists
 
