All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] PCI: Fix use-after-free race in pci_find_bus()
@ 2026-08-12  2:47 Mohamad Raizudeen
  2026-08-12  2:59 ` sashiko-bot
  2026-08-12  3:00 ` Greg KH
  0 siblings, 2 replies; 5+ messages in thread
From: Mohamad Raizudeen @ 2026-08-12  2:47 UTC (permalink / raw)
  To: bhelgaas, gregkh
  Cc: skhan, jkoolstra, linux-pci, linux-kernel, Mohamad Raizudeen

pci_find_bus() iterates over the list of PCI root buses using
pci_find_next_bus(). This helper acquires pci_bus_sem, retrieves the
next bus and drops the lock before returning the pointer to the caller.

pci_find_bus() then uses this pointer to check the domain and traverses
the child buses via pci_do_find_bus() without holding the pci_bus_sem
lock.

If a PCI bus is concurrently removed for example via hotplug between
loop iterations, the from pointer passed back into pci_find_next_bus()
becomes stale, leading to a user-after-free when dereferencing
from->node.next. Additionally, traversing the bus tree without holding
the lock is a race condition.

Fix this by iterating pci_root_buses list directly using
list_for_each_entry() inside pci_find_bus() while holding the
pci_bus_sem read lock for the entire duration of the search. This
ensures the list and tree structures cannot change while being
traversed, eliminating the use-after-free.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Mohamad Raizudeen <raizudeen.kerneldev@gmail.com>
---
 drivers/pci/search.c | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/drivers/pci/search.c b/drivers/pci/search.c
