| From 281e878eab191cce4259abbbf1a0322e3adae02c Mon Sep 17 00:00:00 2001 |
| From: Lukas Wunner <lukas@wunner.de> |
| Date: Thu, 19 Jul 2018 17:27:32 -0500 |
| Subject: PCI: pciehp: Fix use-after-free on unplug |
| |
| From: Lukas Wunner <lukas@wunner.de> |
| |
| commit 281e878eab191cce4259abbbf1a0322e3adae02c upstream. |
| |
| When pciehp is unbound (e.g. on unplug of a Thunderbolt device), the |
| hotplug_slot struct is deregistered and thus freed before freeing the |
| IRQ. The IRQ handler and the work items it schedules print the slot |
| name referenced from the freed structure in various informational and |
| debug log messages, each time resulting in a quadruple dereference of |
| freed pointers (hotplug_slot -> pci_slot -> kobject -> name). |
| |
| At best the slot name is logged as "(null)", at worst kernel memory is |
| exposed in logs or the driver crashes: |
| |
| pciehp 0000:10:00.0:pcie204: Slot((null)): Card not present |
| |
| An attacker may provoke the bug by unplugging multiple devices on a |
| Thunderbolt daisy chain at once. Unplugging can also be simulated by |
| powering down slots via sysfs. The bug is particularly easy to trigger |
| in poll mode. |
| |
| It has been present since the driver's introduction in 2004: |
| https://git.kernel.org/tglx/history/c/c16b4b14d980 |
| |
| Fix by rearranging teardown such that the IRQ is freed first. Run the |
| work items queued by the IRQ handler to completion before freeing the |
| hotplug_slot struct by draining the work queue from the ->release_slot |
| callback which is invoked by pci_hp_deregister(). |
| |
| Signed-off-by: Lukas Wunner <lukas@wunner.de> |
| Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> |
| Cc: stable@vger.kernel.org # v2.6.4 |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/pci/hotplug/pciehp.h | 1 + |
| drivers/pci/hotplug/pciehp_core.c | 7 +++++++ |
| drivers/pci/hotplug/pciehp_hpc.c | 5 ++--- |
| 3 files changed, 10 insertions(+), 3 deletions(-) |
| |
| --- a/drivers/pci/hotplug/pciehp.h |
| +++ b/drivers/pci/hotplug/pciehp.h |
| @@ -132,6 +132,7 @@ int pciehp_unconfigure_device(struct slo |
| void pciehp_queue_pushbutton_work(struct work_struct *work); |
| struct controller *pcie_init(struct pcie_device *dev); |
| int pcie_init_notification(struct controller *ctrl); |
| +void pcie_shutdown_notification(struct controller *ctrl); |
| int pciehp_enable_slot(struct slot *p_slot); |
| int pciehp_disable_slot(struct slot *p_slot); |
| void pcie_reenable_notification(struct controller *ctrl); |
| --- a/drivers/pci/hotplug/pciehp_core.c |
| +++ b/drivers/pci/hotplug/pciehp_core.c |
| @@ -76,6 +76,12 @@ static int reset_slot(struct hotplug_slo |
| */ |
| static void release_slot(struct hotplug_slot *hotplug_slot) |
| { |
| + struct slot *slot = hotplug_slot->private; |
| + |
| + /* queued work needs hotplug_slot name */ |
| + cancel_delayed_work(&slot->work); |
| + drain_workqueue(slot->wq); |
| + |
| kfree(hotplug_slot->ops); |
| kfree(hotplug_slot->info); |
| kfree(hotplug_slot); |
| @@ -278,6 +284,7 @@ static void pciehp_remove(struct pcie_de |
| { |
| struct controller *ctrl = get_service_data(dev); |
| |
| + pcie_shutdown_notification(ctrl); |
| cleanup_slot(ctrl); |
| pciehp_release_ctrl(ctrl); |
| } |
| --- a/drivers/pci/hotplug/pciehp_hpc.c |
| +++ b/drivers/pci/hotplug/pciehp_hpc.c |
| @@ -786,7 +786,7 @@ int pcie_init_notification(struct contro |
| return 0; |
| } |
| |
| -static void pcie_shutdown_notification(struct controller *ctrl) |
| +void pcie_shutdown_notification(struct controller *ctrl) |
| { |
| if (ctrl->notification_enabled) { |
| pcie_disable_notification(ctrl); |
| @@ -821,7 +821,7 @@ abort: |
| static void pcie_cleanup_slot(struct controller *ctrl) |
| { |
| struct slot *slot = ctrl->slot; |
| - cancel_delayed_work(&slot->work); |
| + |
| destroy_workqueue(slot->wq); |
| kfree(slot); |
| } |
| @@ -902,7 +902,6 @@ abort: |
| |
| void pciehp_release_ctrl(struct controller *ctrl) |
| { |
| - pcie_shutdown_notification(ctrl); |
| pcie_cleanup_slot(ctrl); |
| kfree(ctrl); |
| } |