lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Wed, 15 Mar 2023 17:26:13 +0300
From:   Anastasia Belova <abelova@...ralinux.ru>
To:     Mirsad Goran Todorovac <mirsad.todorovac@....unizg.hr>
Cc:     Jakob Koschel <jakobkoschel@...il.com>, linux-usb@...r.kernel.org,
        linux-kernel@...r.kernel.org, lvc-project@...uxtesting.org,
        Greg Kroah-Hartman <gregkh@...uxfoundation.org>
Subject: Re: [PATCH] goku_udc: Add check for NULL in goku_irq


13.03.2023 16:49, Mirsad Goran Todorovac пишет:
> On 13.3.2023. 13:19, Anastasia Belova wrote:
>>
>> 11.03.2023 06:29, Mirsad Goran Todorovac пишет:
>>> On 15. 02. 2023. 14:48, Greg Kroah-Hartman wrote:
>>>> On Wed, Feb 15, 2023 at 04:39:56PM +0300, Анастасия Белова wrote:
>>>>> 03.02.2023 13:45, Greg Kroah-Hartman пишет:
>>>>>> On Fri, Feb 03, 2023 at 01:18:28PM +0300, Anastasia Belova wrote:
>>>>>>> Before dereferencing dev->driver check it for NULL.
>>>>>>>
>>>>>>> If an interrupt handler is called after assigning
>>>>>>> NULL to dev->driver, but before resetting dev->int_enable,
>>>>>>> NULL-pointer will be dereferenced.
>>>>>>>
>>>>>>> Found by Linux Verification Center (linuxtesting.org) with SVACE.
>>>>>>>
>>>>>>> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
>>>>>>> Signed-off-by: Anastasia Belova <abelova@...ralinux.ru>
>>>>>>> ---
>>>>>>>    drivers/usb/gadget/udc/goku_udc.c | 5 +++--
>>>>>>>    1 file changed, 3 insertions(+), 2 deletions(-)
>>>>>>>
>>>>>>> diff --git a/drivers/usb/gadget/udc/goku_udc.c 
>>>>>>> b/drivers/usb/gadget/udc/goku_udc.c
>>>>>>> index bdc56b24b5c9..896bba8b47f1 100644
>>>>>>> --- a/drivers/usb/gadget/udc/goku_udc.c
>>>>>>> +++ b/drivers/usb/gadget/udc/goku_udc.c
>>>>>>> @@ -1616,8 +1616,9 @@ static irqreturn_t goku_irq(int irq, void 
>>>>>>> *_dev)
>>>>>>>    pm_next:
>>>>>>>            if (stat & INT_USBRESET) {        /* hub reset done */
>>>>>>>                ACK(INT_USBRESET);
>>>>>>> -            INFO(dev, "USB reset done, gadget %s\n",
>>>>>>> -                dev->driver->driver.name);
>>>>>>> +            if (dev->driver)
>>>>>>> +                INFO(dev, "USB reset done, gadget %s\n",
>>>>>>> +                    dev->driver->driver.name);
>>>>>> How can this ever happen?  Can you trigger this somehow?  If not, I
>>>>>> don't think this is going to be possible (also what's up with printk
>>>>>> from an irq handler???)
>>>>> Unfortunately, I can't find the way to trigger this at the moment.
>>>> Then the change should not be made.
>>>>
>>>>> What about printk, should trace_printk be used instead?
>>>> Why?
>>>>
>>>>>> Odds are, no one actually has this hardware anymore, right?
>>>>> Despite of this, such vulnerability should be fixed because
>>>>> there is a possibility to exploit it.
>>>> How can this be "exploited" if it can not ever be triggered?
>>>>
>>>> Also, this would cause a NULL dereference in an irq handler, how 
>>>> can you
>>>> "exploit" that?
>>>>
>>>> Please only submit patches that actually do something.  It is getting
>>>> very hard to want to even review patches from this "project" based on
>>>> the recent submissions.
>>>>
>>>> thanks,
>>>>
>>>> greg k-h
>>> Hi Greg, Anastasia,
>>
>> Hi Misrad,
>>
>>> Without any pros or cons, or taking sides, there appears to be a 
>>> similar check
>>> when using dev->driver->driver.name in
>>>
>>> https://elixir.bootlin.com/linux/latest/source/drivers/usb/gadget/udc/goku_udc.c#L1158 
>>>
>>>
>>>     seq_printf(m,
>>>            "%s - %s\n"
>>>            "%s version: %s %s\n"
>>>            "Gadget driver: %s\n"
>>>            "Host %s, %s\n"
>>>            "\n",
>>>            pci_name(dev->pdev), driver_desc,
>>>            driver_name, DRIVER_VERSION, dmastr(),
>>>            dev->driver ? dev->driver->driver.name : "(none)",
>>>            is_usb_connected
>>>                ? ((tmp & PW_PULLUP) ? "full speed" : "powered")
>>>                : "disconnected",
>>>            udc_ep_state(dev->ep0state));
>>>
>>> On the other hand, where could dev->drivre be reset without 
>>> resetting dev->int_enable?
>>>
>>> dev->driver = NULL appears here:
>>>
>>> static int goku_udc_stop(struct usb_gadget *g)
>>> {
>>>     struct goku_udc    *dev = to_goku_udc(g);
>>>     unsigned long    flags;
>>>
>>>     spin_lock_irqsave(&dev->lock, flags);
>>>     dev->driver = NULL;
>>>     stop_activity(dev);
>>>     spin_unlock_irqrestore(&dev->lock, flags);
>>>
>>>     return 0;
>>> }
>>>
>>> it is followed by stop_activity() calling udc_reset():
>>>
>>> static void udc_reset(struct goku_udc *dev)
>>> {
>>>     struct goku_udc_regs __iomem    *regs = dev->regs;
>>>
>>>     writel(0, &regs->power_detect);
>>>     writel(0, &regs->int_enable);
>>>     readl(&regs->int_enable);
>>>     dev->int_enable = 0;
>>> .
>>> .
>>> .
>>>
>>> ... but this happens in between spin_lock_irqsave() and 
>>> spin_unlock_irqsave(),
>>> which appears like a correct way to do it.
>>
>> Are you sure that spin_lock_irqsave makes the code safe? This 
>> function disables interrupts on
>> local processor only (Linux Device Drivers, Third Edition). So it 
>> doesn't seem to be
>> absolutely safe on multiprocessor systems.
>
> Hi, Anastasia,
>
> Looking from the Second Edition or the book and the source, I see that
> spin_lock_irqsave() expands to:
>
> static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
> {
>     unsigned long flags;
>
>     local_irq_save(flags);
>     preempt_disable();
>     spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
>     LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
>     return flags;
> }
>
> if the multiple threads on multiple cores/SMTs contend for the same lock,
> that with preempt_disable() should assure mutual exclusion.
>
> Can you please quote from the Third Edition of Linux Device Drivers where
> it says otherwise?
>

Hi, Mirsad,


If I get it right, preempt_disable blocks interrupts on all processors,

correct? This statement seems to make the code safe, but there is a quote

from Linux Device Drivers, Third Edition, CHAPTER 5, Concurrency and Race

Conditions: "...spin_lock_irqsave disables interrupts (on the local 
processor

only) before taking the spinlock...". These thoughts contradict, don't they?