index e3d3177fce54..f50e83061b76 100644
--- a/drivers/pci/search.c
+++ b/drivers/pci/search.c
@@ -142,17 +142,19 @@ static struct pci_bus *pci_do_find_bus(struct pci_bus *bus, unsigned char busnr)
  */
 struct pci_bus *pci_find_bus(int domain, int busnr)
 {
-	struct pci_bus *bus = NULL;
-	struct pci_bus *tmp_bus;
+	struct pci_bus *bus;
+	struct pci_bus *tmp_bus = NULL;
 
-	while ((bus = pci_find_next_bus(bus)) != NULL)  {
-		if (pci_domain_nr(bus) != domain)
-			continue;
-		tmp_bus = pci_do_find_bus(bus, busnr);
-		if (tmp_bus)
-			return tmp_bus;
+	down_read(&pci_bus_sem);
+	list_for_each_entry(bus, &pci_root_buses, node) {
+		if (pci_domain_nr(bus) == domain) {
+			tmp_bus = pci_do_find_bus(bus, busnr);
+			if (tmp_bus)
+				break;
+		}
 	}
-	return NULL;
+	up_read(&pci_bus_sem);
+	return tmp_bus;
 }
 EXPORT_SYMBOL(pci_find_bus);
 
-- 
2.53.0


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

* Re: [PATCH] PCI: Fix use-after-free race in pci_find_bus()
  2026-08-12  2:47 [PATCH] PCI: Fix use-after-free race in pci_find_bus() Mohamad Raizudeen
@ 2026-08-12  2:59 ` sashiko-bot
  2026-08-12  3:00 ` Greg KH
  1 sibling, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-08-12  2:59 UTC (permalink / raw)
  To: Mohamad Raizudeen; +Cc: linux-pci

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

Pre-existing issues:
- [High] The fundamentally vulnerable pci_find_next_bus() iterator is left unmodified, retaining a use-after-free race.
- [High] pci_find_bus() returns a non-refcounted bus pointer after dropping pci_bus_sem, exposing callers to a caller-side use-after-free.
--

--- Patch [1]: [PATCH] PCI: Fix use-after-free race in pci_find_bus() ---
commit 7750909a991524c7f4e93f423648abcd167d3923
Author: Mohamad Raizudeen <raizudeen.kerneldev@gmail.com>

PCI: Fix use-after-free race in pci_find_bus()
    
pci_find_bus() iterates over the list of PCI root buses using
pci_find_next_bus(). If a PCI bus is concurrently removed via hotplug
between loop iterations, the from pointer passed back into
pci_find_next_bus() becomes stale. This commit fixes this by iterating
pci_root_buses list directly while holding the pci_bus_sem read lock.

> diff --git a/drivers/pci/search.c b/drivers/pci/search.c
> index e3d3177fce549..f50e83061b769 100644
> --- a/drivers/pci/search.c
> +++ b/drivers/pci/search.c
> @@ -142,17 +142,19 @@ static struct pci_bus *pci_do_find_bus(struct pci_bus *bus, unsigned char busnr)
>   */
>  struct pci_bus *pci_find_bus(int domain, int busnr)
>  {
> -	struct pci_bus *bus = NULL;
> -	struct pci_bus *tmp_bus;
> +	struct pci_bus *bus;
> +	struct pci_bus *tmp_bus = NULL;
>  
> -	while ((bus = pci_find_next_bus(bus)) != NULL)  {

[Severity: High]
This is a pre-existing issue, but while you are removing the use of
pci_find_next_bus() here, the pci_find_next_bus() function itself remains
vulnerable to the same use-after-free race.

Other lockless callers like uncore_die_to_segment() in
arch/x86/events/intel/uncore.c still iterate over buses using
pci_find_next_bus() without holding pci_lock_rescan_remove(). Because
pci_find_next_bus() drops pci_bus_sem before returning, a concurrent PCI
hot-remove can free the bus. 

When the caller passes the freed bus back into pci_find_next_bus() for the
next iteration, from->node.next is dereferenced, causing a use-after-free.

Should pci_find_next_bus() also be fixed to prevent memory corruption for
other callers?

> -		if (pci_domain_nr(bus) != domain)
> -			continue;
> -		tmp_bus = pci_do_find_bus(bus, busnr);
> -		if (tmp_bus)
> -			return tmp_bus;
> +	down_read(&pci_bus_sem);
> +	list_for_each_entry(bus, &pci_root_buses, node) {
> +		if (pci_domain_nr(bus) == domain) {
> +			tmp_bus = pci_do_find_bus(bus, busnr);
> +			if (tmp_bus)
> +				break;
> +		}
>  	}
> -	return NULL;
> +	up_read(&pci_bus_sem);
> +	return tmp_bus;

[Severity: High]
This is a pre-existing issue, but does this expose the caller to a
use-after-free race?

By dropping pci_bus_sem before returning tmp_bus, the returned bus pointer
is un-refcounted and no longer protected by the lock. If the caller does
not hold pci_lock_rescan_remove(), a concurrent hot-remove thread could
immediately acquire pci_bus_sem for write, remove the bus, and free its
memory before the caller has a chance to safely use it.

Would it be safer to increment the reference count of the returned bus
with pci_bus_get() before releasing pci_bus_sem?

>  }
>  EXPORT_SYMBOL(pci_find_bus);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260812024713.4958-1-raizudeen.kerneldev@gmail.com?part=1

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

* Re: [PATCH] PCI: Fix use-after-free race in pci_find_bus()
  2026-08-12  2:47 [PATCH] PCI: Fix use-after-free race in pci_find_bus() Mohamad Raizudeen
  2026-08-12  2:59 ` sashiko-bot
@ 2026-08-12  3:00 ` Greg KH
  2026-08-12  7:26   ` Mohamad Raizudeen
  1 sibling, 1 reply; 5+ messages in thread
From: Greg KH @ 2026-08-12  3:00 UTC (permalink / raw)
  To: Mohamad Raizudeen; +Cc: bhelgaas, skhan, jkoolstra, linux-pci, linux-kernel

On Wed, Aug 12, 2026 at 08:17:13AM +0530, Mohamad Raizudeen wrote:
> pci_find_bus() iterates over the list of PCI root buses using
> pci_find_next_bus(). This helper acquires pci_bus_sem, retrieves the
> next bus and drops the lock before returning the pointer to the caller.
> 
> pci_find_bus() then uses this pointer to check the domain and traverses
> the child buses via pci_do_find_bus() without holding the pci_bus_sem
> lock.
> 
> If a PCI bus is concurrently removed for example via hotplug between
> loop iterations, the from pointer passed back into pci_find_next_bus()
> becomes stale, leading to a user-after-free when dereferencing
> from->node.next. Additionally, traversing the bus tree without holding
> the lock is a race condition.

Did you find this actually happens?  How did you find this at all?

> 
> Fix this by iterating pci_root_buses list directly using
> list_for_each_entry() inside pci_find_bus() while holding the
> pci_bus_sem read lock for the entire duration of the search. This
> ensures the list and tree structures cannot change while being
> traversed, eliminating the use-after-free.

Why do two changes here, and not just make a patch series?

> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Signed-off-by: Mohamad Raizudeen <raizudeen.kerneldev@gmail.com>
> ---
>  drivers/pci/search.c | 20 +++++++++++---------
>  1 file changed, 11 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/pci/search.c b/drivers/pci/search.c
> index e3d3177fce54..f50e83061b76 100644
> --- a/drivers/pci/search.c
> +++ b/drivers/pci/search.c
> @@ -142,17 +142,19 @@ static struct pci_bus *pci_do_find_bus(struct pci_bus *bus, unsigned char busnr)
>   */
>  struct pci_bus *pci_find_bus(int domain, int busnr)
>  {
> -	struct pci_bus *bus = NULL;
> -	struct pci_bus *tmp_bus;
> +	struct pci_bus *bus;
> +	struct pci_bus *tmp_bus = NULL;
>  
> -	while ((bus = pci_find_next_bus(bus)) != NULL)  {
> -		if (pci_domain_nr(bus) != domain)
> -			continue;
> -		tmp_bus = pci_do_find_bus(bus, busnr);
> -		if (tmp_bus)
> -			return tmp_bus;
> +	down_read(&pci_bus_sem);
> +	list_for_each_entry(bus, &pci_root_buses, node) {
> +		if (pci_domain_nr(bus) == domain) {
> +			tmp_bus = pci_do_find_bus(bus, busnr);
> +			if (tmp_bus)
> +				break;
> +		}

Are you sure this logic is the same as the original?
pci_find_next_bus() does grab the needed lock here, so why do you think
this is racy?

And pci_bus_sem is just for root busses, not the individual busses,
right?

How was this tested?

>  	}
> -	return NULL;
> +	up_read(&pci_bus_sem);

Why not use guard() instead?

thanks,

greg k-h

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

* Re: [PATCH] PCI: Fix use-after-free race in pci_find_bus()
  2026-08-12  3:00 ` Greg KH
@ 2026-08-12  7:26   ` Mohamad Raizudeen
  2026-08-12  7:45     ` Greg KH
  0 siblings, 1 reply; 5+ messages in thread
From: Mohamad Raizudeen @ 2026-08-12  7:26 UTC (permalink / raw)
  To: Greg KH; +Cc: bhelgaas, skhan, jkoolstra, linux-pci, linux-kernel

On Wed, Aug 12, 2026 at 12:00:33PM +0900, Greg KH wrote:
> On Wed, Aug 12, 2026 at 08:17:13AM +0530, Mohamad Raizudeen wrote:
> > pci_find_bus() iterates over the list of PCI root buses using
> > pci_find_next_bus(). This helper acquires pci_bus_sem, retrieves the
> > next bus and drops the lock before returning the pointer to the caller.
> > 
> > pci_find_bus() then uses this pointer to check the domain and traverses
> > the child buses via pci_do_find_bus() without holding the pci_bus_sem
> > lock.
> > 
> > If a PCI bus is concurrently removed for example via hotplug between
> > loop iterations, the from pointer passed back into pci_find_next_bus()
> > becomes stale, leading to a user-after-free when dereferencing
> > from->node.next. Additionally, traversing the bus tree without holding
> > the lock is a race condition.
> 
> Did you find this actually happens?  How did you find this at all?

I found this purely through by reading and reviewing the code, while
analyzing the locking patterns in the PCI subsystem. I have not seen it
crash in production, but the race condition is statically clear from
reading the code.
>
> > 
> > Fix this by iterating pci_root_buses list directly using
> > list_for_each_entry() inside pci_find_bus() while holding the
> > pci_bus_sem read lock for the entire duration of the search. This
> > ensures the list and tree structures cannot change while being
> > traversed, eliminating the use-after-free.
> 
> Why do two changes here, and not just make a patch series?
I did both in one patch because they are connected. Since
pci_find_next_bus() drops the lock early, I couldn't use it to hold the
lock for the whole search. I had to change the loop to fix the locking.
> 
> > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> > Signed-off-by: Mohamad Raizudeen <raizudeen.kerneldev@gmail.com>
> > ---
> >  drivers/pci/search.c | 20 +++++++++++---------
> >  1 file changed, 11 insertions(+), 9 deletions(-)
> > 
> > diff --git a/drivers/pci/search.c b/drivers/pci/search.c
> > index e3d3177fce54..f50e83061b76 100644
> > --- a/drivers/pci/search.c
> > +++ b/drivers/pci/search.c
> > @@ -142,17 +142,19 @@ static struct pci_bus *pci_do_find_bus(struct pci_bus *bus, unsigned char busnr)
> >   */
> >  struct pci_bus *pci_find_bus(int domain, int busnr)
> >  {
> > -	struct pci_bus *bus = NULL;
> > -	struct pci_bus *tmp_bus;
> > +	struct pci_bus *bus;
> > +	struct pci_bus *tmp_bus = NULL;
> >  
> > -	while ((bus = pci_find_next_bus(bus)) != NULL)  {
> > -		if (pci_domain_nr(bus) != domain)
> > -			continue;
> > -		tmp_bus = pci_do_find_bus(bus, busnr);
> > -		if (tmp_bus)
> > -			return tmp_bus;
> > +	down_read(&pci_bus_sem);
> > +	list_for_each_entry(bus, &pci_root_buses, node) {
> > +		if (pci_domain_nr(bus) == domain) {
> > +			tmp_bus = pci_do_find_bus(bus, busnr);
> > +			if (tmp_bus)
> > +				break;
> > +		}
> 
> Are you sure this logic is the same as the original?
Yes, I used list_for_each_entry() that does the exact same thing as the
old while loop, but it let me keep the lock saefely for the whole
search.

> pci_find_next_bus() does grab the needed lock here, so why do you think
> this is racy?
You are right, it grabs the lock. But it drops the lock before returning
the bus pointer. So the caller then uses that pointer without a lock and
passes it back for the next loop. If a bus is removed in that moment,
the next call reads freed memory.
> 
> And pci_bus_sem is just for root busses, not the individual busses,
> right?
It protects the root bus list, but it also protects the child buses.
Since pci_do_find_bus() walks through the child buses, needed to hold
the lock to make that safe too.
> 
> How was this tested?
I compiled ir and booted it in x86_64 qemu vm. It booted fine without
any PCI crashes. I will run the same test on v2 patch before sending it.
> 
> >  	}
> > -	return NULL;
> > +	up_read(&pci_bus_sem);
> 
> Why not use guard() instead?
Honestly, I was so focused on getting the locking logic right that I
completely missed I meant forgot to use guard(). I will make sure to use
guard() in the v2 patch.
> 
> thanks,
> 
> greg k-h

Thanks & regards,
Mohamad Raizudeen 

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

* Re: [PATCH] PCI: Fix use-after-free race in pci_find_bus()
  2026-08-12  7:26   ` Mohamad Raizudeen
@ 2026-08-12  7:45     ` Greg KH
  0 siblings, 0 replies; 5+ messages in thread
From: Greg KH @ 2026-08-12  7:45 UTC (permalink / raw)
  To: Mohamad Raizudeen; +Cc: bhelgaas, skhan, jkoolstra, linux-pci, linux-kernel

On Wed, Aug 12, 2026 at 12:56:59PM +0530, Mohamad Raizudeen wrote:
> On Wed, Aug 12, 2026 at 12:00:33PM +0900, Greg KH wrote:
> > On Wed, Aug 12, 2026 at 08:17:13AM +0530, Mohamad Raizudeen wrote:
> > > pci_find_bus() iterates over the list of PCI root buses using
> > > pci_find_next_bus(). This helper acquires pci_bus_sem, retrieves the
> > > next bus and drops the lock before returning the pointer to the caller.
> > > 
> > > pci_find_bus() then uses this pointer to check the domain and traverses
> > > the child buses via pci_do_find_bus() without holding the pci_bus_sem
> > > lock.
> > > 
> > > If a PCI bus is concurrently removed for example via hotplug between
> > > loop iterations, the from pointer passed back into pci_find_next_bus()
> > > becomes stale, leading to a user-after-free when dereferencing
> > > from->node.next. Additionally, traversing the bus tree without holding
> > > the lock is a race condition.
> > 
> > Did you find this actually happens?  How did you find this at all?
> 
> I found this purely through by reading and reviewing the code, while
> analyzing the locking patterns in the PCI subsystem. I have not seen it
> crash in production, but the race condition is statically clear from
> reading the code.
> >
> > > 
> > > Fix this by iterating pci_root_buses list directly using
> > > list_for_each_entry() inside pci_find_bus() while holding the
> > > pci_bus_sem read lock for the entire duration of the search. This
> > > ensures the list and tree structures cannot change while being
> > > traversed, eliminating the use-after-free.
> > 
> > Why do two changes here, and not just make a patch series?
> I did both in one patch because they are connected. Since
> pci_find_next_bus() drops the lock early, I couldn't use it to hold the
> lock for the whole search. I had to change the loop to fix the locking.
> > 
> > > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> > > Signed-off-by: Mohamad Raizudeen <raizudeen.kerneldev@gmail.com>
> > > ---
> > >  drivers/pci/search.c | 20 +++++++++++---------
> > >  1 file changed, 11 insertions(+), 9 deletions(-)
> > > 
> > > diff --git a/drivers/pci/search.c b/drivers/pci/search.c
> > > index e3d3177fce54..f50e83061b76 100644
> > > --- a/drivers/pci/search.c
> > > +++ b/drivers/pci/search.c
> > > @@ -142,17 +142,19 @@ static struct pci_bus *pci_do_find_bus(struct pci_bus *bus, unsigned char busnr)
> > >   */
> > >  struct pci_bus *pci_find_bus(int domain, int busnr)
> > >  {
> > > -	struct pci_bus *bus = NULL;
> > > -	struct pci_bus *tmp_bus;
> > > +	struct pci_bus *bus;
> > > +	struct pci_bus *tmp_bus = NULL;
> > >  
> > > -	while ((bus = pci_find_next_bus(bus)) != NULL)  {
> > > -		if (pci_domain_nr(bus) != domain)
> > > -			continue;
> > > -		tmp_bus = pci_do_find_bus(bus, busnr);
> > > -		if (tmp_bus)
> > > -			return tmp_bus;
> > > +	down_read(&pci_bus_sem);
> > > +	list_for_each_entry(bus, &pci_root_buses, node) {
> > > +		if (pci_domain_nr(bus) == domain) {
> > > +			tmp_bus = pci_do_find_bus(bus, busnr);
> > > +			if (tmp_bus)
> > > +				break;
> > > +		}
> > 
> > Are you sure this logic is the same as the original?
> Yes, I used list_for_each_entry() that does the exact same thing as the
> old while loop, but it let me keep the lock saefely for the whole
> search.
> 
> > pci_find_next_bus() does grab the needed lock here, so why do you think
> > this is racy?
> You are right, it grabs the lock. But it drops the lock before returning
> the bus pointer. So the caller then uses that pointer without a lock and
> passes it back for the next loop. If a bus is removed in that moment,
> the next call reads freed memory.
> > 
> > And pci_bus_sem is just for root busses, not the individual busses,
> > right?
> It protects the root bus list, but it also protects the child buses.
> Since pci_do_find_bus() walks through the child buses, needed to hold
> the lock to make that safe too.
> > 
> > How was this tested?
> I compiled ir and booted it in x86_64 qemu vm. It booted fine without
> any PCI crashes. I will run the same test on v2 patch before sending it.

Booting in a vm is very simple as a vm does not have many PCI devices.
Try it on a real system with a big topology as well as a pci hotplug
system please.

Also note that this function should only be called when pci devices are
being added to the system, so odds are it can't race with a device being
removed due to the pci bus lock in the first place, right?

thanks,

greg k-h

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

end of thread, other threads:[~2026-08-12  7:46 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12  2:47 [PATCH] PCI: Fix use-after-free race in pci_find_bus() Mohamad Raizudeen
2026-08-12  2:59 ` sashiko-bot
2026-08-12  3:00 ` Greg KH
2026-08-12  7:26   ` Mohamad Raizudeen
2026-08-12  7:45     ` Greg KH

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.