Bug 1283358 - CVE-2016-2184 Local RedHat Enterprise Linux DoS – RHEL 7.1 Kernel crashes on invalid USB device descriptors (snd_usb_audio driver bug2) [local-DoS]
CVE-2016-2184 Local RedHat Enterprise Linux DoS – RHEL 7.1 Kernel crashes on ...
Status: CLOSED WONTFIX
Product: Red Hat Enterprise Linux 7
Classification: Red Hat
Component: kernel (Show other bugs)
7.1
Unspecified Unspecified
unspecified Severity high
: rc
: 7.3
Assigned To: Don Zickus
Mike Gahagan
: Security, SecurityTracking
Depends On:
Blocks: CVE-2016-2184
  Show dependency treegraph
 
Reported: 2015-11-18 14:38 EST by Ralf Spenneberg
Modified: 2016-11-08 11:08 EST (History)
2 users (show)

See Also:
Fixed In Version:
Doc Type: Release Note
Doc Text:
Story Points: ---
Clone Of:
Environment:
Last Closed: 2016-03-31 09:34:17 EDT
Type: Bug
Regression: ---
Mount Type: ---
Documentation: ---
CRM:
Verified Versions:
Category: ---
oVirt Team: ---
RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: ---


Attachments (Terms of Use)
vUSBf Payload (141 bytes, text/plain)
2015-11-18 14:39 EST, Ralf Spenneberg
no flags Details
Stacktrace (5.66 KB, text/plain)
2015-11-18 14:40 EST, Ralf Spenneberg
no flags Details
Arduino firmware demonstrating the bug (16.68 KB, text/plain)
2015-11-18 14:41 EST, Ralf Spenneberg
no flags Details
research.txt (12.77 KB, text/plain)
2016-03-30 13:49 EDT, Vladis Dronov
no flags Details
snd-usb-audio-double-free.patch (2.70 KB, patch)
2016-03-30 13:51 EDT, Vladis Dronov
no flags Details | Diff
dmesg-9-max-debug.txt (7.16 KB, text/plain)
2016-03-30 13:52 EDT, Vladis Dronov
no flags Details
dmesg-6-more-debug.txt (6.17 KB, text/plain)
2016-03-30 13:53 EDT, Vladis Dronov
no flags Details
snd-usb-audio-double-free-v2.patch (4.41 KB, patch)
2016-03-31 09:25 EDT, Vladis Dronov
no flags Details | Diff

  None (edit)
Description Ralf Spenneberg 2015-11-18 14:38:03 EST
Description of problem:
Local RedHat Enterprise Linux DoS – RHEL 7.1 Kernel crashes on invalid 
USB device descriptors (snd_usb_audio driver bug2) [local-DoS]

Version-Release number of selected component (if applicable):
Kernel-Version: 3.10.0-229.20.1.el7.x86_64 

How reproducible:
always

OpenSource Security Ralf Spenneberg
Am Bahnhof 3-5
48565 Steinfurt
info@os-s.net


Date: November 12th, 2015
Authors: Sergej Schumilo, Hendrik Schwartke, Ralf Spenneberg
CVE: not yet assigned
CVSS: 4.9 (AV:L/AC:L/Au:N/C:N/I:N/A:C) 
Title: Local RedHat Enterprise Linux DoS – RHEL 7.1 Kernel crashes on invalid 
USB device descriptors (snd_usb_audio driver) [local-DoS]
Severity: Critical. The Kernel panics. A reboot is required.
Ease of Exploitation: Trivial
Vulnerability type: Wrong input validation
Products: RHEL 7.1 including all updates
Kernel-Version: 3.10.0-229.20.1.el7.x86_64 
(for debugging-purposes we used the CentOS Kernel kernel-debuginfo-3.10.0-229.14.1.el7)


Abstract
The Kernel 3.10.0-229.20.1.el7.x86_64 crashes when presented a buggy USB 
device which requires the snd_usb_audio driver.
Detailed product description
We confirmed the bug on the following system:
RHEL 7.1
Kernel = 3.10.0-229.20.1.el7.x86_64
Further products or kernel versions have not been tested.
How reproducible: Always
Actual results: Kernel crashes 

Description:
The bug was found using the USB-fuzzing framework vUSBf from Sergej Schumilo 
(github.com/schumilo) using the following device descriptor:

 ######### PAYLOAD 1 #########
[*] Device-Descriptor
  bLength:		0x12
  bDescriptorType:	0x1
  bcdUSB:		0x200
  bDeviceClass:		0x3
  bDeviceSubClass:	0x0
  bDeviceProtocol:	0x0
  bMaxPacketSize:	0x40
  idVendor:		0x582
  idProduct:		0x0
  bcdDevice:		0x100
  iManufacturer:	0x1
  iProduct:		0x2
  iSerialNumbers:	0x3
  bNumConfigurations:	0x1

This is the configuration descriptor containing the malicious value for 
bNumEndpoints causing the crash. A zero value for bNumEndpoints crashes the system.

	[*] Configuration-Descriptor
	  bLength:		0x9
	  bDescriptorType:	0x2
	  wTotalLength:		0x27
	  bNumInterfaces:	0x1
	  bConfigurationValue:	0x1
	  iConfiguration:	0x0
	  bmAttributes:		0x0
	  bMaxPower:		0x31
		[*] Interface-Descriptor
		  bLength:		0x9
		  bDescriptorType:	0x4
		  bInterfaceNumber:	0x0
		  bAlternateSetting:	0x0
		  bNumEndpoints:	0x3
		  bInterfaceClass:	0x0
		  bInterfaceSubClass:	0x0
		  bInterfaceProtocol:	0x0
			[*] Endpoint-Descriptor
			  bLength:		0x7
			  bDescriptorType:	0x5
			  bEndpointAddress:	0x81
			  bmAttribut:		0x3
			  wMaxPacketSize:	0x404
			  bInterval:		0xc
			[*] Endpoint-Descriptor
			  bLength:		0x7
			  bDescriptorType:	0x5
			  bEndpointAddress:	0x1
			  bmAttribut:		0x2
			  wMaxPacketSize:	0x4
			  bInterval:		0xc
			[*] Endpoint-Descriptor
			  bLength:		0x7
			  bDescriptorType:	0x5
			  bEndpointAddress:	0x82
			  bmAttribut:		0x1
			  wMaxPacketSize:	0x4
			  bInterval:		0xc

Proof of Concept:
1) The bug can be reproduced using USB-fuzzing framework vUSBf from Sergej Schumilo (github.com/schumilo). 
The attached vUSBf-obj file contains the payload. Please let us know if you would like to use the Facedancer board. 
In such case, we could also provide a patched version of vUSBf which allows to reproduce vUSBf-Payloads using the Facedancer board.
2) For a proof of concept we are providing also an Arduino firmware file. Just flash it 
on Arduino Leonardo and plug it into any RHEL machine. The Arduino will 
emulate the defective USB device.

   avrdude -v -p ATMEGA32u4 -c avr109 -P /dev/ttyACM0 -b 57600 -U flash:w:binary.hex

The file binary.hex has been attached to this bug report.
To prevent automated sending of payloads, use a jumper to connect port D3 and 
5V!


Severity and Ease of Exploitation
The security weakness can be easily exploited. Using our Arduino firmware only 
physical access to the system is required. 


Additional info:
Stacktrace, vUSBf-Payload, Arduino-Firmware attached.


Please assign a CVE for this issue since this is a local DoS of the targeted system. 
CVSS 4.9 (AV:L/AC:L/Au:N/C:N/I:N/A:C)
Comment 1 Ralf Spenneberg 2015-11-18 14:39 EST
Created attachment 1096251 [details]
vUSBf Payload
Comment 2 Ralf Spenneberg 2015-11-18 14:40 EST
Created attachment 1096252 [details]
Stacktrace
Comment 3 Ralf Spenneberg 2015-11-18 14:41 EST
Created attachment 1096253 [details]
Arduino firmware demonstrating the bug
Comment 4 Vladis Dronov 2016-03-11 08:58:56 EST
CVE-2016-2184 which is Red Hat's private CVE ID was assigned to this security flaw. Please, use it in the public communications regarding this flaw, thank you.
Comment 5 Adam Mariš 2016-03-14 06:28:38 EDT
Public via:

http://seclists.org/bugtraq/2016/Mar/89
Comment 6 Vladis Dronov 2016-03-30 13:47:40 EDT
Research:

1) Even after the upstream commits 0f886ca1, 902eb7fd and 447d6275f (many thanks to Takashi Iwai) there is a double-free bug in [snd-usb-audio] module due to alloc/free logic flaw in snd_usb_add_audio_stream() function. A double-free leads to kernel structure/list/slab corruptions and shortly to null-deref, GPF or lockup.

2.1) Let me describe what happens with the current code in usb_audio_probe(), create_fixed_stream_quirk() and snd_usb_add_audio_stream():

> [ sound/usb/card.c ]
> static int usb_audio_probe(struct usb_interface *intf,
>                            const struct usb_device_id *usb_id)
> {
>         ...
>         if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
>                 /* need some special handlings */
>                 err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk);
>                 if (err < 0)
>                         goto __error;
>         }
>         ...
>  __error:
>         if (chip) {
>                 if (!chip->num_interfaces)
>                         snd_card_free(chip->card);

Somewhere in the middle of usb_audio_probe() the function snd_usb_create_quirk() is called, and if it returns with an error and no interfaces were created, the sound card is destroyed with "snd_card_free(chip->card)".

2.2) create_fixed_stream_quirk() is called from snd_usb_create_quirk():

> [ sound/usb/quirks.c ]
> static int create_fixed_stream_quirk(struct snd_usb_audio *chip,
>                                      struct usb_interface *iface,
>                                      struct usb_driver *driver,
>                                      const struct snd_usb_audio_quirk *quirk)
> {
>         struct audioformat *fp;
>         struct usb_host_interface *alts;
>         struct usb_interface_descriptor *altsd;
>         ...
>         fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL);
>         ...
> (*)     stream = (fp->endpoint & USB_DIR_IN)
> (*)             ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
> (*)     err = snd_usb_add_audio_stream(chip, stream, fp);
> (*)     if (err < 0)
> (*)             goto error;
> 
>         if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber ||
>             fp->altset_idx >= iface->num_altsetting) {
>                 err = -EINVAL;
>                 goto error;
>         }
>         alts = &iface->altsetting[fp->altset_idx];
>         altsd = get_iface_desc(alts);
>         if (altsd->bNumEndpoints < 1) {
>                 err = -EINVAL;
>                 goto error;
>         }
>         ...
>  error:
>         kfree(fp);
>         kfree(rate_table);
>         return err;
> }

2.3) *fp is allocated and passed to snd_usb_add_audio_stream() where snd_usb_init_substream() is called:

> [ sound/usb/stream.c ]
> int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
>                              int stream,
>                              struct audioformat *fp)
> {
>         struct snd_usb_stream *as;
>         struct snd_usb_substream *subs;
>         struct snd_pcm *pcm;
>         ...
>         /* create a new pcm */
>         as = kzalloc(sizeof(*as), GFP_KERNEL);
>         ...
>         snd_usb_init_substream(as, stream, fp);

2.4) In turn snd_usb_init_substream() adds audioformat *fp by its &fp->list to substream's fmt_list list:

> [ sound/usb/stream.c ]
> static void snd_usb_init_substream(struct snd_usb_stream *as,
>                                    int stream,
>                                    struct audioformat *fp)
> {
>         struct snd_usb_substream *subs = &as->substream[stream];
> 
>         INIT_LIST_HEAD(&subs->fmt_list);
>         ...
>         list_add_tail(&fp->list, &subs->fmt_list);
>         subs->num_formats++;

2.5) Things go bad from this point in case snd_usb_add_audio_stream() or the caller go the error path. The bug is that the caller frees (see "error: kfree(fp);" code) *fp on the error path, BUT the pointer to the already-freed memory remains in the substream's fmt_list list.

The double-free happens after create_fixed_stream_quirk() returns with an error and usb_audio_probe() calls snd_card_free()->...->snd_usb_audio_pcm_free()->free_substream(). As subs->fmt_list is already corrupted, iterating it with list_for_each_entry_safe() leads to any and unpredictable results.

> static void free_substream(struct snd_usb_substream *subs)
> {
>         struct audioformat *fp, *n;
>         ...
>         list_for_each_entry_safe(fp, n, &subs->fmt_list, list) {
>         ...
>                 kfree(fp);

3.1) The crash is reproduceable by faking the USB device with no endpoints as described in https://bugzilla.redhat.com/show_bug.cgi?id=1283355 and https://bugzilla.redhat.com/show_bug.cgi?id=1283358.

Please see as a proof the following kernel log with debug printing added to the code. First, *fp is added to fmt_list in snd_usb_init_substream():

[  322.797223] usb 1-1: new full-speed USB device number 2 using xhci_hcd
[  322.974083] usb 1-1: config 1 interface 0 altsetting 0 has 3 endpoint descriptors, different from the interface descriptor's value: 0
[  322.987031] usb 1-1: New USB device found, idVendor=045e, idProduct=0283
[  322.998318] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  323.083913] media: Linux media interface: v0.10
[  323.231249] init_substream: add_tail() fp=ffff88003364ba80 fp->list.next=ffff8800b1e898b8 fp->list.prev=ffff8800b1e898b8 fmt_list=ffff8800b1e898b8 fmt_list.next=ffff88003364ba80 prev=ffff88003364ba80 num_formats=1

As you can see we have a correct list here with head at fmt_list=ffff8800b1e898b8 and single element fp=ffff88003364ba80.

3.2) The code finds out that there are too few endpoints present and goes the error path (to the "error:" label):

[  323.307927] usb 1-1: too few endpoints
[  323.312964] trace-before-free: substream-0 ffff8800b1e89818 numf 1 fmt_list ffff8800b1e898b8 next ffff88003364ba80
[  323.353759] fp=ffff88003364ba80 next=ffff8800b1e898b8 prev=ffff8800b1e898b8 rate=(null) chmap=(null)
[  323.362118] struct sane

As you can see the substream's fmt_list is sane at this point.

3.3) After "kfree(fp)" in the error path of create_fixed_stream_quirk() *fp is freed, BUT the pointer to the freed memory remains in fmt_list. After *fp is freed the list is corrupted and contains trash:

[  323.371752] KFREE(fp) ffff88003364ba80
[  323.380383] trace-after-free: substream-0 ffff8800b1e89818 numf 1 fmt_list ffff8800b1e898b8 next ffff88003364ba80
[  323.400003] fp=ffff88003364ba80 next=ffff88003364bf80 prev=ffff8800b1e898b8 rate=(null) chmap=(null)
[  323.406786] fp=ffff88003364bf80 next=          (null) prev=          (null) rate=(null) chmap=(null)
[  323.422211] next == NULL: FAIL, struct INSANE
[  323.436706] KFREE(rate_table) (null)

3.4) After error return from create_fixed_stream_quirk() the sound card is destroyed with "snd_card_free(chip->card)" in usb_audio_probe(). In the end free_substream() is called:

[  323.511256] usb 1-1: snd_usb_create_quirk() failed: -22
[  323.565108] list_for_each_entry_safe(): fp=ffff88003364ba80 n=ffff88003364bf80
[  323.586337] kfree fp ffff88003364ba80                                  <<< DOUBLE-FREE
[  323.588509] loop-end: fp=ffff88003364ba80 n=ffff88003364bf80
[  323.599969] list_for_each_entry_safe(): fp=ffff88003364bf80 n=(null)
[  323.610460] kfree fp ffff88003364bf80                                  <<< FREEING SOMEONE ELSE'S MEMORY
[  323.613181] loop-end: fp=ffff88003364bf80 n=(null)                     <<< NULL-PTR DEREF
...
[  324.247113] BUG: unable to handle kernel NULL pointer dereference at           (null)
[  324.247533] IP: [<ffffffffc02d40ef>] free_substream.part.0+0xef/0x100 [snd_usb_audio]
[  324.248088] PGD 0 
[  324.248088] Oops: 0000 [#1] SMP 
[  324.248088] Modules linked in: snd_usb_audio(+) snd_usbmidi_lib snd_hwdep ...
[  324.248088] CPU: 2 PID: 767 Comm: systemd-udevd Not tainted 4.5.0-vladis #25
[  324.248088] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.8.2-0-g33fbe13 by qemu-project.org 04/01/2014
[  324.248088] task: ffff880034718000 ti: ffff8800b2fbc000 task.ti: ffff8800b2fbc000
[  324.248088] RIP: 0010:[<ffffffffc02d40ef>]  [<ffffffffc02d40ef>] free_substream.part.0+0xef/0x100 [snd_usb_audio]
[  324.248088] RSP: 0018:ffff8800b2fbf898  EFLAGS: 00010217
...
[  324.248088] Call Trace:
[  324.248088]  [<ffffffffc02d43fa>] snd_usb_audio_pcm_free+0x9a/0xa0 [snd_usb_audio]
[  324.248088]  [<ffffffffc028d982>] snd_pcm_free+0x32/0xa0 [snd_pcm]
[  324.248088]  [<ffffffffc028da02>] snd_pcm_dev_free+0x12/0x20 [snd_pcm]
[  324.248088]  [<ffffffffc027d279>] __snd_device_free+0x29/0x70 [snd]
[  324.248088]  [<ffffffffc027d660>] snd_device_free_all+0x30/0x60 [snd]
[  324.248088]  [<ffffffffc02777a4>] release_card_device+0x34/0x90 [snd]
[  324.248088]  [<ffffffff815ae2b2>] device_release+0x32/0x90
[  324.248088]  [<ffffffff81455f8a>] kobject_release+0x7a/0x190
[  324.248088]  [<ffffffff81455e47>] kobject_put+0x27/0x50
[  324.248088]  [<ffffffff815ae5f7>] put_device+0x17/0x20
[  324.248088]  [<ffffffffc0277b57>] snd_card_free+0x67/0x90 [snd]
[  324.248088]  [<ffffffffc02c4f14>] usb_audio_probe+0x754/0x9d0 [snd_usb_audio]
...

4.1) The suggested patch consists of 2 changes. First, I suppose we should move the code in create_fixed_stream_quirk() marked as "(*)" (see above) after "if (altsd->bNumEndpoints < 1)" check. This way no allocations is done if we go an error path. I've checked that fp->iface and fp->altset_idx are not changed in snd_usb_add_audio_stream() and functions called from it, so it is safe to swap these 2 pieces of code.

4.2) The problem with double-free still remains. I've verified that all the callers of snd_usb_add_audio_stream() free *fp in case of error:

$ git grep snd_usb_add_audio_stream
sound/usb/quirks.c:     err = snd_usb_add_audio_stream(chip, stream, fp);

> *        err = snd_usb_add_audio_stream(chip, stream, fp);
> *        if (err < 0)
> *                goto error;
> * error:
> *        kfree(fp);
> *        kfree(rate_table);
> *        return err;

sound/usb/quirks.c:     err = snd_usb_add_audio_stream(chip, stream, fp);

> *        err = snd_usb_add_audio_stream(chip, stream, fp);
> *        if (err < 0) {
> *                kfree(fp);
> *                return err;
> *        }

sound/usb/stream.c:             err = snd_usb_add_audio_stream(chip, stream, fp);

> *                err = snd_usb_add_audio_stream(chip, stream, fp);
> *                if (err < 0) {
> *                        kfree(fp->rate_table);
> *                        kfree(fp->chmap);
> *                        kfree(fp);
> *                        return err;
> *                }

This means that snd_usb_add_audio_stream() should remove *fp from the substream's fmt_list list on the error path, if it was already added. Such places are:


> int snd_usb_add_audio_stream(struct snd_usb_audio *chip, int stream, struct audioformat *fp)
> {
>         struct snd_usb_stream *as;
>         struct snd_usb_substream *subs;
>         struct snd_pcm *pcm;
>         int err;
> 
>         list_for_each_entry(as, &chip->pcm_list, list) {
>                 subs = &as->substream[stream];
>                 ...
>                         list_add_tail(&fp->list, &subs->fmt_list); <<< ADDING fp HERE
>                         subs->num_formats++;
>                         return 0;                                  <<< NO ERROR PATH HERE
>                 }
>         }
> 
>         /* look for an empty stream */
>         list_for_each_entry(as, &chip->pcm_list, list) {
>                 subs = &as->substream[stream];
>                 ...
>                 snd_usb_init_substream(as, stream, fp);  <<< ADDING fp HERE, IF add_chmap() FAILS
>                 return add_chmap(as->pcm, stream, subs); <<< fp SHOULD BE REMOVED FROM fmt_list
>         }
> 
>         /* create a new pcm */
>         as = kzalloc(sizeof(*as), GFP_KERNEL);
>         err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
>         ...
>         if (err < 0) {            <<< fp IS NOT ADDED YET HERE, SO FINE
>                 kfree(as);
>                 return err;
>         }
>         ...
>         snd_usb_init_substream(as, stream, fp);                 <<< ADDING fp HERE
>         ...                                                     <<< IF add_chmap() FAILS fp SHOULD
>         return add_chmap(pcm, stream, &as->substream[stream]);  <<< BE REMOVED FROM fmt_list
> }

add_chmap() itself does not add anything to fmt_list list, so we indeed need to remove only the single list element from the list. Having all the above in mind, the patch follows.

4.3) How to handle possible error paths after successful call to snd_usb_add_audio_stream() in create_fixed_stream_quirk() is d
iscussable. Properly it should be like the below, but I believe it is overcomplication here would and stick to a simple error_after_add_audio_stream: label:

>  error2:
>         snd_usb_del_audio_stream(...something...);
>  error:
>         kfree(fp);
>         kfree(rate_table);
>         return err;
Comment 7 Vladis Dronov 2016-03-30 13:49 EDT
Created attachment 1141881 [details]
research.txt
Comment 8 Vladis Dronov 2016-03-30 13:51 EDT
Created attachment 1141884 [details]
snd-usb-audio-double-free.patch
Comment 9 Vladis Dronov 2016-03-30 13:52 EDT
Created attachment 1141885 [details]
dmesg-9-max-debug.txt
Comment 10 Vladis Dronov 2016-03-30 13:53 EDT
Created attachment 1141886 [details]
dmesg-6-more-debug.txt
Comment 11 Vladis Dronov 2016-03-31 09:25 EDT
Created attachment 1142228 [details]
snd-usb-audio-double-free-v2.patch
Comment 12 Vladis Dronov 2016-03-31 09:31:37 EDT
Research:
http://mailman.alsa-project.org/pipermail/alsa-devel/2016-March/106316.html

Final patch in alsa-devel@, linux-kernel@, linux-sound@ lists:
http://mailman.alsa-project.org/pipermail/alsa-devel/2016-March/106393.html
Comment 13 Vladis Dronov 2016-03-31 09:34:17 EDT
Thank you for reporting this flaw. The Product Security has rated this flaw as having low security impact (bz#1317012), so the patch is currently not planned to be added to the RHEL source trees. If accepted to the upstream, the patch may get to the RHEL trees later at the next USB subsystem code rebase.

Note You need to log in before you can comment on or make changes to this bug.