• Rafael J. Wysocki's avatar
    ACPI / hotplug / PCI: Fix bridge removal race in handle_hotplug_event() · 1b360f44
    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
         put_bridge
          free_bridge
           acpiphp_put_context (for each child, under context lock)
            kfree (child context)
    
    Now, if a hotplug notify is dispatched for one of the bridge's
    children and the timing is such that handle_hotplug_event() for
    that notify is executed while free_bridge() above is running,
    the get_bridge(context->func.parent) in handle_hotplug_event()
    will not really help, because it is too late to prevent the bridge
    from going away and the child's context may be freed before
    hotplug_event_work() scheduled from handle_hotplug_event()
    dereferences the pointer to it passed via the data argument.
    That will cause a kernel crash to happpen in hotplug_event_work().
    
    To prevent that from happening, make handle_hotplug_event()
    check the is_going_away flag of the function's parent bridge
    (under acpiphp_context_lock) and bail out if it's set.  Also,
    make cleanup_bridge() set the bridge's is_going_away flag under
    acpiphp_context_lock so that it cannot be changed between the
    check and the subsequent get_bridge(context->func.parent) in
    handle_hotplug_event().
    
    Then, in the above scenario, handle_hotplug_event() will notice
    that context->func.parent->is_going_away is already set and it
    will exit immediately preventing the crash from happening.
    Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
    1b360f44
acpiphp_glue.c 28.9 KB