diff options
| author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-02-03 22:30:15 +0100 | 
|---|---|---|
| committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-02-03 22:30:15 +0100 | 
| commit | af9d8adc6b832003bbe3d83fde665ae6b4f072eb (patch) | |
| tree | a5f7dd4f9875e5c71894768aa3af97e6a242ac9f | |
| parent | 1b360f44d009059e446532f29c1a889951e72667 (diff) | |
| download | linux-af9d8adc6b832003bbe3d83fde665ae6b4f072eb.tar.bz2 | |
ACPI / hotplug / PCI: Fix bridge removal race vs dock events
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: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
| -rw-r--r-- | drivers/pci/hotplug/acpiphp_glue.c | 21 | 
1 files changed, 20 insertions, 1 deletions
| diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index 91eceaf3131b..e2a783fdb98f 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -210,10 +210,29 @@ static void post_dock_fixups(acpi_handle not_used, u32 event, void *data)  	}  } +static void dock_event(acpi_handle handle, u32 type, void *data) +{ +	struct acpiphp_context *context; + +	mutex_lock(&acpiphp_context_lock); +	context = acpiphp_get_context(handle); +	if (!context || WARN_ON(context->handle != handle) +	    || context->func.parent->is_going_away) { +		mutex_unlock(&acpiphp_context_lock); +		return; +	} +	get_bridge(context->func.parent); +	acpiphp_put_context(context); +	mutex_unlock(&acpiphp_context_lock); + +	hotplug_event(handle, type, data); + +	put_bridge(context->func.parent); +}  static const struct acpi_dock_ops acpiphp_dock_ops = {  	.fixup = post_dock_fixups, -	.handler = hotplug_event, +	.handler = dock_event,  };  /* Check whether the PCI device is managed by native PCIe hotplug driver */ |