Linux PCI subsystem development
 help / color / mirror / Atom feed
* [PATCH] PCI: endpoint: pci-epf-test: Free doorbell IRQ on unbind and deinit
@ 2026-09-05  1:45 Fan Wu
  2026-09-05  2:00 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Fan Wu @ 2026-09-05  1:45 UTC (permalink / raw)
  To: linux-pci
  Cc: mani, kwilczynski, kishon, Frank.Li, den, linux-kernel, stable,
	Fan Wu, Song Li

Neither pci_epf_test_unbind() nor pci_epf_test_epc_deinit() frees the
doorbell IRQ. epc_deinit() clears the BARs, while unbind() can free the
BAR backing store; if the doorbell is enabled, its IRQ action remains
registered with epf_test as dev_id and epf->db_msg stays allocated. A
doorbell interrupt may already have awakened the threaded handler when
teardown starts, and since cancel_delayed_work_sync() drains only the
command worker and clear_bar() does not wait for the IRQ thread,
unbind() can free that backing while the handler still dereferences
epf_test->reg[]. The leftover IRQ also makes the next doorbell
allocation on the same function fail with -EBUSY.

Free the doorbell IRQ in both paths, guarded by a
doorbell_irq_registered flag set once request_threaded_irq() has
succeeded and cleared in pci_epf_test_doorbell_cleanup(), the
chokepoint shared by the enable error path, disable_doorbell() and the
teardown sites, so no disarm path can double-free. In unbind(), the
drain depends on the driver's own flag, not on epc->init_complete.

This issue was found by an in-house static analysis tool.

Fixes: eff0c286aa91 ("PCI: endpoint: pci-epf-test: Add doorbell test support")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6
Co-developed-by: Song Li <songl@zju.edu.cn>
Signed-off-by: Song Li <songl@zju.edu.cn>
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
 drivers/pci/endpoint/functions/pci-epf-test.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
index d4905aa..ab8df91 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -95,6 +95,7 @@ struct pci_epf_test {
 	const struct pci_epc_features *epc_features;
 	struct pci_epf_bar	db_bar;
 	bool			db_bar_programmed;
+	bool			doorbell_irq_registered;
 	size_t			bar_size[PCI_STD_NUM_BARS];
 };
 
@@ -721,6 +722,7 @@ static void pci_epf_test_doorbell_cleanup(struct pci_epf_test *epf_test)
 	struct pci_epf *epf = epf_test->epf;
 
 	reg->doorbell_bar = cpu_to_le32(NO_BAR);
+	epf_test->doorbell_irq_registered = false;
 
 	pci_epf_free_doorbell(epf);
 }
@@ -772,6 +774,7 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
 		goto err_doorbell_cleanup;
 	}
 
+	epf_test->doorbell_irq_registered = true;
 	reg->doorbell_data = cpu_to_le32(msg->data);
 	reg->doorbell_bar = cpu_to_le32(bar);
 
@@ -1238,6 +1241,10 @@ static void pci_epf_test_epc_deinit(struct pci_epf *epf)
 
 	cancel_delayed_work_sync(&epf_test->cmd_handler);
 	pci_epf_test_clean_dma_chan(epf_test);
+	if (epf_test->doorbell_irq_registered) {
+		free_irq(epf->db_msg[0].virq, epf_test);
+		pci_epf_test_doorbell_cleanup(epf_test);
+	}
 	pci_epf_test_clear_bar(epf);
 }
 
@@ -1374,6 +1381,10 @@ static void pci_epf_test_unbind(struct pci_epf *epf)
 	struct pci_epc *epc = epf->epc;
 
 	cancel_delayed_work_sync(&epf_test->cmd_handler);
+	if (epf_test->doorbell_irq_registered) {
+		free_irq(epf->db_msg[0].virq, epf_test);
+		pci_epf_test_doorbell_cleanup(epf_test);
+	}
 	if (epc->init_complete) {
 		pci_epf_test_clean_dma_chan(epf_test);
 		pci_epf_test_clear_bar(epf);


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] PCI: endpoint: pci-epf-test: Free doorbell IRQ on unbind and deinit
  2026-09-05  1:45 [PATCH] PCI: endpoint: pci-epf-test: Free doorbell IRQ on unbind and deinit Fan Wu
