usb: core: fix a double free in the usb driver
Quoted 27 lines · Chung-Geol Kim
Quoted 26 lines
Quoted 5 lines · Chung-Geol Kim
Quoted 1 line
There is a double free problem in the usb driver.
Which driver?
When I using the USB OTG Storage, this issue happened. When remove the OTG Storage, it reproduced sometimes.
cpu 0 cpu 1 ---------------------------------------------------------------------------------------- (*Insert USB Storage) usb_create_shared_hcd() kmalloc(primary_hcd) kmalloc(primary_hcd->bandwidth_mutex) ->(primary_hcd->kref==1) usb_get_hcd() ->(primary_hcd->kref==2) usb_create_shared_hcd() kmalloc(hcd->shared_hcd) ->hcd->shared_hcd->bandwidth_mutex=primary->bandwidth_mutex ->primary_hcd->primary_hcd = primary_hcd ->hcd->shared_hcd->primary_hcd = primary_hcd ->(hcd->shared_hcd->kref==1) usb_get_hcd() ->(hcd->shared_hcd->kref==2)
Quoted 6 lines
Quoted 2 lines
usb_get_hcd() ->(hcd->shared_hcd->kref==3)I don't understand. Why do these actions take place on two different CPUs? Aren't the
primary_hcdand theshared_hcdstructures allocated by the same thread, on the same CPU?
Yes, you are right, The presentational errors in order to obtain an understanding of the process. Therefore, I will be happy to explain again the diagrammatic representation as shown below. If using usb 3.0 storage(OTG), you can see as below.
==============================================
At *Insert USB(3.0) Storage
sequence <1> --> <5>
==============================================
VOLD
=================================|============
(uevent)
______|___________
|<5> |
| SCSI |
|usb_get_hcd |
|shared_hcd(kref=3)|
|__________________|
___________________ ________|_________
|<2> | |<4> |
|dwc3_otg_sm_work | |dwc3_otg_sm_work |
|usb_get_hcd | |usb_get_hcd |
|primary_hcd(kref=2)| |shared_hcd(kref=2)|
|___________________| |__________________|
_________|_________ ________|_________
|<1> | |<3> |
|New USB BUS #1 | |New USB BUS #2 |
|usb_create_hcd | |usb_create_hcd |
|primary_hcd(kref=1)| |shared_hcd(kref=1)|
| | | |
|bandXX_mutex(alloc)|<-(Link)-bandXX_mutex |
|___________________| |__________________|==============================================
At *remove USB(3.0) Storage
sequence <1> --> <5> ((Normal Case))
==============================================
VOLD
=================================|============
(uevent)
______|___________
|<1> |
| SCSI |
|usb_put_hcd |
|shared_hcd(kref=2)|
|__________________|
___________________ ________|_________
|<4> | |<2> |
|dwc3_otg_sm_work | |dwc3_otg_sm_work |
|usb_put_hcd | |usb_put_hcd |
|primary_hcd(kref=1)| |shared_hcd(kref=1)|
|___________________| |__________________|
_________|_________ ________|_________
|<5> | |<3> |
|New USB BUS #1 | |New USB BUS #2 |
|hcd_release | |hcd_release |
|primary_hcd(kref=0)| |shared_hcd(kref=0)|
| | | |
|bandXX_mutex(free) | -X-cut off)-bandXX_mutex|
|___________________| |__________________|----------------------------------------------
Quoted 22 lines
Quoted 13 lines
--------------------------------------------------------------------------------------- (*remove USB Storage) usb_release_dev() ->(hcd->shared_hcd-kref==2) usb_release_dev() ->(hcd->shared_hcd-kref==1) usb_release_dev() -> (primary_hcd-kref==1) usb_release_dev() -> (primary_hcd-kref==0) hcd_release() -> kfree(primary_hcd->bandwidth_mutex)Quoted 8 lines
-> hcd->shared_hcd->primary_hcd = NULL -> kfree(primary_hcd) usb_release_dev() -> (hcd->shared_hcd-kref==0) hcd_release() -> usb_hcd_is_primary_hcd(hcd->shared_hcd) -> hcd->shared_hcd->primary_hcd already NULL, return 1 -> try to double kfree(primary_hcd->bandwidth_mutex)
Quoted 5 lines
The same question applies here. Aren't the
shared_hcdandprimary_hcdstructures released by the same thread, on the same CPU?The real bug here is that the
shared_hcdis released after theprimary_hcd. That's what you need to fix.
NO, It 's only depend on vold(scsi) release time. If the vold later released and is being released first hcd, Double free happened at <5> as below.
==============================================
At *remove USB(3.0) Storage
sequence <1> --> <5> ((Problem Case))
==============================================
VOLD
=================================|============
(uevent)
______|___________
|<5> |
| SCSI |
|usb_put_hcd |
|shared_hcd(kref=0)|
|*hcd_release |
|bandXX_mutex(free*)|<- double free
|__________________|
___________________ ________|_________
|<3> | |<1> |
|dwc3_otg_sm_work | |dwc3_otg_sm_work |
|usb_put_hcd | |usb_put_hcd |
|primary_hcd(kref=1)| |shared_hcd(kref=2)|
|___________________| |__________________|
_________|_________ ________|_________
|<4> | |<2> |
|New USB BUS #1 | |New USB BUS #2 |
|hcd_release | | |
|primary_hcd(kref=0)| |shared_hcd(kref=1)|
| | | |
|bandXX_mutex(free) |<-(Link)-bandXX_mutex |
|___________________| |__________________|----------------------------------------------
Quoted 35 lines
Quoted 18 lines
Since
hcd->shared_hcd->priary_hcdwas Null it didn't reach (hcd ==hcd->primary_hcd) inusb_hcd_is_primary_hcd(). It returned 1 at since condition !hcd->primary_hcd is met.Quoted 11 lines
drivers/usb/core/hcd.c static void hcd_release(struct kref *kref) · line 2611
Quoted 8 lines
drivers/usb/core/hcd.c +1
-1@@ -2608,7 +2608,7 @@ static void hcd_release(struct kref *kref) struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref);mutex_lock (&usb_port_peer_mutex); -if (usb_hcd_is_primary_hcd (hcd)) { +if (hcd == hcd->primary_hcd) {That doesn't make sense,
usb_hcd_is_primary_hcd()is the same as this check, what are you changing here?Since
hcd->priary_hcdwas Null it didn't reach (hcd ==hcd->primary_hcd). It returned 1 at since condition !hcd->primary_hcd is met.Quoted 6 lines
int
usb_hcd_is_primary_hcd(struct usb_hcd *hcd){ if (!hcd->primary_hcd) return 1; return hcd == hcd->primary_hcd; }That's just a symptom, not the real cause of the bug. You need to fix the real cause: the
shared_hcdhas to be released _before_ theprimary_hcd.The right way to do this is to make the
shared_hcdtake a reference to theprimary_hcd. This reference should be dropped whenhcd_release()is called for theshared_hcd.Alan Stern
Quoted 23 lines · Chung-Geol Kim
Yes, you are right, The presentational errors in order to obtain an understanding of the process. Therefore, I will be happy to explain again the diagrammatic representation as shown below. If using usb 3.0 storage(OTG), you can see as below.
==============================================
At *Insert USB(3.0) Storage
sequence <1> --> <5>
==============================================
VOLD
=================================|============(uevent) ______|___________ |<5> | | SCSI | |usb_get_hcd | |shared_hcd(kref=3)| |__________________| ___________________ ________|_________ |<2> | |<4> | |dwc3_otg_sm_work | |dwc3_otg_sm_work | |usb_get_hcd | |usb_get_hcd | |primary_hcd(kref=2)| |shared_hcd(kref=2)| |___________________| |__________________|
Quoted 9 lines
_________|_________ ________|_________ |<1> | |<3> | |New USB BUS #1 | |New USB BUS #2 | |usb_create_hcd | |usb_create_hcd | |primary_hcd(kref=1)| |shared_hcd(kref=1)| | | | | |bandXX_mutex(alloc)|<-(Link)-bandXX_mutex | |___________________| |__________________|
When people present diagrams like this, the universal convention is to show earlier times at the top and later times at the bottom. That way the order in which you read the diagram is the same as the order in which the events are supposed to occur.
Also, the convention is to put events next to each other if they happen at the same time. In your diagram, <1> and <3> are next to each other but they don't happen at the same time.
Quoted 67 lines
==============================================
At *remove USB(3.0) Storage
sequence <1> --> <5> ((Normal Case))
==============================================
VOLD
=================================|============(uevent) ______|___________ |<1> | | SCSI | |usb_put_hcd | |shared_hcd(kref=2)| |__________________| ___________________ ________|_________ |<4> | |<2> | |dwc3_otg_sm_work | |dwc3_otg_sm_work | |usb_put_hcd | |usb_put_hcd | |primary_hcd(kref=1)| |shared_hcd(kref=1)| |___________________| |__________________| _________|_________ ________|_________ |<5> | |<3> | |New USB BUS #1 | |New USB BUS #2 | |hcd_release | |hcd_release | |primary_hcd(kref=0)| |shared_hcd(kref=0)| | | | | |bandXX_mutex(free) | -X-cut off)-bandXX_mutex| |___________________| |__________________|----------------------------------------------
Quoted 2 lines
The real bug here is that the
shared_hcdis released after theprimary_hcd. That's what you need to fix.NO, It 's only depend on vold(scsi) release time. If the vold later released and is being released first hcd, Double free happened at <5> as below.
==============================================
At *remove USB(3.0) Storage
sequence <1> --> <5> ((Problem Case))
==============================================
VOLD
=================================|============(uevent) ______|___________ |<5> | | SCSI | |usb_put_hcd | |shared_hcd(kref=0)| |*hcd_release | |bandXX_mutex(free*)|<- double free |__________________| ___________________ ________|_________ |<3> | |<1> | |dwc3_otg_sm_work | |dwc3_otg_sm_work | |usb_put_hcd | |usb_put_hcd | |primary_hcd(kref=1)| |shared_hcd(kref=2)| |___________________| |__________________| _________|_________ ________|_________ |<4> | |<2> | |New USB BUS #1 | |New USB BUS #2 | |hcd_release | | | |primary_hcd(kref=0)| |shared_hcd(kref=1)| | | | | |bandXX_mutex(free) |<-(Link)-bandXX_mutex | |___________________| |__________________|
Quoted 1 line
----------------------------------------------
That's the same as what I said. In your diagram, vold releases the shared_hcd (in <5>) after dwc3_otg_sm_work releases the primary_hcd (in <4>).
If you change the code so that the shared_hcd takes a reference to the primary_hcd and drops that reference when the shared_hcd is released, this will never occur.
Alan Stern