> BTW, please also consider reading this article:
>
> https://docs.kernel.org/driver-api/io_ordering.html
>
> I saw they were using this readl() after writel() for synchronisation, so
> please see if this clears your doubts. It says "readl() should flush 
> any pending
> writes".
>
> But I certainly see no harm in your proposal of guarding against NULL 
> pointer
> dereference of dev->driver, both in
>
> > -            INFO(dev, "USB reset done, gadget %s\n",
> > -                dev->driver->driver.name);
> > +            if (dev->driver)
> > +                INFO(dev, "USB reset done, gadget %s\n",
> > +                    dev->driver->driver.name);
>
> or use the construct as the one in another line of the driver:
>
> > -            INFO(dev, "USB reset done, gadget %s\n",
> > -                dev->driver->driver.name);
> > +            INFO(dev, "USB reset done, gadget %s\n",
> > +                dev->driver ? dev->driver->driver.name : "(none)");
>
> (This would IMHO enable detecting and logging when dev->driver 
> unexpectedly becomes NULL
> in a race condition, rather that just silently skipping and ignoring 
> the situation.)
>
Agree, the second construct looks better.
> but additionally also in:
>
> > -     spin_unlock (&dev->lock);
> > -     tmp = dev->driver->setup(&dev->gadget, &ctrl);
> > -     spin_lock (&dev->lock);
> > +     if (dev->driver && dev->driver->setup) {
> > +         spin_unlock (&dev->lock);
> > +         tmp = dev->driver->setup(&dev->gadget, &ctrl);
> > +         spin_lock (&dev->lock);
> > +     }
>
> for completeness and robustness sake.
>
> The author agrees that the race conditions in the device drivers are 
> very hard
> to reproduce:
>
> https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch09s08.html 
>
>
> But I am really not able to analyse all possible scenarios ATM.
> Maybe some ideas come after getting some oxygen.
>
> (This is not an authoritative answer on the matter, just an attempt on 
> analysis.)
>
> Regards,
> Mirsad
>
Regards,