@ 2026-09-05  2:00 ` sashiko-bot
  0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-09-05  2:00 UTC (permalink / raw)
  To: Fan Wu; +Cc: linux-pci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [Critical] pci_epf_test_disable_doorbell() does not check the new doorbell_irq_registered flag, allowing a malicious host to trigger a NULL pointer dereference or double-free.
- [High] Module exit routine destroys workqueue before unregistering the driver, leading to a Use-After-Free.
--

commit dc35dc30c72a974fd9de8195ba6c7cac69c49ed9
Author: Fan Wu <fanwu01@zju.edu.cn>

PCI: endpoint: pci-epf-test: Free doorbell IRQ on unbind and deinit

This commit resolves a missing IRQ teardown in the endpoint teardown paths.
It introduces a new doorbell_irq_registered flag to ensure the doorbell IRQ 
is correctly freed on unbind and deinit, preventing EBUSY on subsequent 
doorbell allocations.

> diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
> index d4905aa8e4c01..ab8df91891cec 100644
> --- a/drivers/pci/endpoint/functions/pci-epf-test.c
> +++ b/drivers/pci/endpoint/functions/pci-epf-test.c
[ ... ]
> @@ -772,6 +774,7 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
>  		goto err_doorbell_cleanup;
>  	}
>  
> +	epf_test->doorbell_irq_registered = true;
>  	reg->doorbell_data = cpu_to_le32(msg->data);
>  	reg->doorbell_bar = cpu_to_le32(bar);
>  

[Severity: Critical]
This is a pre-existing issue, but since this patch introduces the
doorbell_irq_registered flag, could it also be used in
pci_epf_test_disable_doorbell() to prevent a crash?

A PCIe host can write a valid BAR index directly to reg->doorbell_bar and
issue COMMAND_DISABLE_DOORBELL without a prior successful
COMMAND_ENABLE_DOORBELL.

When this happens, pci_epf_test_disable_doorbell() reads the bar value
directly from host-writable memory:

drivers/pci/endpoint/functions/pci-epf-test.c:pci_epf_test_disable_doorbell() {
	enum pci_barno bar = le32_to_cpu(reg->doorbell_bar);
	...
	if (bar < BAR_0)
		goto set_status_err;

	free_irq(epf->db_msg[0].virq, epf_test);
	...
}

Because the host provided a valid index, the sanity check is bypassed.

Since epf->db_msg is only allocated during enable, it remains unallocated.

Does unconditionally dereferencing epf->db_msg without checking
epf_test->doorbell_irq_registered allow a malicious host to trigger a NULL
pointer dereference or double-free?

Since a PCIe endpoint must robustly handle host inputs, could this lead to a
locally triggered remote DoS?

[ ... ]
> @@ -1374,6 +1381,10 @@ static void pci_epf_test_unbind(struct pci_epf *epf)
>  	struct pci_epc *epc = epf->epc;
>  
>  	cancel_delayed_work_sync(&epf_test->cmd_handler);
> +	if (epf_test->doorbell_irq_registered) {
> +		free_irq(epf->db_msg[0].virq, epf_test);
> +		pci_epf_test_doorbell_cleanup(epf_test);
> +	}
>  	if (epc->init_complete) {
>  		pci_epf_test_clean_dma_chan(epf_test);
>  		pci_epf_test_clear_bar(epf);

[Severity: High]
This isn't a bug introduced by this patch, but there appears to be a
use-after-free risk during module unload related to this teardown path.

In the module exit routine, the workqueue is destroyed before the driver
is unregistered:

drivers/pci/endpoint/functions/pci-epf-test.c:pci_epf_test_exit() {
	if (kpcitest_workqueue)
		destroy_workqueue(kpcitest_workqueue);
	pci_epf_unregister_driver(&test_driver);
}

If an interrupt fires or a delayed work reschedules itself before
pci_epf_unregister_driver() fully unbinds the endpoints, could the system
attempt to queue work to a freed workqueue?

Unbinding also issues cancel_delayed_work_sync() here.

Could this access invalid timer state since the workqueue was already
destroyed?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260905014525.420764-1-fanwu01@zju.edu.cn?part=1

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-09-05  2:00 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-05  1:45 [PATCH] PCI: endpoint: pci-epf-test: Free doorbell IRQ on unbind and deinit Fan Wu
2026-09-05  2:00 ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox