pci: aer: wait till the workqueue completes before free memory
PATCH 1/1 pci: aer: wait till the workqueue completes before free memory
I start a binary which should flash the FPGA and re-enumare the PCI-BUS and find a new device. It works most of the time. With SLUB debug it crashes on each iteration with something like this (compressed output):
| pcieport 0000:00:00.0: AER: Multiple Corrected error received: id=0000 | Unable to handle kernel paging request for data at address 0x27ef9e3e | Faulting instruction address: 0x602f5328 | Oops: Kernel access of bad area, sig: 11 [#1] | Workqueue: events aer_isr | GPR24: dd6aa000 6b6b6b6b 605f8378 605f8360 d99b12c0 604fc674 606b1704 d99b12c0 | NIP [602f5328] pci_walk_bus+0xd4/0x104
Register 25 has the user-after magic. As it turns out, the old PCIe device is leaving, generates an error before it left, aer_irq() is fired, it schedules a work item. What happens now is that free_irq() is invoked, all resources are gone *before* the aes_isr() work item is completed.
So to fix this, I flush the workqueue to ensure that there is no more work pending.
---
Bjorn, this could deserve a stable tag. However it seems to have been like that even in v2.6.20.
drivers/pci/pcie/aer/aerdrv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/pci/pcie/aer/aerdrv.c +3 -1
@@ -282,8 +282,10 @@ static void aer_remove(struct pcie_device *dev)if (rpc) {/* If register interrupt service, it must be free. */ -if (rpc->isr) +if (rpc->isr) {free_irq (dev->irq, dev); +flush_work (&rpc->dpc_handler); + }wait_event (rpc->wait_release, rpc->prod_idx == rpc->cons_idx);
Signature · 1 line
2.6.4
Hi Sebastian,
Quoted 33 lines · Sebastian Andrzej Siewior
I start a binary which should flash the FPGA and re-enumare the PCI-BUS and find a new device. It works most of the time. With SLUB debug it crashes on each iteration with something like this (compressed output):
| pcieport 0000:00:00.0: AER: Multiple Corrected error received: id=0000 | Unable to handle kernel paging request for data at address 0x27ef9e3e | Faulting instruction address: 0x602f5328 | Oops: Kernel access of bad area, sig: 11 [#1] | Workqueue: events aer_isr | GPR24: dd6aa000 6b6b6b6b 605f8378 605f8360 d99b12c0 604fc674 606b1704 d99b12c0 | NIP [602f5328] pci_walk_bus+0xd4/0x104Register 25 has the user-after magic. As it turns out, the old PCIe device is leaving, generates an error before it left,
aer_irq()is fired, it schedules a work item. What happens now is thatfree_irq()is invoked, all resources are gone *before* theaes_isr()work item is completed.
So to fix this, I flush the workqueue to ensure that there is no more work pending.Signed-off-by Sebastian Andrzej Siewior <[email protected]>---
Bjorn, this could deserve a stable tag. However it seems to have been like that even in v2.6.20.drivers/pci/pcie/aer/aerdrv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)drivers/pci/pcie/aer/aerdrv.c +0
-0
drivers/pci/pcie/aer/aerdrv.c static void aer_remove(struct pcie_device *dev) · line 290
Quoted 10 lines
drivers/pci/pcie/aer/aerdrv.c +3
-1@@ -282,8 +282,10 @@ static void aer_remove(struct pcie_device *dev)if (rpc) {/* If register interrupt service, it must be free. */ -if (rpc->isr) +if (rpc->isr) {free_irq (dev->irq, dev); +flush_work (&rpc->dpc_handler); + }wait_event (rpc->wait_release, rpc->prod_idx == rpc->cons_idx);
Your change looks reasonable. But I'm curious about the wait_event() just below it. That *looks* like it's intended to do the same thing as your flush_work().
Can you explain why the wait_event() isn't working? If we add the flush_work(), can we remove the wait_event() stuff?
Bjorn
Quoted 1 line · * Bjorn Helgaas | 2016-01-06 17:27:58 [-0600]:
Hi Sebastian,
Hi Bjorn,
Quoted 3 lines
Your change looks reasonable. But I'm curious about the
wait_event()just below it. That *looks* like it's intended to do the same thing as yourflush_work().
Indeed.
Quoted 1 line
Can you explain why the
wait_event()isn't working? If we add the
aer_isr() invokes get_e_source() which increments rpc->cons_idx. So the condition is valid after that and the function does not terminate yes it invokes aer_isr_one_error().
That means if we have one CPU doing the ISR + workqueue task and another CPU doing the aer_remove() removal thingy then the latter CPU evaluates the condition to true and continues cleanup while the former is still in aer_isr_one_error() wondering where the memory went.
Quoted 1 line
flush_work(), can we remove thewait_event()stuff?
I think so since its only purpose is to sync against removal which does not work on SMP. So let me remove this and the wait_release member.
Quoted 1 line
Bjorn
Sebastian
PATCH v2 1/1 pci: aer: wait till the workqueue completes before free memory
In reply to Bjorn HelgaasI start a binary which should flash the FPGA and re-enumare the PCI-BUS and find a new device. It works most of the time. With SLUB debug it crashes on each iteration with something like this (compressed output):
| pcieport 0000:00:00.0: AER: Multiple Corrected error received: id=0000 | Unable to handle kernel paging request for data at address 0x27ef9e3e | Faulting instruction address: 0x602f5328 | Oops: Kernel access of bad area, sig: 11 [#1] | Workqueue: events aer_isr | GPR24: dd6aa000 6b6b6b6b 605f8378 605f8360 d99b12c0 604fc674 606b1704 d99b12c0 | NIP [602f5328] pci_walk_bus+0xd4/0x104
Register 25 has the user-after magic. As it turns out, the old PCIe device is leaving, generates an error before it left, aer_irq() is fired, it schedules a work item. What happens now is that free_irq() is invoked, all resources are gone *before* the aes_isr() work item is completed.
So to fix this, I flush the workqueue to ensure that there is no more work pending.
The wait_event() on wait_release should actually synchronized against removal. However the condition (->prod_idx == ->cons_idx) is made true before the function completes (aer_isr_one_error() is invoked right after that) so it does not fulfill its purpose. Therefore I remove it.
---
v1…v2:
- remove wait_release since it is broken on SMP
- don't flush the workqueue only if we have ->isr set because the workqueue could be scheduled via the inject module.
*compile* tested only because I don't have the HW at the moment.
Bjorn, this could deserve a stable tag. However it seems to have been like that even in v2.6.20.
drivers/pci/pcie/aer/aerdrv.c | 4 +--- drivers/pci/pcie/aer/aerdrv.h | 1 - drivers/pci/pcie/aer/aerdrv_core.c | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-)
drivers/pci/pcie/aer/aerdrv.c +1 -3
@@ -262,7 +262,6 @@ static struct aer_rpc *aer_alloc_rpc(struct pcie_device *dev) rpc->rpd = dev;INIT_WORK (&rpc->dpc_handler, aer_isr);mutex_init (&rpc->rpc_mutex); -init_waitqueue_head (&rpc->wait_release);/* Use PCIe bus function to store rpc into PCIe device */ set_service_data (dev, rpc);
@@ -285,8 +284,7 @@ static void aer_remove(struct pcie_device *dev)if (rpc->isr)free_irq (dev->irq, dev); -wait_event (rpc->wait_release, rpc->prod_idx == rpc->cons_idx); - +flush_work (&rpc->dpc_handler);aer_disable_rootport (rpc);kfree (rpc);set_service_data (dev,NULL );
drivers/pci/pcie/aer/aerdrv.h +0 -1
@@ -72,7 +72,6 @@ struct aer_rpc {* recovery on the same * root port hierarchy */ -wait_queue_head_t wait_release; };struct aer_broadcast_data {
drivers/pci/pcie/aer/aerdrv_core.c +0 -2
@@ -811,8 +811,6 @@ void aer_isr(struct work_struct *work)while (get_e_source (rpc, &e_src))aer_isr_one_error (p_device, &e_src);mutex_unlock (&rpc->rpc_mutex); - -wake_up (&rpc->wait_release); }/**
Signature · 1 line
2.7.0.rc3
Hi Sebastian,
Quoted 24 lines · Sebastian Andrzej Siewior
I start a binary which should flash the FPGA and re-enumare the PCI-BUS and find a new device. It works most of the time. With SLUB debug it crashes on each iteration with something like this (compressed output):
| pcieport 0000:00:00.0: AER: Multiple Corrected error received: id=0000 | Unable to handle kernel paging request for data at address 0x27ef9e3e | Faulting instruction address: 0x602f5328 | Oops: Kernel access of bad area, sig: 11 [#1] | Workqueue: events aer_isr | GPR24: dd6aa000 6b6b6b6b 605f8378 605f8360 d99b12c0 604fc674 606b1704 d99b12c0 | NIP [602f5328] pci_walk_bus+0xd4/0x104Register 25 has the user-after magic. As it turns out, the old PCIe device is leaving, generates an error before it left,
aer_irq()is fired, it schedules a work item. What happens now is thatfree_irq()is invoked, all resources are gone *before* theaes_isr()work item is completed.
So to fix this, I flush the workqueue to ensure that there is no more work pending.
Thewait_event()onwait_releaseshould actually synchronized against removal. However the condition (->prod_idx == ->cons_idx) is made true before the function completes (aer_isr_one_error()is invoked right after that) so it does not fulfill its purpose. Therefore I remove it.
Quoted 1 line
Signed-off-by Sebastian Andrzej Siewior <[email protected]>
I propose to merge this patch unchanged, but with the following changelog. I want to add a bit more detail about the concurrency problem and remove a bit of the specific detail about your FPGA:
commit 9963c9487f733ef8fe3a06ce3398072a40f955bf
Author: Sebastian Andrzej Siewior <[email protected]> Date: Fri Jan 15 19:36:25 2016 +0100
PCI/AER: Flush workqueue on device remove to avoid use-after-free
A Root Port's AER structure (rpc) contains a queue of events. aer_irq() enqueues AER status information and schedules aer_isr() to dequeue and process it. When we remove a device, aer_remove() waits for the queue to be empty, then frees the rpc struct.
But aer_isr() references the rpc struct after dequeueing and possibly emptying the queue, which can cause a use-after-free error as in the following scenario with two threads, aer_isr() on the left and a concurrent aer_remove() on the right:
Thread A Thread B
-------- --------
aer_irq():
rpc->prod_idx++
aer_remove():
wait_event(rpc->prod_idx == rpc->cons_idx)
# now blocked until queue becomes empty
aer_isr(): # ...
rpc->cons_idx++ # unblocked because queue is now empty
... kfree(rpc)
mutex_unlock(&rpc->rpc_mutex)Wait until the last scheduled instance of aer_isr() has completed before freeing the rpc struct by using flush_work() in aer_remove().
I reproduced this use-after-free by flashing a device FPGA and re-enumerating the bus to find the new device. With SLUB debug, this crashes with 0x6b bytes (POISON_FREE, the use-after-free magic number) in GPR25:
pcieport 0000:00:00.0: AER: Multiple Corrected error received: id=0000
Unable to handle kernel paging request for data at address 0x27ef9e3e Workqueue: events aer_isr GPR24: dd6aa000 6b6b6b6b 605f8378 605f8360 d99b12c0 604fc674 606b1704 d99b12c0 NIP [602f5328] pci_walk_bus+0xd4/0x104
Quoted 1 line · Bjorn Helgaas
Hi Sebastian,
Hi Bjorn,
Quoted 3 lines
I propose to merge this patch unchanged, but with the following changelog. I want to add a bit more detail about the concurrency problem and remove a bit of the specific detail about your FPGA:
perfect, thanks.
Sebastian
Bjorn Helgaas
AppliedQuoted 71 lines · Bjorn Helgaas
Hi Sebastian,
Quoted 24 lines · Sebastian Andrzej Siewior
I start a binary which should flash the FPGA and re-enumare the PCI-BUS and find a new device. It works most of the time. With SLUB debug it crashes on each iteration with something like this (compressed output):
| pcieport 0000:00:00.0: AER: Multiple Corrected error received: id=0000 | Unable to handle kernel paging request for data at address 0x27ef9e3e | Faulting instruction address: 0x602f5328 | Oops: Kernel access of bad area, sig: 11 [#1] | Workqueue: events aer_isr | GPR24: dd6aa000 6b6b6b6b 605f8378 605f8360 d99b12c0 604fc674 606b1704 d99b12c0 | NIP [602f5328] pci_walk_bus+0xd4/0x104Register 25 has the user-after magic. As it turns out, the old PCIe device is leaving, generates an error before it left,
aer_irq()is fired, it schedules a work item. What happens now is thatfree_irq()is invoked, all resources are gone *before* theaes_isr()work item is completed.
So to fix this, I flush the workqueue to ensure that there is no more work pending.
Thewait_event()onwait_releaseshould actually synchronized against removal. However the condition (->prod_idx == ->cons_idx) is made true before the function completes (aer_isr_one_error()is invoked right after that) so it does not fulfill its purpose. Therefore I remove it.Quoted 1 line
Signed-off-by Sebastian Andrzej Siewior <[email protected]>I propose to merge this patch unchanged, but with the following changelog. I want to add a bit more detail about the concurrency problem and remove a bit of the specific detail about your FPGA:
commit
9963c9487f733ef8fe3a06ce3398072a40f955bfAuthor: Sebastian Andrzej Siewior <[email protected]> Date: Fri Jan 15 19:36:25 2016 +0100PCI/AER: Flush workqueue on device remove to avoid use-after-free
A Root Port's AER structure (rpc) contains a queue of events.
aer_irq()enqueues AER status information and schedulesaer_isr()to dequeue and process it. When we remove a device,aer_remove()waits for the queue to be empty, then frees the rpc struct.But
aer_isr()references the rpc struct after dequeueing and possibly emptying the queue, which can cause a use-after-free error as in the following scenario with two threads,aer_isr()on the left and a concurrentaer_remove()on the right:Thread A Thread B
-------- --------
aer_irq():rpc->prod_idx++ aer_remove(): wait_event(rpc->prod_idx == rpc->cons_idx) # now blocked until queue becomes empty aer_isr(): # ... rpc->cons_idx++ # unblocked because queue is now empty ... kfree(rpc) mutex_unlock(&rpc->rpc_mutex)Wait until the last scheduled instance of
aer_isr()has completed before freeing the rpc struct by usingflush_work()inaer_remove().I reproduced this use-after-free by flashing a device FPGA and re-enumerating the bus to find the new device. With SLUB debug, this crashes with
0x6bbytes (POISON_FREE, the use-after-free magic number) in GPR25:pcieport 0000:00:00.0: AER: Multiple Corrected error received: id=0000
Quoted 8 lines
Unable to handle kernel paging request for data at address 0x27ef9e3e Workqueue: events aer_isr GPR24: dd6aa000 6b6b6b6b 605f8378 605f8360 d99b12c0 604fc674 606b1704 d99b12c0 NIP [602f5328] pci_walk_bus+0xd4/0x104[bhelgaas: changelog]Signed-off-by Sebastian Andrzej Siewior <[email protected]>Signed-off-by Bjorn Helgaas <[email protected]>
Applied to for-linus for v4.5 with stable tag, thanks!