Anastasia

>>> But second appearance is here:
>>>
>>> https://elixir.bootlin.com/linux/latest/source/drivers/usb/gadget/udc/goku_udc.c#L1559 
>>>
>>>
>>>     spin_lock(&dev->lock);
>>>
>>> rescan:
>>>     stat = readl(&regs->int_status) & dev->int_enable;
>>>          if (!stat)
>>>         goto done;
>>>     dev->irqs++;
>>>
>>>     /* device-wide irqs */
>>>     if (unlikely(stat & INT_DEVWIDE)) {
>>>         if (stat & INT_SYSERROR) {
>>>             ERROR(dev, "system error\n");
>>>             stop_activity(dev);
>>>             stat = 0;
>>>             handled = 1;
>>>             // FIXME have a neater way to prevent re-enumeration
>>>             dev->driver = NULL;
>>>             goto done;
>>>         }
>>>
>>> goto done leads to:
>>>
>>> done:
>>>     (void)readl(&regs->int_enable);
>>>     spin_unlock(&dev->lock);
>>>
>>> This unlocks dev->lock before setting dev->int_enable to zero, or 
>>> calling writel(0, &regs->int_enable);
>>> which could be problematic. Unless it called stop_activity(dev) four 
>>> lines earlier. Which does
>>> bot of:
>>>
>>>     writel(0, &regs->int_enable);
>>>     dev->int_enable = 0;
>>>
>>> So, FWIW, we seem to be safe. Yet, there might be no harm in 
>>> printing "(null)" rather
>>> than having an NULL pointer dereference, it seems.
>>>
>>> Yet, there is another unprotected dereference of dev->driver:
>>>
>>> https://elixir.bootlin.com/linux/latest/source/drivers/usb/gadget/udc/goku_udc.c#L1513 
>>>
>>>
>>>     spin_unlock (&dev->lock);
>>>     tmp = dev->driver->setup(&dev->gadget, &ctrl);
>>>     spin_lock (&dev->lock);
>>>
>>> All others (in goku_udc.c, at least) have triple safeguards like:
>>>
>>>                 if (dev->gadget.speed != USB_SPEED_UNKNOWN
>>>                         && dev->driver
>>>                         && dev->driver->suspend) {
>>>                     spin_unlock(&dev->lock);
>>> dev->driver->suspend(&dev->gadget);
>>>                     spin_lock(&dev->lock);
>>>                 }
>>>
>>> So the above should maybe put to:
>>>
>>>     if (dev->driver && dev->driver->setup) {
>>>         spin_unlock (&dev->lock);
>>>         tmp = dev->driver->setup(&dev->gadget, &ctrl);
>>>         spin_lock (&dev->lock);
>>>     }
>>>
>>> instead to be completely certain.
>>>
>>> Forgive me for this uninspired rant. Thank you if you've read this far.
>>> I hope this helps.
>>>
>>> My $0.02.
>>>
>>> Regards,
>>> Mirsad
>>>
>> Thanks,
>>
>> Anastasia
>

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