• Rafael J. Wysocki's avatar
    ACPI / hotplug / PCI: Fix bridge removal race vs dock events · af9d8adc
    Rafael J. Wysocki authored
    If a PCI bridge with an ACPIPHP context attached is removed via
    sysfs, the code path executed as a result is the following:
    
    pci_stop_and_remove_bus_device_locked
     pci_remove_bus
      pcibios_remove_bus
       acpi_pci_remove_bus
        acpiphp_remove_slots
         cleanup_bridge
          unregister_hotplug_dock_device (drops dock references to the bridge)
         put_bridge
          free_bridge
           acpiphp_put_context (for each child, under context lock)
            kfree (context)
    
    Now, if a dock event affecting one of the bridge's child devices
    occurs (roughly at the same time), it will lead to the following code
    path:
    
    acpi_dock_deferred_cb
     dock_notify
      handle_eject_request
       hot_remove_dock_devices
        dock_hotplug_event
         hotplug_event (dereferences context)
    
    That may lead to a kernel crash in hotplug_event() if it is executed
    after the last kfree() in the bridge removal code path.
    
    To prevent that from happening, add a wrapper around hotplug_event()
    called dock_event() and point the .handler pointer in acpiphp_dock_ops
    to it.  Make that wrapper retrieve the device's ACPIPHP context using
    acpiphp_get_context() (instead of taking it from the data argument)
    under acpiphp_context_lock and check if the parent bridge's
    is_going_away flag is set.  If that flag is set, it will return
    immediately and if it is not set it will grab a reference to the
    device's parent bridge before executing hotplug_event().
    
    Then, in the above scenario, the reference to the parent bridge
    held by dock_event() will prevent free_bridge() from being executed
    for it until hotplug_event() returns.
    Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
    af9d8adc
acpiphp_glue.c 29.4 KB